Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f0c6bc3a5 | |||
| bddcb53614 | |||
| fd971c8b83 | |||
| 332faddd99 | |||
| 7c4805fa5c | |||
| 852841cc14 | |||
| 528e4e4bbf | |||
| 335ce3575f | |||
| 4c454ff90b | |||
| 7ba5f0e768 | |||
| e278a9bacd | |||
| b585dbf8db | |||
| cfcd9d34b9 | |||
| 44bbccc05a | |||
| 0a1c5dc414 | |||
| 0ee56139e9 | |||
| af12f8e2c9 | |||
| aa90aad86b | |||
| b4b69facc0 | |||
| a12baadd15 | |||
| 11584e6779 | |||
| 6af090cc05 | |||
| e1e878687d | |||
| e83dba9e4d | |||
| 26a8ddbcdb | |||
| 087794b136 | |||
| ad9b696471 | |||
| f42e5d8917 | |||
| 411c20ad62 | |||
| 6b0cfe5343 | |||
| 6f3283a427 | |||
| bab38cbc9a | |||
| 538ed8e376 | |||
| b4c4601adf | |||
| 60dd40a62c | |||
| 2ac63b9812 | |||
| bf431367f3 | |||
| 730859568d | |||
| c9bcad12ea | |||
| e376cac1d1 | |||
| b7bcf5c16c | |||
| 31349393ba | |||
| 8df9974169 | |||
| f427296520 | |||
| 920a129de3 | |||
| 34d02325be | |||
| aa189cf00f | |||
| 2dab053e22 | |||
| 0561247d07 | |||
| 1381bbc262 |
@@ -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 "<database>-journal" in the file system alongside
|
||||
the database file "<database>" 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;
|
||||
<create directory>
|
||||
END;
|
||||
</pre>
|
||||
|
||||
<p> As well as signalling new clients that they should enter server-mode,
|
||||
creating a directory named "<database>-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 <database>-journal, server-mode
|
||||
clients use <database>-journal/<client-id>-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 <client-id> 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 <client-id> 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 <client-id>. 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
|
||||
<database>-journal and that no other process holds a RESERVED lock).
|
||||
If the <database>-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>abs((random()%5000000)) LIMIT 10;
|
||||
SELECT * FROM t1 WHERE a>abs((random()%5000000)) LIMIT 10;
|
||||
SELECT * FROM t1 WHERE a>abs((random()%5000000)) LIMIT 10;
|
||||
SELECT * FROM t1 WHERE a>abs((random()%5000000)) LIMIT 10;
|
||||
SELECT * FROM t1 WHERE a>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>
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
@@ -1 +1 @@
|
||||
d282f064698782cf7b584138549a6b27befa0b945ae96b52a3ef6f8a13448077
|
||||
754ad35cd26da361e2ed736b0e400497714a0db9b7fd05fd24e7803b6f478263
|
||||
+384
-28
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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*/}
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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{
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]} {
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user