Compare commits

...

50 Commits

Author SHA1 Message Date
dan 5f0c6bc3a5 Fix an error in README-server-edition.html.
FossilOrigin-Name: 754ad35cd26da361e2ed736b0e400497714a0db9b7fd05fd24e7803b6f478263
2018-03-31 18:43:20 +00:00
dan bddcb53614 Update and add further detail to README-server-edition.html.
FossilOrigin-Name: 337a0b67e30f1030fdc59f712e5914f4801b0e9e4ae19a1e82c10b73eb3f4773
2018-03-30 20:42:25 +00:00
dan fd971c8b83 Update this branch with latest trunk changes.
FossilOrigin-Name: df52e89fff54dbb650cd1fb2b7afe0467acea96a0056728ef48e0c3fea40eeb2
2018-03-28 15:41:57 +00:00
dan 332faddd99 Add a Tcl script to run the performance tests described in
README-server-edition.html.

FossilOrigin-Name: 1b3df8ffc551df0e4d8bcb633e1549ba769d8866cfcffea4cb62949ecf5c4c99
2017-08-29 17:52:14 +00:00
dan 7c4805fa5c Ensure that write-locks on pages are dropped at the end of each
write transaction, even if there is still a read transaction open.

FossilOrigin-Name: 2dd36ade9ea948de27b217b83cbf704e071e6ea5dddb8566165a645143b0f846
2017-08-19 15:50:45 +00:00
dan 852841cc14 Add tests to this branch.
FossilOrigin-Name: abb6e076c851c0b10f62c02d0d7e54f24d86c75f01036dddbc0a8a7dfc627a0d
2017-08-18 18:55:22 +00:00
dan 528e4e4bbf Do not search for locks to clear when connecting to a db in multi-process mode
unless it looks like the previous user of the client-id crashed.

FossilOrigin-Name: 66fb9e1cb479f1e764f1606f041bd97fd3bd428093832c000ee36b643377e9e2
2017-08-18 16:04:40 +00:00
dan 335ce3575f Add support for crash recovery in multi-process mode. And add test cases for
the same.

FossilOrigin-Name: a8115f95e80cc90c095fdd0a151da51f4d3ee427defdc34780e977585a68776d
2017-08-17 19:32:02 +00:00
dan 4c454ff90b Update this branch with the latest changes from trunk.
FossilOrigin-Name: 380a7b7a458b212b0463c8e128c289cc70f2fdb9b9a63b91fc7542c120eb9c10
2017-08-16 17:06:42 +00:00
dan 7ba5f0e768 Enhance this branch to support page-level-locking (without MVCC) for
multi-process deployments.

FossilOrigin-Name: 04e0cb571dbed00e269a890a755e252d7e8204d6d2ed5a7cfdb3d78d990a2876
2017-08-16 16:52:14 +00:00
dan e278a9bacd Remove code for PLL support in wal mode from this branch.
FossilOrigin-Name: 8e1b28ed3e83eba0b2113316a40ed1165e0e051220b68863cb70734c95a82c2a
2017-08-14 07:16:18 +00:00
dan b585dbf8db Update this branch with latest trunk changes.
FossilOrigin-Name: bc2498d60fa22b587ad463ec697abe82d1c87abb7fd0d2cb60cc7316cfd7cec7
2017-08-14 06:55:22 +00:00
dan cfcd9d34b9 Update this branch with latest trunk changes.
FossilOrigin-Name: 17bc7ded07fe2f1f47857bba646eb297e53f69fb1bafd68556d60700ce008750
2017-08-07 14:15:18 +00:00
dan 44bbccc05a Fix a bug on this branch involving mmap mode and readonly transactions.
FossilOrigin-Name: 355c594e821bb5ad782faf0e8d512305f4e1d7fd60949887a8270beed67d03e3
2017-08-07 14:06:01 +00:00
dan 0a1c5dc414 Add documentation file ./README-server-edition.html.
FossilOrigin-Name: fbc4f4ad259cfe04b28d698ef025f179b6708ac788939976c401db2caf8f92c1
2017-07-31 19:55:56 +00:00
dan 0ee56139e9 Merge latest trunk changes with this branch.
FossilOrigin-Name: b42c87790e2dfdc4e52c42967257decf4b44ded909403ea4ac47fe41acfd7077
2017-07-29 17:10:37 +00:00
dan af12f8e2c9 Update test program "tserver" to use a native pthreads mutex/condition
variable to efficiently manage wal file checkpoints without the wal file
growing indefinitely.

FossilOrigin-Name: 8299bdb7cbede30c665dda131bdcbd1d260b3ae9bd16d9b414d8c3776b08f1b3
2017-07-29 17:01:06 +00:00
dan aa90aad86b Reduce the number of mallocs required of writers in server mode.
FossilOrigin-Name: 60953997f62208f82b1efb53b8a1b0c6a26370411041457f747917e10d9a0e68
2017-07-28 21:02:13 +00:00
dan b4b69facc0 Update this branch with latest changes from trunk.
FossilOrigin-Name: d0719ad757bdf7cf2d7d7a4f7b0e713c262ffb434b91ddbb12e190e479abd19e
2017-07-24 19:43:58 +00:00
dan a12baadd15 Only open a db in server-mode if there is a directory named "db-journal"
in the file-system and the VFS is an exclusive locking VFS.

FossilOrigin-Name: e77d29f64480179d7aae7d88adab2ba9ff042701e1f4e624a3a7775cb56a51cc
2017-07-24 19:23:20 +00:00
dan 11584e6779 Merge latest trunk changes with this branch.
FossilOrigin-Name: be0df0a65fc781fde6e94c9370b4819c703d3b270c707dd59e587a2a6f36c9f4
2017-07-19 18:54:21 +00:00
dan 6af090cc05 Add simple tests for "PRAGMA freelist_format".
FossilOrigin-Name: 98a36f4cf127ceec423952a3d80d8d22163f128a5e6290cf6144c59029332944
2017-07-14 08:15:16 +00:00
dan e1e878687d Make the freelist format a separate feature from the page-level locking.
Freelist format is now configure using "PRAGMA freelist_format".

FossilOrigin-Name: dcc407972aa213d28f95cbaf4da5ab7d89ddbad2740f41cfb44b2c2ce20a3132
2017-07-13 21:06:51 +00:00
dan e83dba9e4d Add SQL function usleep() to test program tserver.c.
FossilOrigin-Name: 8cbe8f2b2fe0a5a246401718da8db8d487ced232ada73b53bdc9f9608c47d50e
2017-07-11 18:38:26 +00:00
dan 26a8ddbcdb Fix a bug causing readonly mvcc connections to read the wrong cache entry in
some cases.

FossilOrigin-Name: b6157267f9d4ecdffbdf4b1e591b8d6baaaa964c418ec247a24ccb82146f9041
2017-07-11 16:47:26 +00:00
dan 087794b136 Support read-only MVCC transactions in server-mode. Started using "BEGIN
READONLY".

FossilOrigin-Name: 5a043aa8dd0751e644c495a59deea5fe05da905f49c664d978fe477f9240bc37
2017-07-08 20:46:17 +00:00
dan ad9b696471 Merge latest trunk changes with this branch.
FossilOrigin-Name: 216c757f9289af818482253f77b0b2c9b09e8a478caf67d70a4a6da2fabbc642
2017-07-07 16:40:28 +00:00
dan f42e5d8917 Have this branch maintain an in-memory hash-table of old pages for read-only
MVCC clients. There is no way to access it yet.

FossilOrigin-Name: a3a9a2e1899cc963315590ef4666972e9d92986843706a551962ed16661a19b2
2017-07-07 16:12:45 +00:00
dan 411c20ad62 Merge tserver fixes with this branch.
FossilOrigin-Name: 58a0aab8fd5f5dcbec867468b7f4c1b67c3e16bbe3aeea6bd21ae6b04c2b93f1
2017-06-28 20:21:15 +00:00
dan 6b0cfe5343 Fix bugs in test program tserver.c.
FossilOrigin-Name: 093b9108ea3532437683547c82f756df2413013a50a2fddad3d928cf8d74f9eb
2017-06-28 20:12:41 +00:00
dan 6f3283a427 Support clients within a single process only.
FossilOrigin-Name: dfa9a4d53e0aa7f7911b11af57a4d37a1df2d835cf60eb9e76d28b6b39c2ba58
2017-06-27 20:23:07 +00:00
dan bab38cbc9a Ensure that test tool "tserver" finalizes all statements before attempting to
close a database handle.

FossilOrigin-Name: d8568aacf083b63cdcf68d744ddb07a7615d14f169be6c632813a266a1e8a1cf
2017-06-20 19:20:07 +00:00
dan 538ed8e376 Add too/tserver.c - the implementation of a simple multi-threaded server
designed for interactive testing of concurrency between connections used by
different threads of the same process.

FossilOrigin-Name: 05b4fc4340011847c8585bb822d339dd7b8351266a12b26fdc85edce38fc9dd3
2017-06-07 15:55:59 +00:00
dan b4c4601adf Add code to this branch to emit a log message after each cumulative second
that the WRITER lock has been held.

FossilOrigin-Name: a726d98122704e1e702cfa7ae61d680497df6a826d98082161e0823e115d40a5
2017-05-22 08:01:58 +00:00
dan 60dd40a62c Avoid writer starvation by adding a RESERVED state to page locks.
FossilOrigin-Name: 9b7f80246f2b9921483ab23457865e783ee70b93f67bcecc0c16516447a05875
2017-05-15 19:32:58 +00:00
dan 2ac63b9812 Avoid running recovery while there is another read/write client.
FossilOrigin-Name: a38858a24c6edaa05966b7e158e603bcbde8b05c6233e3bb005cfb32bc91ea06
2017-05-13 19:07:10 +00:00
dan bf431367f3 Require exclusive access to the db to wrap the wal file. Have "PRAGMA
wal_checkpoint = restart" block for this.

FossilOrigin-Name: cbf44ed9758d577e1450b53e645b73c9ca1ee29d2354ce6375c234a41a063400
2017-05-12 18:52:27 +00:00
dan 730859568d Fix a problem causing a lock to be held past the end of a transaction. Use a
blocking lock to take the read-lock on page 1 taken by all transactions.

FossilOrigin-Name: 2584df3d42ece69d37f31f3655cd0d4760914bea73d4f4ccb7f2a7aa47f80f49
2017-05-10 16:18:05 +00:00
dan c9bcad12ea Use a blocking call to obtain the wal-mode WRITER lock in some cases.
FossilOrigin-Name: 4464ca1d686b5c457995cc885d4a8704e402ad387aa4cc37de199276b28cc08e
2017-05-10 13:46:29 +00:00
dan e376cac1d1 Fix a problem with wrapping the log file in server mode.
FossilOrigin-Name: 270b7d1eacb57827465946262b31c5c890d7fc5b5618ccd9e220bc2f9546de54
2017-05-09 16:32:19 +00:00
dan b7bcf5c16c Add some support for wal mode to the hack on this branch.
FossilOrigin-Name: b733afc1d0abc09861903ce8e27a8f2462ec871967f5d3dc2847b31bb28b55f3
2017-05-08 20:15:23 +00:00
dan 31349393ba Update this branch with latest trunk changes.
FossilOrigin-Name: ed6bad67f530afa2fba1bce339a9772f281bd108666ca6c23642efcefa2d98e5
2017-05-06 16:04:58 +00:00
dan 8df9974169 Update this branch with latest trunk changes.
FossilOrigin-Name: 83b1e163ef6ac01d252268415f26a2a999670de583491becc091d294075980bc
2017-05-01 20:07:59 +00:00
dan f427296520 Avoid reading the file-size before page 1 is read-locked.
FossilOrigin-Name: f6b025bf632501f51e691a05d309225671fd78bc62c9a43dd916d35ba4cf6d0e
2017-04-29 15:48:32 +00:00
dan 920a129de3 Fix various bugs revealed by running performance tests.
FossilOrigin-Name: a40b42c422a53aa99280eca7ba419a3937699e273efa3377e0fefe52582cbbd4
2017-04-28 17:32:57 +00:00
dan 34d02325be Add a missing releasePage() call to the server-mode free-list management code.
FossilOrigin-Name: a5a085483c66b17727cf4d2a15165621016031ab78990cb5977ccdbd231d13ac
2017-04-28 14:09:55 +00:00
dan aa189cf00f Use a different free-list format for server-mode databases in order to reduce
contention.

FossilOrigin-Name: 778e8a102d8dc7b0fa006c2d90b0a8cd36ebc1e16bd17477f2d536fc0cef4bf3
2017-04-28 10:20:03 +00:00
dan 2dab053e22 Do not write master journal filenames into server-mode journal files. Use
SQLITE_MUTEX_STATIC_APP1 to protect critical sections in server.c.

FossilOrigin-Name: 3144ae40d2eb63dfd5587579a49163ea1add5947d624daa478ada13339495af4
2017-04-27 14:12:43 +00:00
dan 0561247d07 If possible, delete the journal file when a database connection is closed.
FossilOrigin-Name: d5b5326df25b85b1c3926cd693bcde1ca08e6e03b8aea151a82d222fc9c23dd6
2017-04-27 13:05:43 +00:00
dan 1381bbc262 Experimental implementation of pessimistic page-level locking based on
rollback mode.

FossilOrigin-Name: 64ecf7c7e512827e8a5a42f9f3ad92ff57ec868820e3943dbc74d5823f9a889d
2017-04-26 20:45:00 +00:00
35 changed files with 4122 additions and 116 deletions
+440
View File
@@ -0,0 +1,440 @@
<html>
<center>
<h1> The "server-process-edition" Branch</h1>
</center>
<p>
The "server-process-edition" branch contains two modifications to stock
SQLite that work together to provide concurrent read/write transactions
using pessimistic page-level-locking. The system runs in two modes:
<ul>
<li><p> Single-process mode - where all clients must be within the
same address space, and
<li><p> Multi-process mode - where clients may be distributed between
multiple OS processes.
</ul>
<p> The system is designed to be most efficient when used with
<a href="https://www.sqlite.org/pragma.html#pragma_synchronous">
"PRAGMA synchronous=OFF"</a>, although it does not require this.
<p>
Up to 16 simultaneous read/write transactions controlled by page-level-locking
are possible. Additionally, in single-process mode there may be any number of
read-only transactions started using the "BEGIN READONLY" command. Read-only
transactions do not block read-write transactions, and read-write transactions
do not block read-only transactions. Read-only transactions access a consistent
snapshot of the database - writes committed by other clients after the
transaction has started are never visible to read-only transactions. In
multi-process mode, the "BEGIN READONLY" command is equivalent to a stock
"BEGIN".
<p>
The two features on this branch are:
<ol>
<li><p> An
<a href=#freelist>alternative layout for the database free-page list</a>.
This is intended to reduce contention between writers when allocating
new database pages, either from the free-list or by extending the
database file.
<li><p> The <a href=#servermode>"server-mode" extension</a>, which
provides read/write page-level-locking concurrency and (in
single-process mode) read-only MVCC concurrency mentioned above.
</ol>
<h2 id=freelist> 1.0 Alternative Free-List Format </h2>
<p>
The alternative free-list format is very similar to the current format. It
differs in the following respects:
<ul>
<li><p>The "total number of free pages" field in the db header is not
maintained. It is always set to zero.
<li><p> Instead of pointing to the first free-list trunk page, the free-list
pointer in the db header points to a page known as the "free-list node".
<li><p> The free-list node page contains N pointers to free-lists stored in the
legacy format (i.e. a linked list of trunk pages each containing
pointers to many free leaf pages).
</ul>
<p>
This effectively means that a database has N free-lists instead of just one. To
allocate a free page, a writer only needs to lock one such free-list, and so up
to N transactions that allocate new pages may proceed concurrently.
<p>
Allocating pages from the end of the db file still locks out all other
read/write transactions (because it involves writing to page 1, which every
transaction needs to read). To minimize the frequency with which this occurs,
when a page must be allocated from the end of the database file, the file is
extended by 2048 pages. These are distributed equally between 16 free-lists
(children of the free-list node page). Additionally, the first trunk page in
each free list is never reused. Doing so would require writing to the
free-list node page - effectively an exclusive lock on the entire
page-allocation system.
<p>
The current format used for the free-list can be modified or queried using a
new pragma:
<pre>
PRAGMA [database.]freelist_format;
PRAGMA [database.]freelist_format = 1|2;
</pre>
<p>
At present, the free-list format may only be modified when the free-list is
completely empty. Which, as the implementation ensures that a free-list that
uses the alternative format is never completely emptied, effectively precludes
changing the format from 2 (alternative) to 1 (legacy).
<p>
For databases that use the "alternative" free-list format, the read and write
versions in the database header (byte offsets 18 and 19) are set to 3 for
rollback mode or 4 for wal mode (instead of 1 and 2 respectively).
<h2 id=servermode> 2.0 Page level locking - "Server Mode" </h2>
<p>
A database client automatically enters "server mode" if there exists a
<i>directory</i> named "&lt;database&gt;-journal" in the file system alongside
the database file "&lt;database&gt;" There is currently no provision for
creating this directory, although it could be safely done for a database in
rollback mode using something like:
<pre>
PRAGMA journal_mode = off;
BEGIN EXCLUSIVE;
&lt;create directory&gt;
END;
</pre>
<p> As well as signalling new clients that they should enter server-mode,
creating a directory named "&lt;database&gt;-journal" has the helpful
side-effect of preventing legacy clients from accessing the database file at
all.
<p> If the VFS is one that takes an exclusive lock on the db file (to
guarantee that no other process accesses the db file), then the system
automatically enters single-process mode. Otherwise, multi-process mode.
<p> In both single and multi-process modes, page-level-locking is managed
by allocating a fixed-size array of "locking slots". Each locking slot is
32-bits in size. By default, the array contains 262144 (2^18) slots. Pages are
assigned to locking slots using the formula (pgno % 262144) - so pages 1,
262145, 524289 etc. share a single locking slot.
<p> In single-process mode, the array of locking slots is allocated on
the process heap and access is protected by a mutex. In multi-process mode, it
is created by memory-mapping a file on disk (similar to the *-shm file in
SQLite wal mode) and access is performed using
<a href="https://en.wikipedia.org/wiki/Compare-and-swap">atomic CAS
primitives</a> exclusively.
<p> Each time a read/write transaction is opened, the client assumes a client
id between 0 and 15 for the duration of the transaction. Client ids are unique
at any point in time - concurrently executing transactions must use different
client ids. So there may exist a maximum of 16 concurrent read/write
transactions at any one time.
<p> Read/write transactions in server-mode are similar to regular SQLite
transactions in rollback mode. The most significant differences are that:
<ul>
<li> <p>Instead of using journal file &lt;database&gt;-journal, server-mode
clients use &lt;database&gt;-journal/&lt;client-id&gt;-journal. If
there are multiple concurrent transactions, each uses a separate
journal file.
<li> <p>No database-wide lock is taken. Instead, individual read and write
locks are taken on the pages accessed by the transaction.
</ul>
<p> Each locking slot is 32-bits in size. A locking slot may simultaneously
support a single write-lock, up to 16 read-locks from read/write clients, and
(in single process mode) up 1024 read-locks from "BEGIN READONLY" clients.
Locking slot bits are used as follows:
<ul>
<li> <p> The least-significant 16-bits are used for read-locks taken by
read/write clients. To take a read-lock, bit &lt;client-id&gt; of the
locking slot is set.
<li> <p> The next 5 bytes are used for the write-lock. If no write-lock
is held on the slot, then this 5 byte integer is set to 0. Otherwise,
it is set to (<i>C</i> + 1), where <i>C</i> is the &lt;client-id&gt; of
the client holding the write-lock.
<li> <p> The next 10 bits contain the total number of read-locks held by
"BEGIN READONLY" clients on the locking slot. See the section below
for a description of how these are used.
</ul>
<p> Currently, if a client requests a lock that cannot be granted due to
a conflicting lock, SQLITE_BUSY is returned to the caller and either the
entire transaction or statement transaction must be rolled back. See
<a href=#problems>Problems and Issues</a> below for more details.
<h3> 2.1 Single-Process Mode </h3>
<p> Single process mode is simpler than multi-process mode because it does
not have to deal with runtime client failure - it is assumed that if one
client fails mid-transaction the entire process crashes. As a result the
only time hot-journal rollback is required in single-process mode is as
part of startup. The first client to connect to a database in single-process
mode attempts to open and rollback all 16 potential hot journal files.
<p> But, in order to support non-blocking "BEGIN READONLY" transactions, it is
also in some ways more complicated than multi-process mode. "BEGIN READONLY"
support works as follows:
<ul>
<li> <p>In single-process mode, writers never spill the cache mid-transaction.
Data is only written to the database as part of committing a transaction.
<li> <p>As well as writing the contents of overwritten pages out to the journal
file, a writer in single-process mode also accumulates a list of buffers
containing the original data for each page overwritten by the current
transaction in main-memory.
<li> <p>When a transaction is ready to be committed, a writer obtains a
transaction-id. Transaction-ids are assigned to writers using a
monotonically increasing function. The writer then adds all of its "old
data" buffers to a hash table accessible to all database clients.
Associated with each hash table entry is the newly assigned transaction-id.
It then waits (spin-locks) for all "BEGIN READONLY" read-locks to clear on
all pages that will be written out by the transaction. Following this, it
commits the transaction as normal (writes out the dirty pages and zeroes
the journal file header).
<li> <p>Clients executing "BEGIN READONLY" transactions are not assigned
a &lt;client-id&gt;. Instead, they are assigned a transaction-id that is
either (a) that of the oldest transaction-id belonging to a writer that has
not yet finished committing, or (b) if there are currently no writers
committing then the value that will be assigned to the next committer.
<li> <p>When a "BEGIN READONLY" transaction reads a page, it first checks
the aforementioned hash table for a suitable entry. A suitable entry
is one with the right page-number and a transaction-id greater than or
equal to that of the "BEGIN READONLY" transaction (i.e. one that had not
finished committing when the BEGIN READONLY transaction started). If such
an entry can be found, the client uses the associated data instead of
reading from the db file. Or, if no such entry is found, the client:
<ol>
<li> Increments the number of BEGIN READONLY read-locks on the page.
<li> Reads the contents of the page from the database file.
<li> Decrements the number of BEGIN READONLY read-locks on the page.
</ol>
<p> The mutex used to protect access to the array of locking slots and
the shared hash table is relinquished for step 2 above.
<li> <p>After each transaction is commited in single-process mode, the
client searches the hash table for entries that can be discarded. An
entry can be discarded if it has a transaction-id older than any still
in use (either by BEGIN READONLY transactions or committers).
</ul>
<h3> 2.2 Multi-Process Mode </h3>
<p> Multi-process mode differs from single-process mode in two important ways:
<ul>
<li> <p>Individual clients may fail mid-transaction and the system must recover
from this.
<li> <p>Partly as a consequence of the above, there are no convenient
primitives like mutexes or malloc() with which to build complicated data
structures like the hash-table used in single-process mode. As a result,
there is no support for "BEGIN READONLY" transactions in multi-process
mode.
</ul>
<p> Unlike single-process mode clients, which may be assigned a different
client-id for each transaction, clients in multi-process mode are assigned a
client-id when they connect to the database and do not relinquish it until
they disconnect. As such, a database in multi-process server-mode supports
at most 16 concurrent client connections.
<p> As well as the array of locking slots, the shared-memory mapping used
by clients in multi-process mode contains 16 "client slots". When a client
connects, it takes a posix WRITE lock on the client slot that corresponds
to its client id. This lock is not released until the client disconnects.
Additionally, whenever a client starts a transaction, it sets the value
in its client locking slot to 1, and clears it again after the transaction
is concluded.
<p> This assists with handling client failure mid-transaction in two ways:
<ul>
<li><p> If client A cannot obtain a lock due to a conflicting lock held by
client B, it can check whether or not client B has failed by attempting a
WRITE lock on its client locking slot. If successful, then client B must
have failed and client A may:
<ul>
<li> Roll back client B's journal, and
<li> By iterating through the entire locking slot array, release all
locks held by client B when it failed.
</ul>
<li><p> When a client first connects and locks its client locking slot, it
can check whether or not the previous user of the client locking slot failed
mid-transaction (since if it did, the locking slot value will still be
non-zero). If it did, the new owner of the client locking slot can release
any locks and roll back any hot-journal before proceeding.
</ul>
<h3> 2.3 Required VFS Support </h3>
<p> The server-mode extension requires that the VFS support various special
file-control commands. Currently support is limited to the "unix" VFS.
<dl>
<dt> SQLITE_FCNTL_SERVER_MODE
<dd><p> This is used by SQLite to query the VFS as to whether the
connection should use single-process server-mode, multi-process server-mode,
or continue in legacy mode.
<p>SQLite invokes this file-control as part of the procedure for detecting a
hot journal (after it has established that there is a file-system entry named
&lt;database&gt;-journal and that no other process holds a RESERVED lock).
If the &lt;database&gt;-journal directory is present in the file-system and
the current VFS takes an exclusive lock on the database file (i.e. is
"unix-excl"), then this file-control indicates that the connection should use
single-process server-mode. Or, if the directory exists but the VFS does not
take an exclusive lock on the database file, that the connection should use
multi-proces server-mode. Or, if there is no directory of the required name,
that the connection should use legacy mode.
<dt> SQLITE_FCNTL_FILEID
<dd><p> Return a 128-bit value that uniquely identifies an open file on disk
from the VFS. This is used to ensure that all connections to the same
database from within a process use the same shared state, even if they
connect to the db using different file-system paths.
<dt> SQLITE_FCNTL_SHMOPEN
<dd>
<dt> SQLITE_FCNTL_SHMOPEN2
<dd>
<dt> SQLITE_FCNTL_SHMLOCK
<dd>
<dt> SQLITE_FCNTL_SHMCLOSE
<dd>
</dl>
<h2 id=problems> 3.0 Problems and Issues </h2>
<ul>
<li> <p>Writer starvation might be the biggest issue. How can it be
prevented?
<li> <p>Blocking locks of some sort would likely improve things. The issue
here is deadlock detection.
<li> <p>The limit of 16 concurrent clients in multi-process mode could be
raised to 27 (since the locking-slot bits used for BEGIN READONLY
locks in single-process mode can be reassigned to support more
read/write client read-locks).
</ul>
<h2> 4.0 Performance Test </h2>
<p>
The test uses a single table with the following schema:
<pre>
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);
</pre>
<p>
The database initially contains 5,000,000 rows. Values for column "a" are
between 1 and 5,000,000, inclusive. Other columns are populated with randomly
generated blob values, each 16, 16, and 400 bytes in size, respectively.
<p>
Read/write transactions used by the test take the following form. Each such
transaction modifies approximately 25 pages (5 in the main table and 10 in each
index), not accounting for tree rebalancing operations.
<pre>
BEGIN;
REPLACE INTO t1 VALUES(abs(random() % 5000000), randomblob(16), randomblob(16), randomblob(400));
REPLACE INTO t1 VALUES(abs(random() % 5000000), randomblob(16), randomblob(16), randomblob(400));
REPLACE INTO t1 VALUES(abs(random() % 5000000), randomblob(16), randomblob(16), randomblob(400));
REPLACE INTO t1 VALUES(abs(random() % 5000000), randomblob(16), randomblob(16), randomblob(400));
REPLACE INTO t1 VALUES(abs(random() % 5000000), randomblob(16), randomblob(16), randomblob(400));
COMMIT;
</pre>
<p>
Read-only transactions are as follows:
<pre>
BEGIN READONLY;
SELECT * FROM t1 WHERE a&gt;abs((random()%5000000)) LIMIT 10;
SELECT * FROM t1 WHERE a&gt;abs((random()%5000000)) LIMIT 10;
SELECT * FROM t1 WHERE a&gt;abs((random()%5000000)) LIMIT 10;
SELECT * FROM t1 WHERE a&gt;abs((random()%5000000)) LIMIT 10;
SELECT * FROM t1 WHERE a&gt;abs((random()%5000000)) LIMIT 10;
END;
</pre>
<p>
The performance test features one or more clients executing read/write
transactions as fast as possible, and zero or more clients executing read-only
transactions, also as fast as possible. All tests use the "unix-excl" VFS and
all clients execute in a separate thread within the same process. The database
is located on a tmpfs file-system.
<p>
In the table below "rw:" refers to the number of read-write clients, and "ro:"
the number of read-only clients used by the test. The TPS values in brackets
are the number of read-only transactions per second. All other values are
read-write transactions per second.
<p>
The collision rate (percentage of attempted transactions that failed due to a
page-level locking conflict) in all tests was between 1 and 2%. Failed
transactions are not included in the TPS counts below.
<p>
<table border=1 width=90% align=center>
<!--
320 jm=persist, rw:1 139675, 135797
320 jm=wal, rw:1 120995, 118889
begin-concurrent, rw:1 119438, 117580
begin-concurrent, rw:2 166923, 166904
begin-concurrent, rw:3 180432, 172825
begin-concurrent, rw:2,ro:1 150427(347319),152087(351601)
server-mode, rw:1 126592, 126742
server-mode, rw:2 228317, 227155
server-mode, rw:3 309712, 306218
server-mode, rw:2,ro:1 213303(576032),210994(556005)
-->
<tr><th> Configuration <th>TPS per client <th> TPS total
<tr><td> 3.20.0, journal_mode=persist, rw:1, ro:0 <td>6886 <td> 6886
<tr><td> 3.20.0, journal_mode=wal, rw:1, ro:0 <td>5997 <td> 5997
<tr><td> begin-concurrent, rw:1, ro:0<td>5925<td> 5925
<tr><td> begin-concurrent, rw:2, ro:0<td>4172 <td> 8345
<tr><td> begin-concurrent, rw:3, ro:0<td>2943 <td> 8831
<tr><td> begin-concurrent, rw:2, ro:1<td>3781 <td> 7562 (17473)
<tr><td> server-mode, rw:1, ro:0 <td>6333 <td> 6333
<tr><td> server-mode, rw:2, ro:0<td> 5693 <td> 11386
<tr><td> server-mode, rw:3, ro:0<td>5132 <td> 15398
<tr><td> server-mode, rw:2, ro:1<td>5303 <td> 10607 (28300)
</table>
+3
View File
@@ -71,6 +71,7 @@ LIBOBJ+= vdbe.o parse.o \
pager.o pcache.o pcache1.o pragma.o prepare.o printf.o \
random.o resolve.o rowset.o rtree.o \
select.o sqlite3rbu.o status.o stmt.o \
server.o \
table.o threads.o tokenize.o treeview.o trigger.o \
update.o userauth.o util.o vacuum.o \
vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbesort.o \
@@ -148,6 +149,8 @@ SRC = \
$(TOP)/src/resolve.c \
$(TOP)/src/rowset.c \
$(TOP)/src/select.c \
$(TOP)/src/server.c \
$(TOP)/src/server.h \
$(TOP)/src/status.c \
$(TOP)/src/shell.c.in \
$(TOP)/src/sqlite.h.in \
+39 -27
View File
@@ -1,10 +1,11 @@
C Minor\scomment\schanges.
D 2018-03-28T15:06:39.960
C Fix\san\serror\sin\sREADME-server-edition.html.
D 2018-03-31T18:43:20.756
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc bdcad21b027a56a73e54a1121cfb9edd0a35c0abfa53aa12c2f996006ff99960
F README-server-edition.html b98409c486d6f02871b20a9e29e1e18cd050a02e03062569ffb051774b4d6861
F README.md 1d5342ebda97420f114283e604e5fe99b0da939d63b76d492eabbaae23488276
F VERSION cdf91ac446255ecf3d8f6d8c3ee40d64123235ae5b3cef29d344e61b45ec3759
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
@@ -413,7 +414,7 @@ F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
F main.mk 63668484c95454af7fc04a384da27ac556f27368d6d0c345e405e1677c66768f
F main.mk 3b8fd9c7783cb6790a22d414ce0653bf1073bf4ebb025abbe18e50a946075ff2
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@@ -432,10 +433,10 @@ F src/auth.c 6277d63837357549fe14e723490d6dc1a38768d71c795c5eb5c0f8a99f918f73
F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b
F src/bitvec.c 17ea48eff8ba979f1f5b04cc484c7bb2be632f33
F src/btmutex.c 0e9ce2d56159b89b9bc8e197e023ee11e39ff8ca
F src/btree.c 9eb9531c65346bbfccf5325384b7db1849daf4db6601dcfe21ba5c5b20623b64
F src/btree.h 0866c0a08255142ea0e754aabd211c843cab32045c978a592a43152405ed0c84
F src/btree.c ac5e98b809c0e7e2d1840afa0908f6aa2542cf7bed631aab17199047a35b5588
F src/btree.h 6fb019c0097f90a5c02fffdf8217bc1eb86b8bd5f286a5bdf269e8cfa29ba668
F src/btreeInt.h 620ab4c7235f43572cf3ac2ac8723cbdf68073be4d29da24897c7b77dda5fd96
F src/build.c 8b53aacc26944bb7fd9ab5ddeedecb4cc7c4b84df3a420cf6d2b8f772ad421df
F src/build.c 4b085737d385ab2f07e07a8a5ef64d7378dad112ecf36f6150388b80dd2dcdb5
F src/callback.c fe677cb5f5abb02f7a772a62a98c2f516426081df68856e8f2d5f950929b966a
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
F src/ctime.c bd9da3f1ff21b432564a16ef0b154cff03585dc43742842e99c58907c6cb4bef
@@ -475,34 +476,36 @@ F src/os.c 750d7dca7eff3d76566fc71057e6960316914e3557776e8f50d4314f01090317
F src/os.h 48388821692e87da174ea198bf96b1b2d9d83be5dfc908f673ee21fafbe0d432
F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
F src/os_unix.c 2b53b0b8ddc580db096252c721729e5f5f2f355b4fc056f8f3fb328aeb3c9e8a
F src/os_unix.c e853b5922c4b4bc04bd181289bc2b9756f4bb0a3b5861554bc31937abb0e7a7b
F src/os_win.c eb03c6d52f893bcd7fdd4c6006674c13c1b5e49543fec98d605201af2997171c
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
F src/pager.c 8f2611ef1eb92a18e1605cb4ff37dfcc05acc6000eb6c6c263105ef5aba54661
F src/pager.h c571b064df842ec8f2e90855dead9acf4cbe0d1b2c05afe0ef0d0145f7fd0388
F src/parse.y 140bbc53b5f67f731239f7fc8704a4f1e60cbbc10fb84bf9577322f974725f19
F src/pager.c b99ae56c331ea1129d06c0e6634daa4a7fa544dffe76a4f520febbc9b9afc0a8
F src/pager.h d0fcb55b76087aa8fd605492451edcf03a5aaaa5eac074db8c438ee9185ca832
F src/parse.y a3ab90377e3a58309802ccd866e4df7a9a0eb7d7405ec4c22f05dd352b9c363e
F src/pcache.c 135ef0bc6fb2e3b7178d49ab5c9176254c8a691832c1bceb1156b2fbdd0869bd
F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170
F src/pcache1.c 716975564c15eb6679e97f734cec1bfd6c16ac3d4010f05f1f8e509fc7d19880
F src/pragma.c bea56df3ae0637768c0da4fbbb8f2492f780980d95000034a105ff291bf7ca69
F src/pragma.h bb83728944b42f6d409c77f5838a8edbdb0fe83046c5496ffc9602b40340a324
F src/pragma.c 5a4145c6cef2710c4fc638768d0a6f48b085394c81c5b4b4baf2068d0ae81995
F src/pragma.h a59d572cbc35d210a610264a20403e209165d581bc087ceb3a75b8dc3c2553f7
F src/prepare.c b086fea6a1952db88beca31fdd621201ee5e4ce3f02905248cc3035a8174aa89
F src/printf.c d3b7844ddeb11fbbdd38dd84d09c9c1ac171d21fb038473c3aa97981201cc660
F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
F src/resolve.c 66c73fcb7719b8ff0e841b58338f13604ff3e2b50a723f9b8f383595735262f6
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
F src/select.c e51efe5479d1cb4f48defe0b97cdba7391df42a755ba9592b9159510d03cf738
F src/server.c 70421e6acbb2279878606be160b45c7db78933d6ec320317a2e939218496deb9
F src/server.h f46be129ffe407cac9b7018e6d4851b04e685d59b6837c73a1fb69e6aab52e3a
F src/shell.c.in d6a07811aa9f3b10200c15ab8dd4b6b998849a3b0c8b125bfa980329a33c26a6
F src/sqlite.h.in e0be726ea6e4e6571724d39d242472ecd8bd1ba6f84ade88e1641bde98a6d02b
F src/sqlite.h.in 45150a75c20ad6f9d914cd6e59caf36453206b0f824d514f194b56236f2d63d7
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 83a3c4ce93d650bedfd1aa558cb85a516bd6d094445ee989740827d0d944368d
F src/sqliteInt.h a4837c57f9a3e2af100bc59f4be60d16b823f18131f8cef6a6685440f775eebd
F src/sqliteInt.h 59a8bd112bc0224d2319c8b3b195056d36db1f1e9e993756f934bea663b204ec
F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
F src/status.c 46e7aec11f79dad50965a5ca5fa9de009f7d6bde08be2156f1538a0a296d4d0e
F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
F src/tclsqlite.c 916a92de77ec5cbe27818ca194d8cf0c58aa7ad5b87527098f6aa5a6068800ce
F src/test1.c 1ab7cbbb6693e08364c1a9241e2aee17f8c4925e4cc52396be77ae6845a05828
F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
F src/test2.c 824e16d2ff3b57dc3680a5635d049cc889492f95910368ec1ffe2ad44ca45a7f
F src/test3.c b8434949dfb8aff8dfa082c8b592109e77844c2135ed3c492113839b6956255b
F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6
F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d
@@ -516,7 +519,7 @@ 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 097c6189803886a1fb26ec37d8bc62b90512cb53ab79a1fb6d35196c1ec42ded
F src/test_config.c 432d0e740c9927a33094d12e7e2723b71071c93d6e53eca5b05d90b4ec0897af
F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f
F src/test_demovfs.c a0c3bdd45ed044115c2c9f7779e56eafff18741e
F src/test_devsym.c 1960abbb234b97e9b920f07e99503fc04b443f62bbc3c6ff2c2cea2133e3b8a2
@@ -560,11 +563,11 @@ F src/update.c a90a32ffc0100265b0693dbbdbe490756447af181f5ea2c138cce515b08c8795
F src/utf.c 810fbfebe12359f10bc2a011520a6e10879ab2a163bcb26c74768eab82ea62a5
F src/util.c d9eb0a6c4aae1b00a7369eadd7ca0bbe946cb4c953b6751aa20d357c2f482157
F src/vacuum.c 762ee9bbf8733d87d8cd06f58d950e881982e416f8c767334a40ffd341b6bff5
F src/vdbe.c 066a4e1de2ed83e253adfd2e97a684cf562eaa41d31ee7f3d3e4c8aea4485a55
F src/vdbe.c 6a454c0f6c43275e2fcc0b2ea85374e034dd21b7d5f8f9fe8994ebf6cefe9b61
F src/vdbe.h 134beb7a12a6213c00eba58febaede33447cc4441bc568a0d9c144b33fc3720a
F src/vdbeInt.h 95f7adfdc5c8f1353321f55a6c5ec00a90877e3b85af5159e393afb41ff54110
F src/vdbeapi.c 29d2baf9c1233131ec467d7bed1b7c8a03c27579048d768c4b04acf427838858
F src/vdbeaux.c 2756ac68ac259c416554100598fc291870063288cd7e1af22847f57b3e130e56
F src/vdbeaux.c 4115729898d68209562d5ed084244d893cc537312d877d51475cccef2f2800b2
F src/vdbeblob.c f5c70f973ea3a9e915d1693278a5f890dc78594300cf4d54e64f2b0917c94191
F src/vdbemem.c 414e28d3a7e2a8bee2bb247de115dcbc68e3cbac284d5862d077002f7a93bce1
F src/vdbesort.c 731a09e5cb9e96b70c394c1b7cf3860fbe84acca7682e178615eb941a3a0ef2f
@@ -1141,7 +1144,7 @@ F test/parser1.test 391b9bf9a229547a129c61ac345ed1a6f5eb1854
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
F test/permutations.test 8ada8c1dee071e0fc275bc8bc2db7de537d625cad949d2200664b99a0a89eac5
F test/permutations.test 17d9cbfce2e7d0e2007a245cf88c3c48ee9531fd6008034442feeb0f11357132
F test/pragma.test 7c8cfc328a1717a95663cf8edb06c52ddfeaf97bb0aee69ae7457132e8d39e7d
F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f
F test/pragma3.test 14c12bc5352b1e100e0b6b44f371053a81ccf8ed
@@ -1219,6 +1222,13 @@ F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf
F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3
F test/selectG.test 089f7d3d7e6db91566f00b036cb353107a2cca6220eb1cb264085a836dae8840
F test/server1.test 46803bd3fe8b99b30dbc5ff38ffc756f5c13a118
F test/server2.test 787ba6044b5e9b2c3d60588bc5596054e6a5c3a7dc64bc2fb0e62f6616c142a9
F test/server3.test c3ae4ca7a6e7df870bfcd2450a9815507eaa80b9cdc44ee6c7975d48311505d4
F test/server_common.tcl c491d0f509b94a5cca845d45ca3bb47e464ad3a4bc89641982269112d0f1f3f4
F test/servercrash.test 1cbd2f98cadee2d8d42ed85ad76fbcf48958fedd537c82221838cd9bc6899dae
F test/serverfreelist.test 2e554001145170094a19731a8ce2981d040cf44c947542b35d130e6e31256fca
F test/serverlimit.test 4bc013c0b991956486ddbff6ea3bee78a0d14a3d8091f5ec00e2bd34a7fa9aa7
F test/serverreadonly.test 97040670597948a695b1973537d770417589f1998bcbb3959302aaee3c211250
F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be
F test/sessionfuzz-data1.db 1f8d5def831f19b1c74571037f0d53a588ea49a6c4ca2a028fc0c27ef896dbcb
F test/sessionfuzz.c b0fcdcf757451957e17396a3af5171f1fdf9b2babc81da9fa35675df46c4729a
@@ -1311,7 +1321,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
F test/temptable2.test cd396beb41117a5302fff61767c35fa4270a0d5e
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
F test/tester.tcl 94901a4625d9a2229666dd5c44120ddf7f0fb639470710ef74a4cefc7b039e07
F test/tester.tcl f6342dac83dbc6ca42bac34a5f65a70df4b18e03e98bcb230efd35e621b671c0
F test/thread001.test b61a29dd87cf669f5f6ac96124a7c97d71b0c80d9012746072055877055cf9ef
F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
@@ -1645,17 +1655,17 @@ 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 2e852ac0dfdc5af18886dc1ce7e9676d11714ae3df0a282dc7d90b3a0fe2033c
F tool/mkkeywordhash.c 969c50301da61d73c4c6d0661c1d1abc6c25ba35f1212832d0b12965f90505ab
F tool/mkmsvcmin.tcl 8baf26690b80d861d0ac341b29880eec6ade39e4f11fe690271ded9cb90563a3
F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c
F tool/mkopcodeh.tcl 4ee2a30ccbd900dc4d5cdb61bdab87cd2166cd2affcc78c9cc0b8d22a65b2eee
F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
F tool/mkpragmatab.tcl 2144bc8550a6471a029db262a132d2df4b9e0db61b90398bf64f5b7b3f8d92cd
F tool/mkpragmatab.tcl c249dee507fc499d43072b14eb8b0373204dca952e770b9b9924c211bca0169a
F tool/mkshellc.tcl 1f45770aea226ac093a9c72f718efbb88a2a2833409ec2e1c4cecae4202626f5
F tool/mksourceid.c d458f9004c837bee87a6382228ac20d3eae3c49ea3b0a5aace936f8b60748d3b
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
F tool/mksqlite3c-noext.tcl fef88397668ae83166735c41af99d79f56afaabb
F tool/mksqlite3c.tcl a03cee30de81a2e67b93e5c659f24113a003677c557daeb008205c8e6d4345d6
F tool/mksqlite3c.tcl caf7dec7adbdfd0d76893bafa3e09a30401cc649b4c7bfcc4234eadede944469
F tool/mksqlite3h.tcl 080873e3856eceb9d289a08a00c4b30f875ea3feadcbece796bd509b1532792c
F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b
F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5
@@ -1667,6 +1677,7 @@ F tool/replace.tcl 60f91e8dd06ab81f74d213ecbd9c9945f32ac048
F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a
F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5
F tool/run-speed-test.sh f95d19fd669b68c4c38b6b475242841d47c66076
F tool/se_perf_test.tcl e2a5081f9378f188a431c4a85c5f3f9e99719027df5a6faa405c981b267ad8fd
F tool/showdb.c e6bc9dba233bf1b57ca0a525a2bba762db4e223de84990739db3f09c46151b1e
F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818
F tool/showlocks.c 9920bcc64f58378ff1118caead34147201f48c68
@@ -1690,6 +1701,7 @@ 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 ac67b8fe175850b780c5bd1888c2bb2f0f5e24096f040db7fc1a2af7a4908f2b
F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003
F tool/vdbe-compress.tcl 5926c71f9c12d2ab73ef35c29376e756eb68361c
F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f
@@ -1717,7 +1729,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 79c4383b66fee9d43a75eef30ed0364573fc99e6d3be12267a99773ab8f57a9f
R bdf50b6931c1183458a9402ee429b45b
U drh
Z 476c818e9139005e841c1162fa3fe2b7
P 337a0b67e30f1030fdc59f712e5914f4801b0e9e4ae19a1e82c10b73eb3f4773
R 5045a1c8302fea1a74fa92a9ce0972dd
U dan
Z d3c495daa23a1fc63d551424dd8817b7
+1 -1
View File
@@ -1 +1 @@
d282f064698782cf7b584138549a6b27befa0b945ae96b52a3ef6f8a13448077
754ad35cd26da361e2ed736b0e400497714a0db9b7fd05fd24e7803b6f478263
+384 -28
View File
@@ -266,6 +266,15 @@ static int hasReadConflicts(Btree *pBtree, Pgno iRoot){
}
#endif /* #ifdef SQLITE_DEBUG */
#ifdef SQLITE_SERVER_EDITION
/*
** Return true if the b-tree uses free-list format 2. Or false otherwise.
*/
static int btreeFreelistFormat2(BtShared *pBt){
return (pBt->pPage1->aData[18] > 2);
}
#endif
/*
** Query to see if Btree handle p may obtain a lock of type eLock
** (READ_LOCK or WRITE_LOCK) on the table with root-page iTab. Return
@@ -2990,7 +2999,7 @@ static int lockBtree(BtShared *pBt){
assert( sqlite3_mutex_held(pBt->mutex) );
assert( pBt->pPage1==0 );
rc = sqlite3PagerSharedLock(pBt->pPager);
rc = sqlite3PagerSharedLock(pBt->pPager, pBt->db->readonlyTrans);
if( rc!=SQLITE_OK ) return rc;
rc = btreeGetPage(pBt, 1, &pPage1, 0);
if( rc!=SQLITE_OK ) return rc;
@@ -3007,6 +3016,15 @@ static int lockBtree(BtShared *pBt){
u32 pageSize;
u32 usableSize;
u8 *page1 = pPage1->aData;
u8 i18 = page1[18];
u8 i19 = page1[19];
#ifdef SQLITE_SERVER_EDITION
if( i18==i19 && i18>2 ){
i18 -= 2;
i19 -= 2;
}
#endif
rc = SQLITE_NOTADB;
/* EVIDENCE-OF: R-43737-39999 Every valid SQLite database file begins
** with the following 16 bytes (in hex): 53 51 4c 69 74 65 20 66 6f 72 6d
@@ -3016,17 +3034,17 @@ static int lockBtree(BtShared *pBt){
}
#ifdef SQLITE_OMIT_WAL
if( page1[18]>1 ){
if( i18>1 ){
pBt->btsFlags |= BTS_READ_ONLY;
}
if( page1[19]>1 ){
if( i19>1 ){
goto page1_init_failed;
}
#else
if( page1[18]>2 ){
if( i18>2 ){
pBt->btsFlags |= BTS_READ_ONLY;
}
if( page1[19]>2 ){
if( i19>2 ){
goto page1_init_failed;
}
@@ -3038,7 +3056,7 @@ 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( i19==2 && (pBt->btsFlags & BTS_NO_WAL)==0 ){
int isOpen = 0;
rc = sqlite3PagerOpenWal(pBt->pPager, &isOpen);
if( rc!=SQLITE_OK ){
@@ -5721,6 +5739,255 @@ int sqlite3BtreePrevious(BtCursor *pCur, int flags){
return SQLITE_OK;
}
#ifdef SQLITE_SERVER_EDITION
#define SERVER_DEFAULT_FREELISTS 16
#define SERVER_DEFAULT_FREELIST_SIZE 128
/*
** Allocate the free-node and the first SERVER_DEFAULT_FREELISTS
** trunk pages.
*/
static int allocateServerFreenode(BtShared *pBt){
int rc;
MemPage *pPage1 = pBt->pPage1;
rc = sqlite3PagerWrite(pPage1->pDbPage);
if( rc==SQLITE_OK ){
Pgno pgnoNode = (++pBt->nPage);
MemPage *pNode = 0;
int i;
put4byte(&pPage1->aData[32], pgnoNode);
rc = btreeGetUnusedPage(pBt, pgnoNode, &pNode, PAGER_GET_NOCONTENT);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pNode->pDbPage);
}
if( rc==SQLITE_OK ){
put4byte(&pNode->aData[0], 0);
put4byte(&pNode->aData[4], SERVER_DEFAULT_FREELISTS);
}
for(i=0; rc==SQLITE_OK && i<SERVER_DEFAULT_FREELISTS; i++){
MemPage *pTrunk = 0;
Pgno pgnoTrunk;
if( ++pBt->nPage==PENDING_BYTE_PAGE(pBt) ) pBt->nPage++;
pgnoTrunk = pBt->nPage;
rc = btreeGetUnusedPage(pBt, pgnoTrunk, &pTrunk, PAGER_GET_NOCONTENT);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pTrunk->pDbPage);
}
if( rc==SQLITE_OK ){
memset(pTrunk->aData, 0, 8);
put4byte(&pNode->aData[8+i*4], pgnoTrunk);
}
releasePage(pTrunk);
}
releasePage(pNode);
}
return rc;
}
/*
** Return a reference to the first trunk page in one of the database free-lists.
** Allocate the database free-lists if required.
*/
static int findServerTrunk(BtShared *pBt, int bAlloc, MemPage **ppTrunk){
MemPage *pPage1 = pBt->pPage1;
MemPage *pNode = 0; /* The node page */
MemPage *pTrunk = 0; /* The returned page */
Pgno iNode; /* Page number of node page */
int rc = SQLITE_OK;
/* If the node page and free-list trunks have not yet been allocated, allocate
** them now. */
pPage1 = pBt->pPage1;
iNode = get4byte(&pPage1->aData[32]);
if( iNode==0 ){
rc = allocateServerFreenode(pBt);
iNode = get4byte(&pPage1->aData[32]);
}
/* Grab the node page */
if( rc==SQLITE_OK ){
rc = btreeGetUnusedPage(pBt, iNode, &pNode, 0);
}
if( rc==SQLITE_OK ){
int nList; /* Number of free-lists in this db */
int i;
/* Try to lock a free-list trunk. If bAlloc is true, it has to be a
** free-list trunk with at least one entry in the free-list. */
nList = (int)get4byte(&pNode->aData[4]);
for(i=0; i<nList; i++){
Pgno iTrunk = get4byte(&pNode->aData[8+i*4]);
if( SQLITE_OK==sqlite3PagerPagelock(pBt->pPager, iTrunk, 1) ){
rc = btreeGetUnusedPage(pBt, iTrunk, &pTrunk, 0);
if( rc==SQLITE_OK && bAlloc ){
if( !get4byte(&pTrunk->aData[0]) && !get4byte(&pTrunk->aData[4]) ){
releasePage(pTrunk);
pTrunk = 0;
}
}
if( rc!=SQLITE_OK || pTrunk ) break;
}
}
/* No free pages in any free-list. Or perhaps we were locked out. In
** either case, try to allocate more from the end of the file now. */
if( i==nList ){
assert( rc==SQLITE_OK && pTrunk==0 );
rc = sqlite3PagerWrite(pPage1->pDbPage);
for(i=0; rc==SQLITE_OK && i<nList; i++){
/* Add some free pages to each free-list. No server-locks are required
** to do this as we have a write-lock on page 1 - guaranteeing
** exclusive access to the db file. */
MemPage *pT = 0;
Pgno iTrunk = get4byte(&pNode->aData[8+i*4]);
rc = btreeGetUnusedPage(pBt, iTrunk, &pT, 0);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pT->pDbPage);
}
if( rc==SQLITE_OK ){
int iPg = get4byte(&pT->aData[4]);
for(/*no-op*/; iPg<SERVER_DEFAULT_FREELIST_SIZE; iPg++){
if( ++pBt->nPage==PENDING_BYTE_PAGE(pBt) ) pBt->nPage++;
put4byte(&pT->aData[8+iPg*4], pBt->nPage);
}
put4byte(&pT->aData[4], iPg);
if( pTrunk==0 ){
pTrunk = pT;
pT = 0;
}
}
releasePage(pT);
}
if( rc==SQLITE_OK ){
MemPage *pLast = 0;
rc = btreeGetUnusedPage(pBt, pBt->nPage, &pLast, 0);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pLast->pDbPage);
releasePage(pLast);
put4byte(28 + (u8*)pPage1->aData, pBt->nPage);
}
}
}
}
releasePage(pNode);
if( rc==SQLITE_OK ){
assert( pTrunk );
rc = sqlite3PagerWrite(pTrunk->pDbPage);
}
if( rc!=SQLITE_OK ){
releasePage(pTrunk);
pTrunk = 0;
}
*ppTrunk = pTrunk;
return rc;
}
static int allocateServerPage(
BtShared *pBt, /* The btree */
MemPage **ppPage, /* Store pointer to the allocated page here */
Pgno *pPgno, /* Store the page number here */
Pgno nearby, /* Search for a page near this one */
u8 eMode /* BTALLOC_EXACT, BTALLOC_LT, or BTALLOC_ANY */
){
int rc; /* Return code */
MemPage *pTrunk = 0; /* The node page */
Pgno pgnoNew = 0;
#ifdef SQLITE_DEBUG
int nRef = sqlite3PagerRefcount(pBt->pPager);
#endif
assert( eMode==BTALLOC_ANY );
assert( sqlite3_mutex_held(pBt->mutex) );
*ppPage = 0;
rc = findServerTrunk(pBt, 1, &pTrunk);
if( rc==SQLITE_OK ){
int nFree; /* Number of free pages on this trunk page */
nFree = (int)get4byte(&pTrunk->aData[4]);
if( nFree==0 ){
pgnoNew = get4byte(&pTrunk->aData[0]);
assert( pgnoNew );
}else{
nFree--;
pgnoNew = get4byte(&pTrunk->aData[8+4*nFree]);
put4byte(&pTrunk->aData[4], (u32)nFree);
releasePage(pTrunk);
pTrunk = 0;
}
}
if( rc==SQLITE_OK ){
MemPage *pNew = 0;
int flags = pTrunk ? 0 : PAGER_GET_NOCONTENT;
rc = btreeGetUnusedPage(pBt, pgnoNew, &pNew, flags);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pNew->pDbPage);
if( rc!=SQLITE_OK ){
releasePage(pNew);
pNew = 0;
}
}
if( rc==SQLITE_OK && pTrunk ){
memcpy(pTrunk->aData, pNew->aData, pBt->usableSize);
}
*ppPage = pNew;
*pPgno = pgnoNew;
}
releasePage(pTrunk);
assert( (rc==SQLITE_OK)==(*ppPage!=0) );
assert( sqlite3PagerRefcount(pBt->pPager)==(nRef+(*ppPage!=0)) );
return rc;
}
static int freeServerPage2(BtShared *pBt, MemPage *pPage, Pgno iPage){
int rc; /* Return code */
MemPage *pTrunk = 0; /* The node page */
#ifdef SQLITE_DEBUG
int nRef = sqlite3PagerRefcount(pBt->pPager);
#endif
assert( sqlite3_mutex_held(pBt->mutex) );
rc = findServerTrunk(pBt, 0, &pTrunk);
if( rc==SQLITE_OK ){
int nFree; /* Number of free pages on this trunk page */
nFree = (int)get4byte(&pTrunk->aData[4]);
if( nFree>=((pBt->usableSize / 4) - 2) ){
if( pPage==0 ){
rc = btreeGetUnusedPage(pBt, iPage, &pPage, 0);
}else{
sqlite3PagerRef(pPage->pDbPage);
}
rc = sqlite3PagerWrite(pPage->pDbPage);
if( rc==SQLITE_OK ){
memcpy(pPage->aData, pTrunk->aData, pBt->usableSize);
put4byte(&pTrunk->aData[0], iPage);
put4byte(&pTrunk->aData[4], 0);
}
releasePage(pPage);
}else{
put4byte(&pTrunk->aData[8+nFree*4], iPage);
put4byte(&pTrunk->aData[4], (u32)nFree+1);
}
releasePage(pTrunk);
}
assert( nRef==sqlite3PagerRefcount(pBt->pPager) );
return rc;
}
#else
# define allocateServerPage(v, w, x, y, z) SQLITE_OK
# define freeServerPage2(x, y, z) SQLITE_OK
#endif /* SQLITE_SERVER_EDITION */
/*
** Allocate a new page from the database file.
**
@@ -5758,6 +6025,10 @@ static int allocateBtreePage(
MemPage *pPrevTrunk = 0;
Pgno mxPage; /* Total size of the database file */
if( btreeFreelistFormat2(pBt) ){
return allocateServerPage(pBt, ppPage, pPgno, nearby, eMode);
}
assert( sqlite3_mutex_held(pBt->mutex) );
assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) );
pPage1 = pBt->pPage1;
@@ -6085,12 +6356,6 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
pPage = btreePageLookup(pBt, iPage);
}
/* Increment the free page count on pPage1 */
rc = sqlite3PagerWrite(pPage1->pDbPage);
if( rc ) goto freepage_out;
nFree = get4byte(&pPage1->aData[36]);
put4byte(&pPage1->aData[36], nFree+1);
if( pBt->btsFlags & BTS_SECURE_DELETE ){
/* If the secure_delete option is enabled, then
** always fully overwrite deleted information with zeros.
@@ -6102,6 +6367,17 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
}
memset(pPage->aData, 0, pPage->pBt->pageSize);
}
if( btreeFreelistFormat2(pBt) ){
rc = freeServerPage2(pBt, pPage, iPage);
goto freepage_out;
}
/* Increment the free page count on pPage1 */
rc = sqlite3PagerWrite(pPage1->pDbPage);
if( rc ) goto freepage_out;
nFree = get4byte(&pPage1->aData[36]);
put4byte(&pPage1->aData[36], nFree+1);
/* If the database supports auto-vacuum, write an entry in the pointer-map
** to indicate that the page is free.
@@ -9580,6 +9856,49 @@ end_of_check:
#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
#ifndef SQLITE_OMIT_INTEGRITY_CHECK
#if !defined(SQLITE_OMIT_INTEGRITY_CHECK) && defined(SQLITE_SERVER_EDITION)
static void checkServerList(IntegrityCk *pCheck){
u32 pgnoNode = get4byte(&pCheck->pBt->pPage1->aData[32]);
if( pgnoNode ){
DbPage *pNode = 0;
u8 *aNodeData;
u32 nList; /* Number of free-lists */
int i;
checkRef(pCheck, pgnoNode);
if( sqlite3PagerGet(pCheck->pPager, (Pgno)pgnoNode, &pNode, 0) ){
checkAppendMsg(pCheck, "failed to get node page %d", pgnoNode);
return;
}
aNodeData = sqlite3PagerGetData(pNode);
nList = get4byte(&aNodeData[4]);
for(i=0; i<nList; i++){
u32 pgnoTrunk = get4byte(&aNodeData[8+4*i]);
while( pgnoTrunk ){
DbPage *pTrunk = 0;
checkRef(pCheck, pgnoTrunk);
if( sqlite3PagerGet(pCheck->pPager, (Pgno)pgnoTrunk, &pTrunk, 0) ){
checkAppendMsg(pCheck, "failed to get page %d", pgnoTrunk);
pgnoTrunk = 0;
}else{
u8 *aTrunkData = sqlite3PagerGetData(pTrunk);
int nLeaf = (int)get4byte(&aTrunkData[4]);
int iLeaf;
for(iLeaf=0; iLeaf<nLeaf; iLeaf++){
u32 pgnoLeaf = get4byte(&aTrunkData[8+iLeaf*4]);
checkRef(pCheck, pgnoLeaf);
}
pgnoTrunk = get4byte(&aTrunkData[0]);
sqlite3PagerUnref(pTrunk);
}
}
}
sqlite3PagerUnref(pNode);
}
}
#endif
/*
** This routine does a complete check of the given BTree file. aRoot[] is
** an array of pages numbers were each page number is the root page of
@@ -9645,8 +9964,15 @@ char *sqlite3BtreeIntegrityCheck(
/* Check the integrity of the freelist
*/
sCheck.zPfx = "Main freelist: ";
checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]),
get4byte(&pBt->pPage1->aData[36]));
#ifdef SQLITE_SERVER_EDITION
if( btreeFreelistFormat2(pBt) ){
checkServerList(&sCheck);
}else
#endif
{
checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]),
get4byte(&pBt->pPage1->aData[36]));
}
sCheck.zPfx = 0;
/* Check all the tables.
@@ -9915,6 +10241,37 @@ void sqlite3BtreeIncrblobCursor(BtCursor *pCur){
}
#endif
int btreeSetVersion(Btree *pBtree, int iVersion, int iFreelistFmt){
BtShared *pBt = pBtree->pBt;
int rc = sqlite3BtreeBeginTrans(pBtree, 0);
if( rc==SQLITE_OK ){
u8 iVal;
u8 *aData = pBt->pPage1->aData;
assert( (iVersion==0 && (iFreelistFmt==1 || iFreelistFmt==2))
|| (iFreelistFmt==0 && (iVersion==1 || iVersion==2))
);
if( iVersion==0 ){
iVal = ((aData[18] & 0x01) ? 1 : 2) + (u8)(iFreelistFmt==2 ? 2 : 0);
}else{
iVal = (u8)iVersion + (u8)(aData[18]>2 ? 2 : 0);
}
if( aData[18]!=iVal || aData[19]!=iVal ){
rc = sqlite3BtreeBeginTrans(pBtree, 2);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pBt->pPage1->pDbPage);
if( rc==SQLITE_OK ){
aData[18] = iVal;
aData[19] = iVal;
}
}
}
}
return rc;
}
/*
** Set both the "read version" (single byte at byte offset 18) and
** "write version" (single byte at byte offset 19) fields in the database
@@ -9931,23 +10288,22 @@ int sqlite3BtreeSetVersion(Btree *pBtree, int iVersion){
*/
pBt->btsFlags &= ~BTS_NO_WAL;
if( iVersion==1 ) pBt->btsFlags |= BTS_NO_WAL;
rc = btreeSetVersion(pBtree, iVersion, 0);
pBt->btsFlags &= ~BTS_NO_WAL;
return rc;
}
rc = sqlite3BtreeBeginTrans(pBtree, 0);
if( rc==SQLITE_OK ){
u8 *aData = pBt->pPage1->aData;
if( aData[18]!=(u8)iVersion || aData[19]!=(u8)iVersion ){
rc = sqlite3BtreeBeginTrans(pBtree, 2);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pBt->pPage1->pDbPage);
if( rc==SQLITE_OK ){
aData[18] = (u8)iVersion;
aData[19] = (u8)iVersion;
}
}
int sqlite3BtreeFreelistFormat(Btree *p, int eParam, int *peFmt){
int rc = SQLITE_OK;
sqlite3BtreeEnter(p);
if( eParam ){
u8 *aData = p->pBt->pPage1->aData;
if( 0==get4byte(&aData[32]) ){
rc = btreeSetVersion(p, 0, eParam);
}
}
pBt->btsFlags &= ~BTS_NO_WAL;
*peFmt = (btreeFreelistFormat2(p->pBt) ? 2 : 1);
sqlite3BtreeLeave(p);
return rc;
}
+3
View File
@@ -371,5 +371,8 @@ void sqlite3BtreeCursorList(Btree*);
# define sqlite3SchemaMutexHeld(X,Y,Z) 1
#endif
#ifdef SQLITE_SERVER_EDITION
int sqlite3BtreeFreelistFormat(Btree *p, int eParam, int *peFmt);
#endif
#endif /* SQLITE_BTREE_H */
+2 -2
View File
@@ -3978,13 +3978,13 @@ void sqlite3BeginTransaction(Parse *pParse, int type){
}
v = sqlite3GetVdbe(pParse);
if( !v ) return;
if( type!=TK_DEFERRED ){
if( type!=TK_DEFERRED && type!=TK_READONLY ){
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);
}
/*
+244
View File
@@ -3841,12 +3841,247 @@ static void unixModeBit(unixFile *pFile, unsigned char mask, int *pArg){
/* Forward declaration */
static int unixGetTempname(int nBuf, char *zBuf);
#ifdef SQLITE_SERVER_EDITION
/*
** Structure passed by SQLite through the (void*) argument to various
** fcntl operations.
*/
struct UnixServerArg {
void *h; /* Handle from SHMOPEN */
void *p; /* Mapping */
int i1; /* Integer value 1 */
int i2; /* Integer value 2 */
};
typedef struct UnixServerArg UnixServerArg;
/*
** Structure used as a server-shm handle.
*/
struct UnixServerShm {
void *pMap; /* Pointer to mapping */
int nMap; /* Size of mapping in bytes */
int fd; /* File descriptor open on *-hma file */
};
typedef struct UnixServerShm UnixServerShm;
/*
** Implementation of SQLITE_FCNTL_FILEID
*/
static int unixFcntlServerFileid(unixFile *pFile, void *pArg){
i64 *aId = (i64*)pArg;
aId[0] = (i64)(pFile->pInode->fileId.dev);
aId[1] = (i64)(pFile->pInode->fileId.ino);
return SQLITE_OK;
}
/*
** Implementation of SQLITE_FCNTL_SERVER_MODE
*/
static int unixFcntlServerMode(unixFile *pFile, void *pArg){
int rc = SQLITE_OK;
int eServer = 0;
char *zJrnl = sqlite3_mprintf("%s-journal", pFile->zPath);
if( zJrnl==0 ){
rc = SQLITE_NOMEM;
}else{
struct stat buf; /* Used to hold return values of stat() */
if( osStat(zJrnl, &buf) ){
rc = SQLITE_IOERR_FSTAT;
}else if( buf.st_mode & S_IFDIR ){
eServer = (pFile->ctrlFlags & UNIXFILE_EXCL) ? 1 : 2;
}
}
sqlite3_free(zJrnl);
*((int*)pArg) = eServer;
return rc;
}
/*
** Implementation of SQLITE_FCNTL_SERVER_SHMOPEN.
**
** The (void*) argument passed to this file control should actually be
** a pointer to a UnixServerArg or equivalent structure. Arguments are
** interpreted as follows:
**
** UnixServerArg.h - OUT: New server shm handle.
** UnixServerArg.p - OUT: New server shm mapping.
** UnixServerArg.i1 - Size of requested mapping in bytes.
** UnixServerArg.i2 - OUT: True if journal rollback + SHMOPEN2 are required.
*/
static int unixFcntlServerShmopen(unixFile *pFd, void *pArg){
int rc = SQLITE_OK;
UnixServerArg *pSArg = (UnixServerArg*)pArg;
UnixServerShm *p;
char *zHma;
p = sqlite3_malloc(sizeof(UnixServerShm));
if( p==0 ) return SQLITE_NOMEM;
memset(p, 0, sizeof(UnixServerShm));
p->fd = -1;
zHma = sqlite3_mprintf("%s-journal/hma", pFd->zPath);
if( zHma==0 ){
rc = SQLITE_NOMEM;
}else{
p->fd = osOpen(zHma, O_RDWR|O_CREAT, 0644);
p->nMap = pSArg->i1;
if( p->fd<0 ){
rc = SQLITE_CANTOPEN;
}else{
int res = ftruncate(p->fd, p->nMap);
if( res!=0 ){
rc = SQLITE_IOERR_TRUNCATE;
}else{
p->pMap = osMmap(0, p->nMap, PROT_READ|PROT_WRITE, MAP_SHARED, p->fd,0);
if( p->pMap==0 ){
rc = SQLITE_IOERR_MMAP;
}
}
}
sqlite3_free(zHma);
}
if( rc==SQLITE_OK ){
int res;
struct flock lock;
memset(&lock, 0, sizeof(struct flock));
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = p->nMap;
lock.l_len = 1;
res = osFcntl(p->fd, F_SETLK, &lock);
if( res==0 ){
pSArg->i2 = 1;
memset(p->pMap, 0, p->nMap);
}else{
pSArg->i2 = 0;
lock.l_type = F_RDLCK;
res = osFcntl(p->fd, F_SETLKW, &lock);
if( res!=0 ){
rc = SQLITE_IOERR_LOCK;
}
}
}
if( rc!=SQLITE_OK ){
if( p->pMap ) osMunmap(p->pMap, p->nMap);
if( p->fd>=0 ) close(p->fd);
sqlite3_free(p);
pSArg->h = pSArg->p = 0;
}else{
pSArg->h = (void*)p;
pSArg->p = (void*)(p->pMap);
}
return rc;
}
/*
** Implementation of SQLITE_FCNTL_SERVER_SHMOPEN2.
**
** The (void*) argument passed to this file control should actually be
** a pointer to a UnixServerArg or equivalent structure. Arguments are
** interpreted as follows:
**
** UnixServerArg.h - Server shm handle (from SHMOPEN).
** UnixServerArg.p - unused.
** UnixServerArg.i1 - unused.
** UnixServerArg.i2 - unused.
*/
static int unixFcntlServerShmopen2(unixFile *pFd, void *pArg){
UnixServerArg *pSArg = (UnixServerArg*)pArg;
UnixServerShm *p = (UnixServerShm*)pSArg->h;
int res;
struct flock lock;
memset(&lock, 0, sizeof(struct flock));
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = p->nMap;
lock.l_len = 1;
res = osFcntl(p->fd, F_SETLK, &lock);
return res ? SQLITE_IOERR_LOCK : SQLITE_OK;
}
/*
** Implementation of SQLITE_FCNTL_SERVER_SHMCLOSE.
**
** The (void*) argument passed to this file control should actually be
** a pointer to a UnixServerArg or equivalent structure. Arguments are
** interpreted as follows:
**
** UnixServerArg.h - Server shm handle (from SHMOPEN).
** UnixServerArg.p - unused.
** UnixServerArg.i1 - unused.
** UnixServerArg.i2 - unused.
*/
static int unixFcntlServerShmclose(unixFile *pFd, void *pArg){
UnixServerArg *pSArg = (UnixServerArg*)pArg;
UnixServerShm *p = (UnixServerShm*)pSArg->h;
if( p->pMap ) osMunmap(p->pMap, p->nMap);
if( p->fd>=0 ) close(p->fd);
sqlite3_free(p);
return SQLITE_OK;
}
/*
** Implementation of SQLITE_FCNTL_SERVER_SHMLOCK.
**
** The (void*) argument passed to this file control should actually be
** a pointer to a UnixServerArg or equivalent structure. Arguments are
** interpreted as follows:
**
** UnixServerArg.h - Server shm handle (from SHMOPEN).
** UnixServerArg.p - unused.
** UnixServerArg.i1 - slot to lock.
** UnixServerArg.i2 - true to take the lock, false to release it.
*/
static int unixFcntlServerShmlock(unixFile *pFd, void *pArg){
UnixServerArg *pSArg = (UnixServerArg*)pArg;
UnixServerShm *p = (UnixServerShm*)pSArg->h;
int res;
struct flock lock;
memset(&lock, 0, sizeof(struct flock));
lock.l_type = pSArg->i2 ? F_WRLCK : F_UNLCK;
lock.l_whence = SEEK_SET;
lock.l_start = p->nMap + pSArg->i1 + 1;
lock.l_len = 1;
res = osFcntl(p->fd, F_SETLK, &lock);
return (res==0 ? SQLITE_OK : SQLITE_BUSY);
}
#endif
/*
** Information and control of an open file handle.
*/
static int unixFileControl(sqlite3_file *id, int op, void *pArg){
unixFile *pFile = (unixFile*)id;
switch( op ){
#ifdef SQLITE_SERVER_EDITION
case SQLITE_FCNTL_FILEID:
return unixFcntlServerFileid(pFile, pArg);
case SQLITE_FCNTL_SERVER_MODE:
return unixFcntlServerMode(pFile, pArg);
case SQLITE_FCNTL_SERVER_SHMOPEN:
return unixFcntlServerShmopen(pFile, pArg);
case SQLITE_FCNTL_SERVER_SHMOPEN2:
return unixFcntlServerShmopen2(pFile, pArg);
case SQLITE_FCNTL_SERVER_SHMCLOSE:
return unixFcntlServerShmclose(pFile, pArg);
case SQLITE_FCNTL_SERVER_SHMLOCK:
return unixFcntlServerShmlock(pFile, pArg);
#endif
#if defined(__linux__) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE)
case SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: {
int rc = osIoctl(pFile->h, F2FS_IOC_START_ATOMIC_WRITE);
@@ -5780,6 +6015,15 @@ static int findCreateFileMode(
zDb[nDb] = '\0';
rc = getFileMode(zDb, pMode, pUid, pGid);
#ifdef SQLITE_SERVER_EDITION
if( rc==SQLITE_IOERR_FSTAT ){
while( nDb && zDb[nDb]!='/' ) nDb--;
if( nDb>8 && memcmp("-journal/", &zDb[nDb-8], 9)==0 ){
zDb[nDb-8] = '\0';
rc = getFileMode(zDb, pMode, pUid, pGid);
}
}
#endif
}else if( flags & SQLITE_OPEN_DELETEONCLOSE ){
*pMode = 0600;
}else if( flags & SQLITE_OPEN_URI ){
+228 -16
View File
@@ -717,6 +717,10 @@ struct Pager {
Wal *pWal; /* Write-ahead log used by "journal_mode=wal" */
char *zWal; /* File name for write-ahead log */
#endif
#ifdef SQLITE_SERVER_EDITION
Server *pServer;
ServerPage *pServerPage;
#endif
};
/*
@@ -848,6 +852,13 @@ int sqlite3PagerUseWal(Pager *pPager, Pgno pgno){
# define pagerBeginReadTransaction(z) SQLITE_OK
#endif
#ifdef SQLITE_SERVER_EDITION
# define pagerIsServer(x) ((x)->pServer!=0)
# define pagerIsProcessServer(x) sqlite3ServerIsSingleProcess((x)->pServer)
#else
# define pagerIsServer(x) 0
#endif
#ifndef NDEBUG
/*
** Usage:
@@ -1056,6 +1067,9 @@ static void setGetterMethod(Pager *pPager){
pPager->xGet = getPageError;
#if SQLITE_MAX_MMAP_SIZE>0
}else if( USEFETCH(pPager)
#ifdef SQLITE_SERVER_EDITION
&& sqlite3ServerIsReadonly(pPager->pServer)==0
#endif
#ifdef SQLITE_HAS_CODEC
&& pPager->xCodec==0
#endif
@@ -1146,6 +1160,7 @@ static int pagerUnlockDb(Pager *pPager, int eLock){
assert( !pPager->exclusiveMode || pPager->eLock==eLock );
assert( eLock==NO_LOCK || eLock==SHARED_LOCK );
assert( eLock!=NO_LOCK || pagerUseWal(pPager)==0 );
assert( eLock!=NO_LOCK || pagerIsServer(pPager)==0 );
if( isOpen(pPager->fd) ){
assert( pPager->eLock>=eLock );
rc = pPager->noLock ? SQLITE_OK : sqlite3OsUnlock(pPager->fd, eLock);
@@ -1807,6 +1822,21 @@ static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){
return rc;
}
#ifdef SQLITE_SERVER_EDITION
/*
** Free the linked list of ServerPage objects headed at Pager.pServerPage.
*/
static void pagerFreeServerPage(Pager *pPager){
ServerPage *pPg;
ServerPage *pNext;
for(pPg=pPager->pServerPage; pPg; pPg=pNext){
pNext = pPg->pNext;
sqlite3_free(pPg);
}
pPager->pServerPage = 0;
}
#endif
/*
** 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
@@ -1835,6 +1865,13 @@ static void pager_unlock(Pager *pPager){
pPager->pInJournal = 0;
releaseAllSavepoints(pPager);
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
pagerFreeServerPage(pPager);
sqlite3ServerEnd(pPager->pServer);
pPager->eState = PAGER_OPEN;
}else
#endif
if( pagerUseWal(pPager) ){
assert( !isOpen(pPager->jfd) );
sqlite3WalEndReadTransaction(pPager->pWal);
@@ -2135,6 +2172,11 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
}
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
rc2 = sqlite3ServerEndWrite(pPager->pServer);
}else
#endif
if( !pPager->exclusiveMode
&& (!pagerUseWal(pPager) || sqlite3WalExclusiveMode(pPager->pWal, 0))
){
@@ -3031,11 +3073,28 @@ static int readDbPage(PgHdr *pPg){
}else
#endif
{
i64 iOffset = (pPg->pgno-1)*(i64)pPager->pageSize;
rc = sqlite3OsRead(pPager->fd, pPg->pData, pPager->pageSize, iOffset);
if( rc==SQLITE_IOERR_SHORT_READ ){
rc = SQLITE_OK;
#ifdef SQLITE_SERVER_EDITION
u8 *pData = 0;
if( pagerIsServer(pPager) ){
sqlite3ServerReadPage(pPager->pServer, pPg->pgno, &pData);
if( pData ){
memcpy(pPg->pData, pData, pPager->pageSize);
}
}
if( pData==0 ){
#endif
i64 iOffset = (pPg->pgno-1)*(i64)pPager->pageSize;
rc = sqlite3OsRead(pPager->fd, pPg->pData, pPager->pageSize, iOffset);
if( rc==SQLITE_IOERR_SHORT_READ ){
rc = SQLITE_OK;
}
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
sqlite3ServerEndReadPage(pPager->pServer, pPg->pgno);
}
}
#endif
}
if( pPg->pgno==1 ){
@@ -4180,15 +4239,31 @@ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){
** rollback before accessing the database file.
*/
if( isOpen(pPager->jfd) ){
#if 0
if( pagerIsServer(pPager) ){
assert( pPager->journalMode==PAGER_JOURNALMODE_PERSIST );
pPager->journalMode = PAGER_JOURNALMODE_DELETE;
/* If necessary, change the pager state so that the journal file
** is deleted by the call to pagerUnlockAndRollback() below. */
if( pPager->eState==PAGER_OPEN ) pPager->eState = PAGER_READER;
}
#endif
pager_error(pPager, pagerSyncHotJournal(pPager));
}
pagerUnlockAndRollback(pPager);
}
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
sqlite3ServerDisconnect(pPager->pServer, pPager->fd);
pPager->pServer = 0;
}else{
sqlite3OsClose(pPager->jfd);
}
#endif
sqlite3EndBenignMalloc();
enable_simulated_io_errors();
PAGERTRACE(("CLOSE %d\n", PAGERID(pPager)));
IOTRACE(("CLOSE %p\n", pPager))
sqlite3OsClose(pPager->jfd);
IOTRACE(("CLOSE %p\n", pPager));
sqlite3OsClose(pPager->fd);
sqlite3PageFree(pTmp);
sqlite3PcacheClose(pPager->pPCache);
@@ -4198,7 +4273,7 @@ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){
#endif
assert( !pPager->aSavepoint && !pPager->pInJournal );
assert( !isOpen(pPager->jfd) && !isOpen(pPager->sjfd) );
assert( !isOpen(pPager->sjfd) );
sqlite3_free(pPager);
return SQLITE_OK;
@@ -4407,6 +4482,14 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
assert( pPager->eLock==EXCLUSIVE_LOCK );
assert( isOpen(pPager->fd) || pList->pDirty==0 );
#ifdef SQLITE_SERVER_EDITION
if( pagerIsProcessServer(pPager) ){
rc = sqlite3ServerPreCommit(pPager->pServer, pPager->pServerPage);
pPager->pServerPage = 0;
if( rc!=SQLITE_OK ) return rc;
}
#endif
/* If the file is a temp-file has not yet been opened, open it now. It
** is not possible for rc to be other than SQLITE_OK if this branch
** is taken, as pager_wait_on_lock() is a no-op for temp-files.
@@ -4589,6 +4672,8 @@ static int pagerStress(void *p, PgHdr *pPg){
Pager *pPager = (Pager *)p;
int rc = SQLITE_OK;
if( pagerIsServer(pPager) ) return SQLITE_OK;
assert( pPg->pPager==pPager );
assert( pPg->flags&PGHDR_DIRTY );
@@ -5038,7 +5123,7 @@ act_like_temp_file:
** to determine whether or not a hot-journal file exists, the IO error
** code is returned and the value of *pExists is undefined.
*/
static int hasHotJournal(Pager *pPager, int *pExists){
static int hasHotJournal(Pager *pPager, int *pExists, int *peServer){
sqlite3_vfs * const pVfs = pPager->pVfs;
int rc = SQLITE_OK; /* Return code */
int exists = 1; /* True if a journal file is present */
@@ -5059,6 +5144,13 @@ static int hasHotJournal(Pager *pPager, int *pExists){
if( rc==SQLITE_OK && exists ){
int locked = 0; /* True if some process holds a RESERVED lock */
#ifdef SQLITE_SERVER_EDITION
rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SERVER_MODE, peServer);
if( rc!=SQLITE_NOTFOUND ){
if( rc!=SQLITE_OK || *peServer ) return rc;
}
#endif
/* Race condition here: Another process might have been holding the
** the RESERVED lock and have a journal open at the sqlite3OsAccess()
** call above, but then delete the journal and drop the lock before
@@ -5131,6 +5223,53 @@ static int hasHotJournal(Pager *pPager, int *pExists){
return rc;
}
#ifdef SQLITE_SERVER_EDITION
static int pagerServerConnect(Pager *pPager, int eServer){
int rc = SQLITE_OK;
if( pPager->tempFile==0 ){
pPager->noLock = 1;
pPager->journalMode = PAGER_JOURNALMODE_PERSIST;
rc = sqlite3ServerConnect(pPager, eServer, &pPager->pServer);
}
return rc;
}
int sqlite3PagerRollbackJournal(Pager *pPager, sqlite3_file *pJfd){
int rc; /* Return Code */
sqlite3_file *saved_jfd = pPager->jfd;
u8 saved_eState = pPager->eState;
u8 saved_eLock = pPager->eLock;
i64 saved_journalOff = pPager->journalOff;
i64 saved_journalHdr = pPager->journalHdr;
assert( pPager->journalMode==PAGER_JOURNALMODE_PERSIST );
pPager->eLock = EXCLUSIVE_LOCK;
pPager->eState = PAGER_WRITER_DBMOD;
pPager->jfd = pJfd;
rc = pagerSyncHotJournal(pPager);
if( rc==SQLITE_OK ) rc = pager_playback(pPager, 1);
assert( isOpen(pPager->jfd) );
pPager->jfd = saved_jfd;
pPager->eState = saved_eState;
pPager->eLock = saved_eLock;
pPager->journalOff = saved_journalOff;
pPager->journalHdr = saved_journalHdr;
return rc;
}
void sqlite3PagerServerJournal(
Pager *pPager,
sqlite3_file *jfd,
const char *zJournal
){
pPager->zJournal = (char*)zJournal;
pPager->jfd = jfd;
}
#endif
/*
** This function is called to obtain a shared lock on the database file.
** It is illegal to call sqlite3PagerGet() until after this function
@@ -5158,8 +5297,11 @@ static int hasHotJournal(Pager *pPager, int *pExists){
** occurs while locking the database, checking for a hot-journal file or
** rolling back a journal file, the IO error code is returned.
*/
int sqlite3PagerSharedLock(Pager *pPager){
int sqlite3PagerSharedLock(Pager *pPager, int bReadonly){
int rc = SQLITE_OK; /* Return code */
#ifdef SQLITE_SERVER_EDITION
int eServer = 0;
#endif
/* This routine is only called from b-tree and only when there are no
** outstanding pages. This implies that the pager state should either
@@ -5170,7 +5312,9 @@ int sqlite3PagerSharedLock(Pager *pPager){
assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER );
assert( pPager->errCode==SQLITE_OK );
if( !pagerUseWal(pPager) && pPager->eState==PAGER_OPEN ){
if( !pagerUseWal(pPager)
&& !pagerIsServer(pPager)
&& pPager->eState==PAGER_OPEN ){
int bHotJournal = 1; /* True if there exists a hot journal-file */
assert( !MEMDB );
@@ -5186,7 +5330,8 @@ int sqlite3PagerSharedLock(Pager *pPager){
** database file, then it either needs to be played back or deleted.
*/
if( pPager->eLock<=SHARED_LOCK ){
rc = hasHotJournal(pPager, &bHotJournal);
rc = hasHotJournal(pPager, &bHotJournal, &eServer);
assert( bHotJournal==0 || eServer==0 );
}
if( rc!=SQLITE_OK ){
goto failed;
@@ -5319,6 +5464,7 @@ int sqlite3PagerSharedLock(Pager *pPager){
if( rc!=SQLITE_IOERR_SHORT_READ ){
goto failed;
}
rc = SQLITE_OK;
memset(dbFileVers, 0, sizeof(dbFileVers));
}
@@ -5337,16 +5483,37 @@ int sqlite3PagerSharedLock(Pager *pPager){
}
}
#ifdef SQLITE_SERVER_EDITION
if( eServer ){
rc = pagerServerConnect(pPager, eServer);
}
#endif
/* If there is a WAL file in the file-system, open this database in WAL
** mode. Otherwise, the following function call is a no-op.
*/
rc = pagerOpenWalIfPresent(pPager);
if( rc==SQLITE_OK ){
rc = pagerOpenWalIfPresent(pPager);
}
#ifndef SQLITE_OMIT_WAL
assert( pPager->pWal==0 || rc==SQLITE_OK );
#endif
}
if( pagerUseWal(pPager) ){
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
assert( rc==SQLITE_OK );
assert( sqlite3PagerRefcount(pPager)==0 );
assert( pagerUseWal(pPager)==0 );
pager_reset(pPager);
rc = sqlite3ServerBegin(pPager->pServer, bReadonly);
if( rc==SQLITE_OK ){
rc = sqlite3ServerLock(pPager->pServer, 1, 0, 0);
}
setGetterMethod(pPager);
}
#endif
if( rc==SQLITE_OK && pagerUseWal(pPager) ){
assert( rc==SQLITE_OK );
rc = pagerBeginReadTransaction(pPager);
}
@@ -5635,6 +5802,12 @@ int sqlite3PagerGet(
DbPage **ppPage, /* Write a pointer to the page here */
int flags /* PAGER_GET_XXX flags */
){
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
int rc = sqlite3ServerLock(pPager->pServer, pgno, 0, 0);
if( rc!=SQLITE_OK ) return rc;
}
#endif
return pPager->xGet(pPager, pgno, ppPage, flags);
}
@@ -5885,6 +6058,24 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){
char *pData2;
i64 iOff = pPager->journalOff;
#ifdef SQLITE_SERVER_EDITION
if( pagerIsProcessServer(pPager) ){
ServerPage *p = sqlite3ServerBuffer(pPager->pServer);
if( p==0 ){
int nByte = sizeof(ServerPage) + pPager->pageSize;
p = (ServerPage*)sqlite3_malloc(nByte);
if( !p ) return SQLITE_NOMEM_BKPT;
}
memset(p, 0, sizeof(ServerPage));
p->aData = (u8*)&p[1];
p->nData = pPager->pageSize;
p->pgno = pPg->pgno;
p->pNext = pPager->pServerPage;
pPager->pServerPage = p;
memcpy(p->aData, pPg->pData, pPager->pageSize);
}
#endif
/* We should never write to the journal file the page that
** contains the database locks. The following assert verifies
** that we do not. */
@@ -5952,6 +6143,13 @@ static int pager_write(PgHdr *pPg){
assert( pPager->readOnly==0 );
CHECK_PAGE(pPg);
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
rc = sqlite3ServerLock(pPager->pServer, pPg->pgno, 1, 0);
if( rc!=SQLITE_OK ) return rc;
}
#endif
/* The journal file needs to be opened. Higher level routines have already
** obtained the necessary locks to begin the write-transaction, but the
** rollback journal might not yet be open. Open it now if this is the case.
@@ -6230,7 +6428,10 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
# define DIRECT_MODE isDirectMode
#endif
if( !pPager->changeCountDone && ALWAYS(pPager->dbSize>0) ){
if( 0==pagerIsServer(pPager)
&& !pPager->changeCountDone
&& ALWAYS(pPager->dbSize>0)
){
PgHdr *pPgHdr; /* Reference to page 1 */
assert( !pPager->tempFile && isOpen(pPager->fd) );
@@ -6389,6 +6590,10 @@ int sqlite3PagerCommitPhaseOne(
** backup in progress needs to be restarted. */
sqlite3BackupRestart(pPager->pBackup);
}else{
/* If this connection is in server mode, ignore any master journal. */
if( pagerIsServer(pPager) ){
zMaster = 0;
}
if( pagerUseWal(pPager) ){
PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);
PgHdr *pPageOne = 0;
@@ -7341,7 +7546,7 @@ int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){
int state = pPager->eState;
assert( state==PAGER_OPEN || state==PAGER_READER );
if( state==PAGER_OPEN ){
rc = sqlite3PagerSharedLock(pPager);
rc = sqlite3PagerSharedLock(pPager, 0);
}
if( pPager->eState==PAGER_READER ){
assert( rc==SQLITE_OK );
@@ -7458,7 +7663,7 @@ int sqlite3PagerWalCallback(Pager *pPager){
*/
int sqlite3PagerWalSupported(Pager *pPager){
const sqlite3_io_methods *pMethods = pPager->fd->pMethods;
if( pPager->noLock ) return 0;
if( pPager->noLock && !pagerIsServer(pPager) ) return 0;
return pPager->exclusiveMode || (pMethods->iVersion>=2 && pMethods->xShmMap);
}
@@ -7666,4 +7871,11 @@ int sqlite3PagerWalFramesize(Pager *pPager){
}
#endif
#ifdef SQLITE_SERVER_EDITION
int sqlite3PagerPagelock(Pager *pPager, Pgno pgno, int bWrite){
if( pagerIsServer(pPager)==0 ) return SQLITE_OK;
return sqlite3ServerLock(pPager->pServer, pgno, bWrite, 0);
}
#endif
#endif /* SQLITE_OMIT_DISKIO */
+7 -1
View File
@@ -171,7 +171,7 @@ int sqlite3PagerCommitPhaseTwo(Pager*);
int sqlite3PagerRollback(Pager*);
int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
int sqlite3PagerSharedLock(Pager *pPager);
int sqlite3PagerSharedLock(Pager *pPager, int bReadonly);
#ifndef SQLITE_OMIT_WAL
int sqlite3PagerCheckpoint(Pager *pPager, sqlite3*, int, int*, int*);
@@ -242,4 +242,10 @@ void *sqlite3PagerCodec(DbPage *);
# define enable_simulated_io_errors()
#endif
#ifdef SQLITE_SERVER_EDITION
int sqlite3PagerRollbackJournal(Pager*, sqlite3_file*);
int sqlite3PagerPagelock(Pager *pPager, Pgno, int);
void sqlite3PagerServerJournal(Pager*, sqlite3_file*, const char*);
#endif
#endif /* SQLITE_PAGER_H */
+1
View File
@@ -131,6 +131,7 @@ trans_opt ::= TRANSACTION.
trans_opt ::= TRANSACTION nm.
%type transtype {int}
transtype(A) ::= . {A = TK_DEFERRED;}
transtype(A) ::= READONLY(X). {A = @X; /*A-overwrites-X*/}
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*/}
+27
View File
@@ -660,6 +660,33 @@ void sqlite3Pragma(
break;
}
#ifdef SQLITE_SERVER_EDITION
/*
** PRAGMA [schema.]freelist_format
** PRAGMA [schema.]freelist_format = (1|2)
*/
case PragTyp_FREELIST_FORMAT: {
sqlite3VdbeUsesBtree(v, iDb);
static const VdbeOpList freelist[] = {
{ OP_Transaction, 0, 0, 0}, /* 0 */
{ OP_FreelistFmt, 0, 1, 0}, /* 1 */
{ OP_ResultRow, 1, 1, 0} /* 2 */
};
VdbeOp *aOp;
sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(freelist));
aOp = sqlite3VdbeAddOpList(v, ArraySize(freelist), freelist,0);
aOp[0].p1 = iDb;
aOp[1].p1 = iDb;
if( zRight && (zRight[0]=='1' || zRight[0]=='2') && zRight[1]=='\0' ){
aOp[0].p2 = 1; /* Open a write transaction */
aOp[1].p3 = (int)(zRight[0] - '0');
}
break;
}
#endif
/*
** PRAGMA [schema.]journal_size_limit
** PRAGMA [schema.]journal_size_limit=N
+41 -33
View File
@@ -20,38 +20,39 @@
#define PragTyp_ENCODING 12
#define PragTyp_FOREIGN_KEY_CHECK 13
#define PragTyp_FOREIGN_KEY_LIST 14
#define PragTyp_FUNCTION_LIST 15
#define PragTyp_INCREMENTAL_VACUUM 16
#define PragTyp_INDEX_INFO 17
#define PragTyp_INDEX_LIST 18
#define PragTyp_INTEGRITY_CHECK 19
#define PragTyp_JOURNAL_MODE 20
#define PragTyp_JOURNAL_SIZE_LIMIT 21
#define PragTyp_LOCK_PROXY_FILE 22
#define PragTyp_LOCKING_MODE 23
#define PragTyp_PAGE_COUNT 24
#define PragTyp_MMAP_SIZE 25
#define PragTyp_MODULE_LIST 26
#define PragTyp_OPTIMIZE 27
#define PragTyp_PAGE_SIZE 28
#define PragTyp_PRAGMA_LIST 29
#define PragTyp_SECURE_DELETE 30
#define PragTyp_SHRINK_MEMORY 31
#define PragTyp_SOFT_HEAP_LIMIT 32
#define PragTyp_SYNCHRONOUS 33
#define PragTyp_TABLE_INFO 34
#define PragTyp_TEMP_STORE 35
#define PragTyp_TEMP_STORE_DIRECTORY 36
#define PragTyp_THREADS 37
#define PragTyp_WAL_AUTOCHECKPOINT 38
#define PragTyp_WAL_CHECKPOINT 39
#define PragTyp_ACTIVATE_EXTENSIONS 40
#define PragTyp_HEXKEY 41
#define PragTyp_KEY 42
#define PragTyp_REKEY 43
#define PragTyp_LOCK_STATUS 44
#define PragTyp_PARSER_TRACE 45
#define PragTyp_STATS 46
#define PragTyp_FREELIST_FORMAT 15
#define PragTyp_FUNCTION_LIST 16
#define PragTyp_INCREMENTAL_VACUUM 17
#define PragTyp_INDEX_INFO 18
#define PragTyp_INDEX_LIST 19
#define PragTyp_INTEGRITY_CHECK 20
#define PragTyp_JOURNAL_MODE 21
#define PragTyp_JOURNAL_SIZE_LIMIT 22
#define PragTyp_LOCK_PROXY_FILE 23
#define PragTyp_LOCKING_MODE 24
#define PragTyp_PAGE_COUNT 25
#define PragTyp_MMAP_SIZE 26
#define PragTyp_MODULE_LIST 27
#define PragTyp_OPTIMIZE 28
#define PragTyp_PAGE_SIZE 29
#define PragTyp_PRAGMA_LIST 30
#define PragTyp_SECURE_DELETE 31
#define PragTyp_SHRINK_MEMORY 32
#define PragTyp_SOFT_HEAP_LIMIT 33
#define PragTyp_SYNCHRONOUS 34
#define PragTyp_TABLE_INFO 35
#define PragTyp_TEMP_STORE 36
#define PragTyp_TEMP_STORE_DIRECTORY 37
#define PragTyp_THREADS 38
#define PragTyp_WAL_AUTOCHECKPOINT 39
#define PragTyp_WAL_CHECKPOINT 40
#define PragTyp_ACTIVATE_EXTENSIONS 41
#define PragTyp_HEXKEY 42
#define PragTyp_KEY 43
#define PragTyp_REKEY 44
#define PragTyp_LOCK_STATUS 45
#define PragTyp_PARSER_TRACE 46
#define PragTyp_STATS 47
/* Property flags associated with various pragma. */
#define PragFlg_NeedSchema 0x01 /* Force schema load before running */
@@ -300,6 +301,13 @@ static const PragmaName aPragmaName[] = {
/* ColNames: */ 0, 0,
/* iArg: */ BTREE_FREE_PAGE_COUNT },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && defined(SQLITE_SERVER_EDITION)
{/* zName: */ "freelist_format",
/* ePragTyp: */ PragTyp_FREELIST_FORMAT,
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq,
/* ColNames: */ 0, 0,
/* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
{/* zName: */ "full_column_names",
/* ePragTyp: */ PragTyp_FLAG,
@@ -646,4 +654,4 @@ static const PragmaName aPragmaName[] = {
/* iArg: */ SQLITE_WriteSchema },
#endif
};
/* Number of pragmas: 60 on by default, 77 total. */
/* Number of pragmas: 60 on by default, 78 total. */
+1026
View File
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
/*
** 2017 April 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.
**
*************************************************************************
*/
#ifdef SQLITE_SERVER_EDITION
#ifndef SQLITE_SERVER_H
#define SQLITE_SERVER_H
typedef struct Server Server;
typedef struct ServerPage ServerPage;
struct ServerPage {
Pgno pgno; /* Page number for this record */
int nData; /* Size of aData[] in bytes */
u8 *aData;
ServerPage *pNext;
int iCommitId;
ServerPage *pHashNext;
ServerPage *pHashPrev;
};
int sqlite3ServerConnect(Pager *pPager, int eServer, Server **ppOut);
void sqlite3ServerDisconnect(Server *p, sqlite3_file *dbfd);
int sqlite3ServerBegin(Server *p, int bReadonly);
int sqlite3ServerPreCommit(Server*, ServerPage*);
int sqlite3ServerEnd(Server *p);
int sqlite3ServerEndWrite(Server *p);
int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock);
ServerPage *sqlite3ServerBuffer(Server*);
int sqlite3ServerIsSingleProcess(Server*);
/* For "BEGIN READONLY" clients. */
int sqlite3ServerIsReadonly(Server*);
void sqlite3ServerReadPage(Server*, Pgno, u8**);
void sqlite3ServerEndReadPage(Server*, Pgno);
#endif /* SQLITE_SERVER_H */
#endif /* SQLITE_SERVER_EDITION */
+7
View File
@@ -506,6 +506,7 @@ int sqlite3_exec(
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8))
#define SQLITE_BUSY_DEADLOCK (SQLITE_BUSY | (3<<8))
#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8))
#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8))
#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8))
@@ -1105,6 +1106,12 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32
#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33
#define SQLITE_FCNTL_LOCK_TIMEOUT 34
#define SQLITE_FCNTL_FILEID 35
#define SQLITE_FCNTL_SERVER_MODE 36
#define SQLITE_FCNTL_SERVER_SHMOPEN 37
#define SQLITE_FCNTL_SERVER_SHMOPEN2 38
#define SQLITE_FCNTL_SERVER_SHMLOCK 39
#define SQLITE_FCNTL_SERVER_SHMCLOSE 40
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
+2
View File
@@ -1120,6 +1120,7 @@ typedef int VList;
#include "pcache.h"
#include "os.h"
#include "mutex.h"
#include "server.h"
/* The SQLITE_EXTRA_DURABLE compile-time option used to set the default
** synchronous setting to EXTRA. It is no longer supported.
@@ -1345,6 +1346,7 @@ struct sqlite3 {
u16 dbOptFlags; /* Flags to enable/disable optimizations */
u8 enc; /* Text encoding */
u8 autoCommit; /* The auto-commit flag. */
u8 readonlyTrans; /* Transaction opened with BEGIN READONLY */
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 */
+1 -1
View File
@@ -324,7 +324,7 @@ static int SQLITE_TCLAPI page_get(
}
pPager = sqlite3TestTextToPtr(argv[1]);
if( Tcl_GetInt(interp, argv[2], &pgno) ) return TCL_ERROR;
rc = sqlite3PagerSharedLock(pPager);
rc = sqlite3PagerSharedLock(pPager, 0);
if( rc==SQLITE_OK ){
rc = sqlite3PagerGet(pPager, pgno, &pPage, 0);
}
+6
View File
@@ -762,6 +762,12 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY);
Tcl_SetVar2(interp, "sqlite_options", "uri_00_error", "0", TCL_GLOBAL_ONLY);
#endif
#ifdef SQLITE_SERVER_EDITION
Tcl_SetVar2(interp, "sqlite_options", "server", "1", TCL_GLOBAL_ONLY);
#else
Tcl_SetVar2(interp, "sqlite_options", "server", "0", TCL_GLOBAL_ONLY);
#endif
#define LINKVAR(x) { \
static const int cv_ ## x = SQLITE_ ## x; \
Tcl_LinkVar(interp, "SQLITE_" #x, (char *)&(cv_ ## x), \
+30 -1
View File
@@ -3096,7 +3096,7 @@ 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
@@ -3113,6 +3113,7 @@ case OP_AutoCommit: {
iRollback = pOp->p2;
assert( desiredAutoCommit==1 || desiredAutoCommit==0 );
assert( desiredAutoCommit==1 || iRollback==0 );
assert( desiredAutoCommit==0 || pOp->p3==0 );
assert( db->nVdbeActive>0 ); /* At least this one VM is active */
assert( p->bIsReader );
@@ -3144,6 +3145,7 @@ case OP_AutoCommit: {
sqlite3CloseSavepoints(db);
if( p->rc==SQLITE_OK ){
rc = SQLITE_DONE;
db->readonlyTrans = (pOp->p3==TK_READONLY);
}else{
rc = SQLITE_ERROR;
}
@@ -3351,6 +3353,33 @@ case OP_SetCookie: {
break;
}
#ifdef SQLITE_SERVER_EDITION
/* Opcode: FreelistFmt P1 P2 P3 * *
**
** Parameter P3 must be 0, 1 or 2. If it is not 0, attempt to set the
** freelist format of database P1 to format 1 or format 2. Before
** returning, store the final freelist format (either 1 or 2) of
** database P1 into register P2.
*/
case OP_FreelistFmt: { /* out2 */
Db *pDb;
int iVal;
assert( pOp->p1>=0 && pOp->p1<db->nDb );
assert( DbMaskTest(p->btreeMask, pOp->p1) );
assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) );
pDb = &db->aDb[pOp->p1];
assert( pDb->pBt!=0 );
pOut = out2Prerelease(p, pOp);
rc = sqlite3BtreeFreelistFormat(pDb->pBt, pOp->p3, &iVal);
if( rc ) goto abort_due_to_error;
pOut->u.i = iVal;
break;
}
#endif
/* Opcode: OpenRead P1 P2 P3 P4 P5
** Synopsis: root=P2 iDb=P3
**
+7 -6
View File
@@ -2662,12 +2662,13 @@ int sqlite3VdbeHalt(Vdbe *p){
/* Check for one of the special errors */
mrc = p->rc & 0xff;
isSpecialError = mrc==SQLITE_NOMEM || mrc==SQLITE_IOERR
|| mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL;
|| mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL
|| p->rc==SQLITE_BUSY_DEADLOCK;
if( isSpecialError ){
/* If the query was read-only and the error code is SQLITE_INTERRUPT,
** no rollback is necessary. Otherwise, at least a savepoint
** transaction must be rolled back to restore the database to a
** consistent state.
/* If the query was read-only and the error code is SQLITE_INTERRUPT
** or SQLITE_BUSY_SERVER, no rollback is necessary. Otherwise, at
** least a savepoint transaction must be rolled back to restore the
** database to a consistent state.
**
** Even if the statement is read-only, it is important to perform
** a statement or transaction rollback operation. If the error
@@ -2676,7 +2677,7 @@ int sqlite3VdbeHalt(Vdbe *p){
** pagerStress() in pager.c), the rollback is required to restore
** the pager to a consistent state.
*/
if( !p->readOnly || mrc!=SQLITE_INTERRUPT ){
if( !p->readOnly || (mrc!=SQLITE_INTERRUPT && mrc!=SQLITE_BUSY) ){
if( (mrc==SQLITE_NOMEM || mrc==SQLITE_FULL) && p->usesStmtJournal ){
eStatementOp = SAVEPOINT_ROLLBACK;
}else{
+8
View File
@@ -275,6 +275,14 @@ test_suite "fts5" -prefix "" -description {
All FTS5 tests.
} -files [glob -nocomplain $::testdir/../ext/fts5/test/*.test]
test_suite "server" -prefix "" -description {
All server-edition tests.
} -files [
test_set \
select1.test server2.test server3.test serverfreelist.test \
serverreadonly.test servercrash.test serverlimit.test
]
test_suite "fts5-light" -prefix "" -description {
All FTS5 tests.
} -files [
+175
View File
@@ -0,0 +1,175 @@
# 2017 April 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 implements regression tests for SQLite library. The
# focus of this script is testing the server mode of SQLite.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix server2
source $testdir/server_common.tcl
return_if_no_server
db close
foreach {tn vfs} {1 unix-excl 2 unix} {
server_set_vfs $vfs
foreach f [glob -nocomplain test.db*] {
forcedelete $f
}
#-------------------------------------------------------------------------
# Check that the *-journal* files are deleted correctly.
#
server_reset_db
do_execsql_test 1.0 {
CREATE TABLE t1(a, b);
} {}
do_test $tn.1.1 {
lsort [glob -nocomplain test.db-journal/*-journal]
} {test.db-journal/0-journal}
do_test $tn.1.2 {
db close
lsort [glob -nocomplain test.db-journal/*-journal]
} {}
server_sqlite3 db test.db
do_execsql_test $tn.1.3 {
CREATE TABLE t2(a, b);
} {}
server_sqlite3 db2 test.db
do_test $tn.1.4 {
db eval {
BEGIN;
INSERT INTO t1 VALUES(1, 2);
}
db2 eval {
BEGIN;
INSERT INTO t2 VALUES(3, 4);
}
} {}
do_test $tn.1.5 {
db2 eval COMMIT
db eval COMMIT
lsort [glob -nocomplain test.db-journal/*-journal]
} {test.db-journal/0-journal test.db-journal/1-journal}
do_test $tn.1.6 {
db close
lsort [glob -nocomplain test.db-journal/*-journal]
} {test.db-journal/0-journal test.db-journal/1-journal}
do_test $tn.1.7 {
db2 close
lsort [glob -nocomplain test.db-journal/*-journal]
} {}
#-------------------------------------------------------------------------
#
server_reset_db
server_sqlite3 db2 test.db
do_execsql_test $tn.2.0 {
CREATE TABLE t1(a, b);
CREATE TABLE t2(c, d);
}
# Two concurrent transactions committed.
#
do_test $tn.2.1 {
db eval {
BEGIN;
INSERT INTO t1 VALUES(1, 2);
}
db2 eval {
BEGIN;
INSERT INTO t2 VALUES(3, 4);
}
} {}
do_test $tn.2.2 {
lsort [glob -nocomplain test.db-journal/*-journal]
} {test.db-journal/0-journal test.db-journal/1-journal}
do_test $tn.2.3.1 { db eval COMMIT } {}
do_test $tn.2.3.2 { db2 eval COMMIT } {}
do_execsql_test 2.4 {SELECT * FROM t1, t2} {1 2 3 4}
do_test $tn.2.5 {
lsort [glob -nocomplain test.db-journal/*-journal]
} {test.db-journal/0-journal test.db-journal/1-journal}
do_test $tn.2.6 {
execsql {BEGIN}
execsql {INSERT INTO t1 VALUES(5, 6)}
execsql {BEGIN} db2
catchsql {INSERT INTO t1 VALUES(7, 8)} db2
} {1 {database is locked}}
do_test $tn.2.7 {
# Transaction is automatically rolled back in this case.
sqlite3_get_autocommit db2
} {1}
do_test $tn.2.8 {
execsql COMMIT
execsql { SELECT * FROM t1 } db2
} {1 2 5 6}
db2 close
#-------------------------------------------------------------------------
#
server_reset_db
do_execsql_test $tn.3.0 {
CREATE TABLE t1(a, b);
}
do_test $tn.3.1 {
lsort [glob -nocomplain test.db-journal/*-journal]
} {test.db-journal/0-journal}
do_test $tn.3.2 {
db close
lsort [glob -nocomplain test.db-journal/*-journal]
} {}
#-----------------------------------------------------------------------
# Test that write-locks are downgraded when a transaction is ended,
# even if the connection holds an open read statement.
#
do_test $tn.4.1 {
server_sqlite3 db test.db
server_sqlite3 db2 test.db
db eval {
CREATE TABLE t2(a);
INSERT INTO t2 VALUES('one');
INSERT INTO t2 VALUES('two');
INSERT INTO t2 VALUES('three');
CREATE TABLE t3(k INTEGER PRIMARY KEY, val);
}
set res [list]
db eval { SELECT a FROM t2 ORDER BY rowid } {
db eval { REPLACE INTO t3 VALUES(1, $a) }
lappend res [db2 one { SELECT val FROM t3 }]
}
set res
} {one two three}
catch { db close }
catch { db2 close }
}
finish_test
+57
View File
@@ -0,0 +1,57 @@
# 2017 April 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 implements regression tests for SQLite library. The
# focus of this script is testing the server mode of SQLite.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
set testprefix server3
source $testdir/server_common.tcl
return_if_no_server
foreach {tn vfs} {1 unix-excl 2 unix} {
server_set_vfs $vfs
server_reset_db
server_sqlite3 db2 test.db
do_test 1.1 {
db eval { CREATE TABLE t1(a, b) }
db2 eval { CREATE TABLE t2(a, b) }
} {}
do_test 1.2 {
db eval {
INSERT INTO t2 VALUES(1, 2);
BEGIN;
INSERT INTO t1 VALUES(1, 2);
}
} {}
do_test 1.3 {
list [catch { db2 eval { SELECT * FROM t1 } } msg] $msg
} {1 {database is locked}}
do_test 1.4 {
list [catch { db2 eval { SELECT * FROM t1 } } msg] $msg
} {1 {database is locked}}
do_test 1.4 {
db2 eval { SELECT * FROM t2 }
} {1 2}
}
finish_test
+46
View File
@@ -0,0 +1,46 @@
# 2017 July 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.
#
#***********************************************************************
#
#
ifcapable !server {
proc return_if_no_server {} {
finish_test
return -code return
}
return
} else {
proc return_if_no_server {} {}
}
proc server_sqlite3 {cmd file} {
sqlite3 $cmd $file -vfs $::server_vfs
}
proc server_reset_db {} {
catch {db close}
forcedelete test.db test.db-journal test.db-wal
file mkdir test.db-journal
server_sqlite3 db test.db
}
set ::server_vfs unix-excl
proc server_set_vfs {vfs} {
if {$vfs=="single"} {
set ::server_vfs unix-excl
} elseif {$vfs=="multi"} {
set ::server_vfs unix
} else {
set ::server_vfs $vfs
}
}
+87
View File
@@ -0,0 +1,87 @@
# 2017 April 27
#
# 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 servercrash
ifcapable !crashtest {
finish_test
return
}
do_not_use_codec
source $testdir/server_common.tcl
return_if_no_server
db close
server_set_vfs unix
server_reset_db
do_execsql_test 1.0 {
PRAGMA page_size = 4096;
PRAGMA auto_vacuum = OFF;
CREATE TABLE t1(a, b);
CREATE TABLE t2(c, d);
INSERT INTO t1 VALUES(1, 2), (3, 4);
INSERT INTO t2 VALUES(1, 2), (3, 4);
}
for {set i 0} {$i < 10} {incr i} {
do_test 1.$i.1 {
crashsql -delay 1 -file test.db { INSERT INTO t1 VALUES(5, 6) }
} {1 {child process exited abnormally}}
do_execsql_test 1.$i.2 {
SELECT * FROM t1
} {1 2 3 4}
}
for {set i 0} {$i < 10} {incr i} {
do_test 2.$i.1 {
crashsql -delay 1 -file test.db { INSERT INTO t1 VALUES(5, 6) }
} {1 {child process exited abnormally}}
do_test 2.$i.2 {
sqlite3 dbX test.db
execsql { SELECT * FROM t1 } dbX
} {1 2 3 4}
dbX close
}
db close
for {set i 0} {$i < 10} {incr i} {
do_test 3.$i.1 {
crashsql -delay 1 -file test.db { INSERT INTO t1 VALUES(5, 6) }
} {1 {child process exited abnormally}}
sqlite3 db test.db
do_execsql_test 3.$i.2 { SELECT * FROM t1 } {1 2 3 4}
db close
}
sqlite3 db test.db
db eval {SELECT * FROM t1}
for {set i 0} {$i < 10} {incr i} {
do_test 4.$i.1 {
crashsql -delay 1 -file test.db { INSERT INTO t1 VALUES(5, 6) }
} {1 {child process exited abnormally}}
db close
sqlite3 db test.db
do_execsql_test 4.$i.2 { SELECT * FROM t1 } {1 2 3 4}
}
finish_test
+56
View File
@@ -0,0 +1,56 @@
# 2017 July 09
#
# 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 the server mode of SQLite.
# Specifically, that "PRAGMA freelist_format" works.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix server5
do_execsql_test 1.0 {
PRAGMA freelist_format;
} {1}
do_execsql_test 1.1 {
PRAGMA freelist_format = 2;
} {2}
do_execsql_test 1.2 {
PRAGMA freelist_format;
} {2}
do_execsql_test 1.3 {
PRAGMA freelist_format = 1;
} {1}
do_execsql_test 1.4 {
PRAGMA freelist_format;
} {1}
do_execsql_test 1.5 {
CREATE TABLE t1(x);
PRAGMA freelist_format = 2;
} {2}
do_execsql_test 1.6 {
CREATE TABLE t2(y);
}
do_execsql_test 1.6 {
PRAGMA freelist_format = 1;
} {2}
finish_test
+177
View File
@@ -0,0 +1,177 @@
# 2017 April 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 implements regression tests for SQLite library. The
# focus of this script is testing the server mode of SQLite.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix serverlimit
source $testdir/server_common.tcl
source $testdir/lock_common.tcl
return_if_no_server
#-------------------------------------------------------------------------
# Test plan:
#
# 1.* The concurrent connections limit in multi-process mode. With all
# connections in the local process.
#
# 2.* The concurrent connections limit in multi-process mode. Using
# multiple processes.
#
# 3.* The concurrent transactions limit in single-process mode.
#
server_set_vfs multi
server_reset_db
set MLIMIT 16 ;# maximum number of allowed connections
do_test 1.0 {
server_sqlite3 db test.db
db eval {
CREATE TABLE t1(x);
INSERT INTO t1 VALUES('hello'), ('world');
}
db close
for {set i 0} {$i < $MLIMIT} {incr i} {
server_sqlite3 db.$i test.db
db.$i eval { SELECT * FROM t1 }
}
set {} {}
} {}
# Connection [db] cannot connect - all client slots are occupied.
#
do_test 1.1 {
server_sqlite3 db test.db
list [catch { db eval { SELECT * FROM t1 } } msg] $msg
} {1 {database is locked}}
# But, if one connection disconnects, [db] can then connect and
# query the db.
#
do_test 1.2 {
db.0 close
list [catch { db eval { SELECT * FROM t1 } } msg] $msg
} {0 {hello world}}
do_test 1.3 {
for {set i 0} {$i < $MLIMIT} {incr i} {
catch { db.$i close }
}
set {} {}
} {}
#-------------------------------------------------------------------------
# Connections in different processes.
do_multiclient_test tn {
code1 { db close }
code2 { db2 close }
code3 { db3 close }
set N1 [expr $MLIMIT / 2]
set N2 [expr $MLIMIT - $N1]
do_test 2.$tn.0 {
file mkdir test.db-journal
code1 {
sqlite3 db test.db
db eval {
CREATE TABLE t11(a, b);
INSERT INTO t11 VALUES(1, 2), (3, 4);
}
db close
for {set i 0} {$i < $N1} {incr i} {
sqlite3 db.$i test.db
db.$i eval { SELECT * FROM t11 }
}
}
code2 [string map [list %N2% $N2] {
for {set i 0} {$i < %N2%} {incr i} {
sqlite3 db2.$i test.db
db2.$i eval { SELECT * FROM t11 }
}
}]
code2 { db2.0 eval {SELECT * FROM t11} }
} {1 2 3 4}
do_test 2.$tn.1 {
code3 { sqlite3 db3 test.db }
csql3 { SELECT * FROM t11 }
} {1 {database is locked}}
do_test 2.$tn.2 {
code2 { db2.0 close }
csql3 { SELECT * FROM t11 }
} {0 {1 2 3 4}}
do_test 2.$tn.3 {
code1 { sqlite3 db test.db }
csql1 { SELECT * FROM t11 }
} {1 {database is locked}}
do_test 2.$tn.4 {
code2 { db2.1 close }
csql1 { SELECT * FROM t11 }
} {0 {1 2 3 4}}
do_test 2.$tn.X {
code1 { for {set i 0} {$i < 50} {incr i} { catch {db.$i close} } }
code2 { for {set i 0} {$i < 50} {incr i} { catch {db2.$i close} } }
} {}
}
server_set_vfs single
server_reset_db
set TLIMIT 16
do_test 3.0 {
execsql "CREATE TABLE t1 (o PRIMARY KEY) WITHOUT ROWID"
for {set i 0} {$i < $TLIMIT} {incr i} {
execsql "CREATE TABLE x$i (o PRIMARY KEY) WITHOUT ROWID"
}
set "" ""
} {}
do_test 3.1 {
for {set i 0} {$i < $TLIMIT} {incr i} {
sqlite3 db.$i test.db
db.$i eval "
BEGIN;
INSERT INTO x$i VALUES ('one');
"
}
} {}
do_catchsql_test 3.2 {
INSERT INTO t1 VALUES('two');
} {1 {database is locked}}
do_test 3.3 {
db.0 eval COMMIT
execsql { INSERT INTO t1 VALUES('two'); }
} {}
do_catchsql_test 3.4 { SELECT * FROM x1 } {1 {database is locked}}
do_catchsql_test 3.5 { SELECT * FROM x0 } {0 one}
do_test 3.6 {
for {set i 1} {$i < $TLIMIT} {incr i} {
db.$i eval COMMIT
}
} {}
finish_test
+65
View File
@@ -0,0 +1,65 @@
# 2017 July 09
#
# 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 the server mode of SQLite.
# Specifically, that "BEGIN READONLY" starts a read-only MVCC
# transaction.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
set testprefix server4
source $testdir/server_common.tcl
return_if_no_server
server_reset_db
server_sqlite3 db2 test.db
do_execsql_test 1.0 {
CREATE TABLE t1(x);
INSERT INTO t1 VALUES(1);
CREATE TABLE t2(x);
INSERT INTO t2 VALUES(1);
BEGIN;
INSERT INTO t1 VALUES(2);
INSERT INTO t2 VALUES(2);
}
do_execsql_test -db db2 1.1 {
BEGIN READONLY;
SELECT * FROM t1;
} {1}
do_execsql_test 1.2 {
COMMIT;
INSERT INTO t1 VALUES(3);
SELECT * FROM t1;
} {1 2 3}
do_execsql_test 1.2a {
INSERT INTO t2 VALUES(3);
} {}
do_execsql_test -db db2 1.3 {
SELECT * FROM t2;
} {1}
do_execsql_test -db db2 1.4 {
ROLLBACK;
SELECT * FROM t1;
} {1 2 3}
finish_test
+4
View File
@@ -586,6 +586,10 @@ proc reset_db {} {
forcedelete test.db
forcedelete test.db-journal
forcedelete test.db-wal
for {set i 0} {$i < 16} {incr i} {
forcedelete test.db-journal$i
}
sqlite3 db ./test.db
set ::DB [sqlite3_connection_pointer db]
if {[info exists ::SETUP_SQL]} {
+1
View File
@@ -239,6 +239,7 @@ static Keyword aKeywordTable[] = {
{ "PRIMARY", "TK_PRIMARY", ALWAYS },
{ "QUERY", "TK_QUERY", EXPLAIN },
{ "RAISE", "TK_RAISE", TRIGGER },
{ "READONLY", "TK_READONLY", ALWAYS },
{ "RECURSIVE", "TK_RECURSIVE", CTE },
{ "REFERENCES", "TK_REFERENCES", FKEY },
{ "REGEXP", "TK_LIKE_KW", ALWAYS },
+4
View File
@@ -382,6 +382,10 @@ set pragma_def {
NAME: optimize
FLAG: Result1 NeedSchema
NAME: freelist_format
FLAG: NeedSchema Result0 SchemaReq
IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) && defined(SQLITE_SERVER_EDITION)
}
# Open the output file
+2
View File
@@ -114,6 +114,7 @@ foreach hdr {
pcache.h
pragma.h
rtree.h
server.h
sqlite3session.h
sqlite3.h
sqlite3ext.h
@@ -328,6 +329,7 @@ foreach file {
rowset.c
pager.c
wal.c
server.c
btmutex.c
btree.c
+303
View File
@@ -0,0 +1,303 @@
#!/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(-mode) "server"
set O(-rows) [expr 5000000]
set O(-tserver) "./tserver"
set O(-seconds) 20
set O(-writers) 2
set O(-readers) 1
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.$mode.db)"
puts stderr " -mode server|begin-concurrent (default: server)"
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: 2)"
puts stderr " -readers <number of reader clients> (default: 1)"
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 != "server" && $val != "begin-concurrent"
&& $val != "wal" && $val != "persist"
} {
set xyz "\"server\", \"begin-concurrent\", \"wal\" or \"persist\""
error_out "Found \"$val\" - expected $xyz"
}
}
}
set O($opt) [lindex $argv $i]
}
if {$O(-database)==""} {
set O(-database) "test.$O(-mode).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;
}
if {$O(-mode)=="server"} {
db eval "PRAGMA freelist_format = 2"
}
db close
switch -- $O(-mode) {
server {
if {![file exists $O(-database)-journal]} {
file mkdir $O(-database)-journal
}
}
wal {
sqlite3 db $O(-database)
db eval {PRAGMA journal_mode = wal}
db close
}
begin-concurrent {
sqlite3 db $O(-database)
db eval {PRAGMA journal_mode = wal}
db close
}
}
}
}
#-------------------------------------------------------------------------
# Functions to start and stop the tserver process:
#
# tserver_start
# tserver_stop
#
set ::tserver {}
proc tserver_start {} {
global O
set ::tserver [open "|$O(-tserver) -vfs unix-excl $O(-database)"]
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
switch -- $O(-mode) {
persist {
puts $fd "PRAGMA journal_mode = PERSIST;"
}
}
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 {} {
global O
set commit "COMMIT;"
set begin "BEGIN;"
if {$O(-mode)=="begin-concurrent" || $O(-mode)=="wal"} {
set commit ".mutex_commit"
set begin "BEGIN CONCURRENT;"
}
if {$O(-mode)=="server"} { set beginarg "READONLY" }
set tail "randomblob(16), randomblob(16), randomblob(400));"
return [subst -nocommands {
$begin
REPLACE INTO t1 VALUES(abs(random() % $O(-rows)), $tail
REPLACE INTO t1 VALUES(abs(random() % $O(-rows)), $tail
REPLACE INTO t1 VALUES(abs(random() % $O(-rows)), $tail
REPLACE INTO t1 VALUES(abs(random() % $O(-rows)), $tail
REPLACE INTO t1 VALUES(abs(random() % $O(-rows)), $tail
$commit
}]
}
proc script_reader {} {
global O
set beginarg ""
if {$O(-mode)=="server"} { set beginarg "READONLY" }
return [subst -nocommands {
BEGIN $beginarg;
SELECT * FROM t1 WHERE a>abs((random()%$O(-rows))) LIMIT 10;
SELECT * FROM t1 WHERE a>abs((random()%$O(-rows))) LIMIT 10;
SELECT * FROM t1 WHERE a>abs((random()%$O(-rows))) LIMIT 10;
SELECT * FROM t1 WHERE a>abs((random()%$O(-rows))) LIMIT 10;
SELECT * FROM t1 WHERE a>abs((random()%$O(-rows))) LIMIT 10;
END;
}]
}
create_test_database
tserver_start
for {set i 0} {$i < $O(-writers)} {incr i} {
client_launch w.$i [script_writer]
}
for {set i 0} {$i < $O(-readers)} {incr i} {
client_launch r.$i [script_reader]
}
client_wait
set name(w) "Writers"
set name(r) "Readers"
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
+582
View File
@@ -0,0 +1,582 @@
/*
** 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).
**
** 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 bMutex;
};
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[] */
};
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\n", sqlite3_errmsg(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( nFrame>=g.nThreshold ){
g.bCkptRequired = 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 bMutex flag is set, grab g.commit_mutex before executing
** the SQL statement (which is always "COMMIT" in this case). */
if( p->aPrepare[i].bMutex ){
sqlite3_mutex_enter(g.commit_mutex);
}
/* Execute the statement */
while( sqlite3_step(pStmt)==SQLITE_ROW );
rc = sqlite3_reset(pStmt);
/* Relinquish the g.commit_mutex mutex if required. */
if( p->aPrepare[i].bMutex ){
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\n", sqlite3_errmsg(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;
}
/* Checkpoint handling. */
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 ){
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 (repeat=%d)\n", p->nRepeat);
}
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].bMutex = 1;
}
}
else if( n>=2 && n<=4 && 0==strncmp(z, "stop", n) ){
sqlite3_close(g.db);
exit(0);
}
else{
send_message(p,
"unrecognized dot command: %.*s\n"
"should be \"list\", \"run\", \"repeats\", \"mutex_commit\" "
"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\n", ctx.fd);
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;
/* Ignore SIGPIPE. Otherwise the server exits if a client disconnects
** abruptly. */
signal(SIGPIPE, SIG_IGN);
if( argc!=2 && argc!=4 ){
usage(argv[0]);
}
if( argc==4 ){
int n = strlen(argv[1]);
if( n<2 || n>4 || memcmp("-vfs", argv[1], 4) ) usage(argv[0]);
g.zVfs = argv[2];
}
g.zDatabaseName = argv[argc-1];
g.commit_mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
g.nThreshold = TSERVER_DEFAULT_CHECKPOINT_THRESHOLD;
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;
}