diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle
index 451bba39..5fbedf97 100644
--- a/TMessagesProj/build.gradle
+++ b/TMessagesProj/build.gradle
@@ -82,7 +82,7 @@ android {
defaultConfig {
minSdkVersion 8
targetSdkVersion 21
- versionCode 416
- versionName "2.3.3"
+ versionCode 423
+ versionName "2.4.0"
}
}
diff --git a/TMessagesProj/jni/sqlite/sqlite3.c b/TMessagesProj/jni/sqlite/sqlite3.c
index 81e9c733..cae0c4ad 100644
--- a/TMessagesProj/jni/sqlite/sqlite3.c
+++ b/TMessagesProj/jni/sqlite/sqlite3.c
@@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
-** version 3.8.7.4. By combining all the individual C code files into this
+** version 3.8.8.1. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@@ -43,6 +43,53 @@
#ifndef _SQLITEINT_H_
#define _SQLITEINT_H_
+/*
+** Include the header file used to customize the compiler options for MSVC.
+** This should be done first so that it can successfully prevent spurious
+** compiler warnings due to subsequent content in this file and other files
+** that are included by this file.
+*/
+/************** Include msvc.h in the middle of sqliteInt.h ******************/
+/************** Begin file msvc.h ********************************************/
+/*
+** 2015 January 12
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains code that is specific to MSVC.
+*/
+#ifndef _MSVC_H_
+#define _MSVC_H_
+
+#if defined(_MSC_VER)
+#pragma warning(disable : 4054)
+#pragma warning(disable : 4055)
+#pragma warning(disable : 4100)
+#pragma warning(disable : 4127)
+#pragma warning(disable : 4152)
+#pragma warning(disable : 4189)
+#pragma warning(disable : 4206)
+#pragma warning(disable : 4210)
+#pragma warning(disable : 4232)
+#pragma warning(disable : 4244)
+#pragma warning(disable : 4305)
+#pragma warning(disable : 4306)
+#pragma warning(disable : 4702)
+#pragma warning(disable : 4706)
+#endif /* defined(_MSC_VER) */
+
+#endif /* _MSVC_H_ */
+
+/************** End of msvc.h ************************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
+
/*
** These #defines should enable >2GB file support on POSIX if the
** underlying operating system supports it. If the OS lacks
@@ -181,7 +228,7 @@ extern "C" {
/*
** These no-op macros are used in front of interfaces to mark those
** interfaces as either deprecated or experimental. New applications
-** should not use deprecated interfaces - they are support for backwards
+** should not use deprecated interfaces - they are supported for backwards
** compatibility only. Application writers should be aware that
** experimental interfaces are subject to change in point releases.
**
@@ -231,9 +278,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.8.7.4"
-#define SQLITE_VERSION_NUMBER 3008007
-#define SQLITE_SOURCE_ID "2014-12-09 01:34:36 f66f7a17b78ba617acde90fc810107f34f1a1f2e"
+#define SQLITE_VERSION "3.8.8.1"
+#define SQLITE_VERSION_NUMBER 3008008
+#define SQLITE_SOURCE_ID "2015-01-20 16:51:25 f73337e3e289915a76ca96e7a05a1a8d4e890d55"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -325,7 +372,7 @@ SQLITE_API const char *sqlite3_compileoption_get(int N);
** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but
** can be fully or partially disabled using a call to [sqlite3_config()]
** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD],
-** or [SQLITE_CONFIG_MUTEX]. ^(The return value of the
+** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the
** sqlite3_threadsafe() function shows only the compile-time setting of
** thread safety, not any run-time changes to that setting made by
** sqlite3_config(). In other words, the return value from sqlite3_threadsafe()
@@ -1345,7 +1392,7 @@ struct sqlite3_vfs {
**
**
** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as
-** was given no the corresponding lock.
+** was given on the corresponding lock.
**
** The xShmLock method can transition between unlocked and SHARED or
** between unlocked and EXCLUSIVE. It cannot transition between SHARED
@@ -1628,26 +1675,28 @@ struct sqlite3_mem_methods {
** SQLITE_CONFIG_SERIALIZED configuration option.
**
** [[SQLITE_CONFIG_MALLOC]]
SQLITE_CONFIG_MALLOC
-**
^(This option takes a single argument which is a pointer to an
-** instance of the [sqlite3_mem_methods] structure. The argument specifies
+**
^(The SQLITE_CONFIG_MALLOC option takes a single argument which is
+** a pointer to an instance of the [sqlite3_mem_methods] structure.
+** The argument specifies
** alternative low-level memory allocation routines to be used in place of
** the memory allocation routines built into SQLite.)^ ^SQLite makes
** its own private copy of the content of the [sqlite3_mem_methods] structure
** before the [sqlite3_config()] call returns.
**
** [[SQLITE_CONFIG_GETMALLOC]]
SQLITE_CONFIG_GETMALLOC
-**
^(This option takes a single argument which is a pointer to an
-** instance of the [sqlite3_mem_methods] structure. The [sqlite3_mem_methods]
+**
^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which
+** is a pointer to an instance of the [sqlite3_mem_methods] structure.
+** The [sqlite3_mem_methods]
** structure is filled with the currently defined memory allocation routines.)^
** This option can be used to overload the default memory allocation
** routines with a wrapper that simulations memory allocation failure or
** tracks memory usage, for example.
**
** [[SQLITE_CONFIG_MEMSTATUS]]
SQLITE_CONFIG_MEMSTATUS
-**
^This option takes single argument of type int, interpreted as a
-** boolean, which enables or disables the collection of memory allocation
-** statistics. ^(When memory allocation statistics are disabled, the
-** following SQLite interfaces become non-operational:
+**
^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
+** interpreted as a boolean, which enables or disables the collection of
+** memory allocation statistics. ^(When memory allocation statistics are
+** disabled, the following SQLite interfaces become non-operational:
**
^This option specifies a static memory buffer that SQLite can use for
-** scratch memory. There are three arguments: A pointer an 8-byte
+**
^The SQLITE_CONFIG_SCRATCH option specifies a static memory buffer
+** that SQLite can use for scratch memory. ^(There are three arguments
+** to SQLITE_CONFIG_SCRATCH: A pointer an 8-byte
** aligned memory buffer from which the scratch allocations will be
** drawn, the size of each scratch allocation (sz),
-** and the maximum number of scratch allocations (N). The sz
-** argument must be a multiple of 16.
+** and the maximum number of scratch allocations (N).)^
** The first argument must be a pointer to an 8-byte aligned buffer
** of at least sz*N bytes of memory.
-** ^SQLite will use no more than two scratch buffers per thread. So
-** N should be set to twice the expected maximum number of threads.
-** ^SQLite will never require a scratch buffer that is more than 6
-** times the database page size. ^If SQLite needs needs additional
+** ^SQLite will not use more than one scratch buffers per thread.
+** ^SQLite will never request a scratch buffer that is more than 6
+** times the database page size.
+** ^If SQLite needs needs additional
** scratch memory beyond what is provided by this configuration option, then
-** [sqlite3_malloc()] will be used to obtain the memory needed.
+** [sqlite3_malloc()] will be used to obtain the memory needed.
+** ^When the application provides any amount of scratch memory using
+** SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary large
+** [sqlite3_malloc|heap allocations].
+** This can help [Robson proof|prevent memory allocation failures] due to heap
+** fragmentation in low-memory embedded systems.
+**
**
** [[SQLITE_CONFIG_PAGECACHE]]
SQLITE_CONFIG_PAGECACHE
-**
^This option specifies a static memory buffer that SQLite can use for
-** the database page cache with the default page cache implementation.
+**
^The SQLITE_CONFIG_PAGECACHE option specifies a static memory buffer
+** that SQLite can use for the database page cache with the default page
+** cache implementation.
** This configuration should not be used if an application-define page
-** cache implementation is loaded using the SQLITE_CONFIG_PCACHE2 option.
-** There are three arguments to this option: A pointer to 8-byte aligned
+** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2]
+** configuration option.
+** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to
+** 8-byte aligned
** memory, the size of each page buffer (sz), and the number of pages (N).
** The sz argument should be the size of the largest database page
-** (a power of two between 512 and 32768) plus a little extra for each
-** page header. ^The page header size is 20 to 40 bytes depending on
-** the host architecture. ^It is harmless, apart from the wasted memory,
-** to make sz a little too large. The first
-** argument should point to an allocation of at least sz*N bytes of memory.
+** (a power of two between 512 and 65536) plus some extra bytes for each
+** page header. ^The number of extra bytes needed by the page header
+** can be determined using the [SQLITE_CONFIG_PCACHE_HDRSZ] option
+** to [sqlite3_config()].
+** ^It is harmless, apart from the wasted memory,
+** for the sz parameter to be larger than necessary. The first
+** argument should pointer to an 8-byte aligned block of memory that
+** is at least sz*N bytes of memory, otherwise subsequent behavior is
+** undefined.
** ^SQLite will use the memory provided by the first argument to satisfy its
** memory needs for the first N pages that it adds to cache. ^If additional
** page cache memory is needed beyond what is provided by this option, then
-** SQLite goes to [sqlite3_malloc()] for the additional storage space.
-** The pointer in the first argument must
-** be aligned to an 8-byte boundary or subsequent behavior of SQLite
-** will be undefined.
+** SQLite goes to [sqlite3_malloc()] for the additional storage space.
**
** [[SQLITE_CONFIG_HEAP]]
SQLITE_CONFIG_HEAP
-**
^This option specifies a static memory buffer that SQLite will use
-** for all of its dynamic memory allocation needs beyond those provided
-** for by [SQLITE_CONFIG_SCRATCH] and [SQLITE_CONFIG_PAGECACHE].
-** There are three arguments: An 8-byte aligned pointer to the memory,
+**
^The SQLITE_CONFIG_HEAP option specifies a static memory buffer
+** that SQLite will use for all of its dynamic memory allocation needs
+** beyond those provided for by [SQLITE_CONFIG_SCRATCH] and
+** [SQLITE_CONFIG_PAGECACHE].
+** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled
+** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns
+** [SQLITE_ERROR] if invoked otherwise.
+** ^There are three arguments to SQLITE_CONFIG_HEAP:
+** An 8-byte aligned pointer to the memory,
** the number of bytes in the memory buffer, and the minimum allocation size.
** ^If the first pointer (the memory pointer) is NULL, then SQLite reverts
** to using its default memory allocator (the system malloc() implementation),
** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the
-** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or
-** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory
+** memory pointer is not NULL then the alternative memory
** allocator is engaged to handle all of SQLites memory allocation needs.
** The first pointer (the memory pointer) must be aligned to an 8-byte
** boundary or subsequent behavior of SQLite will be undefined.
@@ -1714,11 +1777,11 @@ struct sqlite3_mem_methods {
** for the minimum allocation size are 2**5 through 2**8.
**
** [[SQLITE_CONFIG_MUTEX]]
SQLITE_CONFIG_MUTEX
-**
^(This option takes a single argument which is a pointer to an
-** instance of the [sqlite3_mutex_methods] structure. The argument specifies
-** alternative low-level mutex routines to be used in place
-** the mutex routines built into SQLite.)^ ^SQLite makes a copy of the
-** content of the [sqlite3_mutex_methods] structure before the call to
+**
^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a
+** pointer to an instance of the [sqlite3_mutex_methods] structure.
+** The argument specifies alternative low-level mutex routines to be used
+** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of
+** the content of the [sqlite3_mutex_methods] structure before the call to
** [sqlite3_config()] returns. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** the entire mutexing subsystem is omitted from the build and hence calls to
@@ -1726,8 +1789,8 @@ struct sqlite3_mem_methods {
** return [SQLITE_ERROR].
**
** [[SQLITE_CONFIG_GETMUTEX]]
SQLITE_CONFIG_GETMUTEX
-**
^(This option takes a single argument which is a pointer to an
-** instance of the [sqlite3_mutex_methods] structure. The
+**
^(The SQLITE_CONFIG_GETMUTEX option takes a single argument which
+** is a pointer to an instance of the [sqlite3_mutex_methods] structure. The
** [sqlite3_mutex_methods]
** structure is filled with the currently defined mutex routines.)^
** This option can be used to overload the default mutex allocation
@@ -1739,25 +1802,25 @@ struct sqlite3_mem_methods {
** return [SQLITE_ERROR].
**
** [[SQLITE_CONFIG_LOOKASIDE]]
SQLITE_CONFIG_LOOKASIDE
-**
^(This option takes two arguments that determine the default
-** memory allocation for the lookaside memory allocator on each
-** [database connection]. The first argument is the
+**
^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine
+** the default size of lookaside memory on each [database connection].
+** The first argument is the
** size of each lookaside buffer slot and the second is the number of
-** slots allocated to each database connection.)^ ^(This option sets the
-** default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
-** verb to [sqlite3_db_config()] can be used to change the lookaside
+** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE
+** sets the default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
+** option to [sqlite3_db_config()] can be used to change the lookaside
** configuration on individual connections.)^
**
** [[SQLITE_CONFIG_PCACHE2]]
SQLITE_CONFIG_PCACHE2
-**
^(This option takes a single argument which is a pointer to
-** an [sqlite3_pcache_methods2] object. This object specifies the interface
-** to a custom page cache implementation.)^ ^SQLite makes a copy of the
-** object and uses it for page cache memory allocations.
+**
^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
+** a pointer to an [sqlite3_pcache_methods2] object. This object specifies
+** the interface to a custom page cache implementation.)^
+** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.
**
** [[SQLITE_CONFIG_GETPCACHE2]]
SQLITE_CONFIG_GETPCACHE2
-**
^(This option takes a single argument which is a pointer to an
-** [sqlite3_pcache_methods2] object. SQLite copies of the current
-** page cache implementation into that object.)^
+**
^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which
+** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of
+** the current page cache implementation into that object.)^
**
** [[SQLITE_CONFIG_LOG]]
SQLITE_CONFIG_LOG
**
The SQLITE_CONFIG_LOG option is used to configure the SQLite
@@ -1780,10 +1843,11 @@ struct sqlite3_mem_methods {
** function must be threadsafe.
**
** [[SQLITE_CONFIG_URI]]
SQLITE_CONFIG_URI
-**
^(This option takes a single argument of type int. If non-zero, then
-** URI handling is globally enabled. If the parameter is zero, then URI handling
-** is globally disabled.)^ ^If URI handling is globally enabled, all filenames
-** passed to [sqlite3_open()], [sqlite3_open_v2()], [sqlite3_open16()] or
+**
^(The SQLITE_CONFIG_URI option takes a single argument of type int.
+** If non-zero, then URI handling is globally enabled. If the parameter is zero,
+** then URI handling is globally disabled.)^ ^If URI handling is globally
+** enabled, all filenames passed to [sqlite3_open()], [sqlite3_open_v2()],
+** [sqlite3_open16()] or
** specified as part of [ATTACH] commands are interpreted as URIs, regardless
** of whether or not the [SQLITE_OPEN_URI] flag is set when the database
** connection is opened. ^If it is globally disabled, filenames are
@@ -1793,9 +1857,10 @@ struct sqlite3_mem_methods {
** [SQLITE_USE_URI] symbol defined.)^
**
** [[SQLITE_CONFIG_COVERING_INDEX_SCAN]]
SQLITE_CONFIG_COVERING_INDEX_SCAN
-**
^This option takes a single integer argument which is interpreted as
-** a boolean in order to enable or disable the use of covering indices for
-** full table scans in the query optimizer. ^The default setting is determined
+**
^The SQLITE_CONFIG_COVERING_INDEX_SCAN option takes a single integer
+** argument which is interpreted as a boolean in order to enable or disable
+** the use of covering indices for full table scans in the query optimizer.
+** ^The default setting is determined
** by the [SQLITE_ALLOW_COVERING_INDEX_SCAN] compile-time option, or is "on"
** if that compile-time option is omitted.
** The ability to disable the use of covering indices for full table scans
@@ -1835,19 +1900,39 @@ struct sqlite3_mem_methods {
** ^The default setting can be overridden by each database connection using
** either the [PRAGMA mmap_size] command, or by using the
** [SQLITE_FCNTL_MMAP_SIZE] file control. ^(The maximum allowed mmap size
-** cannot be changed at run-time. Nor may the maximum allowed mmap size
-** exceed the compile-time maximum mmap size set by the
+** will be silently truncated if necessary so that it does not exceed the
+** compile-time maximum mmap size set by the
** [SQLITE_MAX_MMAP_SIZE] compile-time option.)^
** ^If either argument to this option is negative, then that argument is
** changed to its compile-time default.
**
** [[SQLITE_CONFIG_WIN32_HEAPSIZE]]
**
SQLITE_CONFIG_WIN32_HEAPSIZE
-**
^This option is only available if SQLite is compiled for Windows
-** with the [SQLITE_WIN32_MALLOC] pre-processor macro defined.
-** SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value
+**
^The SQLITE_CONFIG_WIN32_HEAPSIZE option is only available if SQLite is
+** compiled for Windows with the [SQLITE_WIN32_MALLOC] pre-processor macro
+** defined. ^SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value
** that specifies the maximum size of the created heap.
**
+**
+** [[SQLITE_CONFIG_PCACHE_HDRSZ]]
+**
SQLITE_CONFIG_PCACHE_HDRSZ
+**
^The SQLITE_CONFIG_PCACHE_HDRSZ option takes a single parameter which
+** is a pointer to an integer and writes into that integer the number of extra
+** bytes per page required for each page in [SQLITE_CONFIG_PAGECACHE].
+** The amount of extra space required can change depending on the compiler,
+** target platform, and SQLite version.
+**
+** [[SQLITE_CONFIG_PMASZ]]
+**
SQLITE_CONFIG_PMASZ
+**
^The SQLITE_CONFIG_PMASZ option takes a single parameter which
+** is an unsigned integer and sets the "Minimum PMA Size" for the multithreaded
+** sorter to that integer. The default minimum PMA Size is set by the
+** [SQLITE_SORTER_PMASZ] compile-time option. New threads are launched
+** to help with sort operations when multithreaded sorting
+** is enabled (using the [PRAGMA threads] command) and the amount of content
+** to be sorted exceeds the page size times the minimum of the
+** [PRAGMA cache_size] setting and this value.
+**
*/
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
@@ -1872,6 +1957,8 @@ struct sqlite3_mem_methods {
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */
+#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
+#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
/*
** CAPI3REF: Database Connection Configuration Options
@@ -1999,47 +2086,45 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
/*
** CAPI3REF: Count The Number Of Rows Modified
**
-** ^This function returns the number of database rows that were changed
-** or inserted or deleted by the most recently completed SQL statement
-** on the [database connection] specified by the first parameter.
-** ^(Only changes that are directly specified by the [INSERT], [UPDATE],
-** or [DELETE] statement are counted. Auxiliary changes caused by
-** triggers or [foreign key actions] are not counted.)^ Use the
-** [sqlite3_total_changes()] function to find the total number of changes
-** including changes caused by triggers and foreign key actions.
+** ^This function returns the number of rows modified, inserted or
+** deleted by the most recently completed INSERT, UPDATE or DELETE
+** statement on the database connection specified by the only parameter.
+** ^Executing any other type of SQL statement does not modify the value
+** returned by this function.
**
-** ^Changes to a view that are simulated by an [INSTEAD OF trigger]
-** are not counted. Only real table changes are counted.
+** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are
+** considered - auxiliary changes caused by [CREATE TRIGGER | triggers],
+** [foreign key actions] or [REPLACE] constraint resolution are not counted.
+**
+** Changes to a view that are intercepted by
+** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value
+** returned by sqlite3_changes() immediately after an INSERT, UPDATE or
+** DELETE statement run on a view is always zero. Only changes made to real
+** tables are counted.
**
-** ^(A "row change" is a change to a single row of a single table
-** caused by an INSERT, DELETE, or UPDATE statement. Rows that
-** are changed as side effects of [REPLACE] constraint resolution,
-** rollback, ABORT processing, [DROP TABLE], or by any other
-** mechanisms do not count as direct row changes.)^
-**
-** A "trigger context" is a scope of execution that begins and
-** ends with the script of a [CREATE TRIGGER | trigger].
-** Most SQL statements are
-** evaluated outside of any trigger. This is the "top level"
-** trigger context. If a trigger fires from the top level, a
-** new trigger context is entered for the duration of that one
-** trigger. Subtriggers create subcontexts for their duration.
-**
-** ^Calling [sqlite3_exec()] or [sqlite3_step()] recursively does
-** not create a new trigger context.
-**
-** ^This function returns the number of direct row changes in the
-** most recent INSERT, UPDATE, or DELETE statement within the same
-** trigger context.
-**
-** ^Thus, when called from the top level, this function returns the
-** number of changes in the most recent INSERT, UPDATE, or DELETE
-** that also occurred at the top level. ^(Within the body of a trigger,
-** the sqlite3_changes() interface can be called to find the number of
-** changes in the most recently completed INSERT, UPDATE, or DELETE
-** statement within the body of the same trigger.
-** However, the number returned does not include changes
-** caused by subtriggers since those have their own context.)^
+** Things are more complicated if the sqlite3_changes() function is
+** executed while a trigger program is running. This may happen if the
+** program uses the [changes() SQL function], or if some other callback
+** function invokes sqlite3_changes() directly. Essentially:
+**
+**
+**
^(Before entering a trigger program the value returned by
+** sqlite3_changes() function is saved. After the trigger program
+** has finished, the original value is restored.)^
+**
+**
^(Within a trigger program each INSERT, UPDATE and DELETE
+** statement sets the value returned by sqlite3_changes()
+** upon completion as normal. Of course, this value will not include
+** any changes performed by sub-triggers, as the sqlite3_changes()
+** value will be saved and restored after each sub-trigger has run.)^
+**
+**
+** ^This means that if the changes() SQL function (or similar) is used
+** by the first INSERT, UPDATE or DELETE statement within a trigger, it
+** returns the value as set when the calling statement began executing.
+** ^If it is used by the second or subsequent such statement within a trigger
+** program, the value returned reflects the number of rows modified by the
+** previous INSERT, UPDATE or DELETE statement within the same trigger.
**
** See also the [sqlite3_total_changes()] interface, the
** [count_changes pragma], and the [changes() SQL function].
@@ -2053,20 +2138,17 @@ SQLITE_API int sqlite3_changes(sqlite3*);
/*
** CAPI3REF: Total Number Of Rows Modified
**
-** ^This function returns the number of row changes caused by [INSERT],
-** [UPDATE] or [DELETE] statements since the [database connection] was opened.
-** ^(The count returned by sqlite3_total_changes() includes all changes
-** from all [CREATE TRIGGER | trigger] contexts and changes made by
-** [foreign key actions]. However,
-** the count does not include changes used to implement [REPLACE] constraints,
-** do rollbacks or ABORT processing, or [DROP TABLE] processing. The
-** count does not include rows of views that fire an [INSTEAD OF trigger],
-** though if the INSTEAD OF trigger makes changes of its own, those changes
-** are counted.)^
-** ^The sqlite3_total_changes() function counts the changes as soon as
-** the statement that makes them is completed (when the statement handle
-** is passed to [sqlite3_reset()] or [sqlite3_finalize()]).
-**
+** ^This function returns the total number of rows inserted, modified or
+** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed
+** since the database connection was opened, including those executed as
+** part of trigger programs. ^Executing any other type of SQL statement
+** does not affect the value returned by sqlite3_total_changes().
+**
+** ^Changes made as part of [foreign key actions] are included in the
+** count, but those made as part of REPLACE constraint resolution are
+** not. ^Changes to a view that are intercepted by INSTEAD OF triggers
+** are not counted.
+**
** See also the [sqlite3_changes()] interface, the
** [count_changes pragma], and the [total_changes() SQL function].
**
@@ -2153,6 +2235,7 @@ SQLITE_API int sqlite3_complete16(const void *sql);
/*
** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors
+** KEYWORDS: {busy-handler callback} {busy handler}
**
** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X
** that might be invoked with argument P whenever
@@ -2169,7 +2252,7 @@ SQLITE_API int sqlite3_complete16(const void *sql);
** ^The first argument to the busy handler is a copy of the void* pointer which
** is the third argument to sqlite3_busy_handler(). ^The second argument to
** the busy handler callback is the number of times that the busy handler has
-** been invoked for the same locking event. ^If the
+** been invoked previously for the same locking event. ^If the
** busy callback returns 0, then no additional attempts are made to
** access the database and [SQLITE_BUSY] is returned
** to the application.
@@ -2544,13 +2627,14 @@ SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
** applications to access the same PRNG for other purposes.
**
** ^A call to this routine stores N bytes of randomness into buffer P.
-** ^If N is less than one, then P can be a NULL pointer.
+** ^The P parameter can be a NULL pointer.
**
** ^If this routine has not been previously called or if the previous
-** call had N less than one, then the PRNG is seeded using randomness
-** obtained from the xRandomness method of the default [sqlite3_vfs] object.
-** ^If the previous call to this routine had an N of 1 or more then
-** the pseudo-randomness is generated
+** call had N less than one or a NULL pointer for P, then the PRNG is
+** seeded using randomness obtained from the xRandomness method of
+** the default [sqlite3_vfs] object.
+** ^If the previous call to this routine had an N of 1 or more and a
+** non-NULL P then the pseudo-randomness is generated
** internally and without recourse to the [sqlite3_vfs] xRandomness
** method.
*/
@@ -4272,9 +4356,9 @@ SQLITE_API int sqlite3_create_function_v2(
** These constant define integer codes that represent the various
** text encodings supported by SQLite.
*/
-#define SQLITE_UTF8 1
-#define SQLITE_UTF16LE 2
-#define SQLITE_UTF16BE 3
+#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */
+#define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */
+#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */
#define SQLITE_UTF16 4 /* Use native byte order */
#define SQLITE_ANY 5 /* Deprecated */
#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */
@@ -4623,7 +4707,8 @@ typedef void (*sqlite3_destructor_type)(void*);
** the [sqlite3_context] pointer, the results are undefined.
*/
SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*));
-SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*,sqlite3_uint64,void(*)(void*));
+SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*,
+ sqlite3_uint64,void(*)(void*));
SQLITE_API void sqlite3_result_double(sqlite3_context*, double);
SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int);
SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int);
@@ -5255,20 +5340,27 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
/*
** CAPI3REF: Extract Metadata About A Column Of A Table
**
-** ^This routine returns metadata about a specific column of a specific
-** database table accessible using the [database connection] handle
-** passed as the first function argument.
+** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns
+** information about column C of table T in database D
+** on [database connection] X.)^ ^The sqlite3_table_column_metadata()
+** interface returns SQLITE_OK and fills in the non-NULL pointers in
+** the final five arguments with appropriate values if the specified
+** column exists. ^The sqlite3_table_column_metadata() interface returns
+** SQLITE_ERROR and if the specified column does not exist.
+** ^If the column-name parameter to sqlite3_table_column_metadata() is a
+** NULL pointer, then this routine simply checks for the existance of the
+** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it
+** does not.
**
** ^The column is identified by the second, third and fourth parameters to
-** this function. ^The second parameter is either the name of the database
+** this function. ^(The second parameter is either the name of the database
** (i.e. "main", "temp", or an attached database) containing the specified
-** table or NULL. ^If it is NULL, then all attached databases are searched
+** table or NULL.)^ ^If it is NULL, then all attached databases are searched
** for the table using the same algorithm used by the database engine to
** resolve unqualified table references.
**
** ^The third and fourth parameters to this function are the table and column
-** name of the desired column, respectively. Neither of these parameters
-** may be NULL.
+** name of the desired column, respectively.
**
** ^Metadata is returned by writing to the memory locations passed as the 5th
** and subsequent parameters to this function. ^Any of these arguments may be
@@ -5287,16 +5379,17 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
** )^
**
** ^The memory pointed to by the character pointers returned for the
-** declaration type and collation sequence is valid only until the next
+** declaration type and collation sequence is valid until the next
** call to any SQLite API function.
**
** ^If the specified table is actually a view, an [error code] is returned.
**
-** ^If the specified column is "rowid", "oid" or "_rowid_" and an
+** ^If the specified column is "rowid", "oid" or "_rowid_" and the table
+** is not a [WITHOUT ROWID] table and an
** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output
** parameters are set for the explicitly declared column. ^(If there is no
-** explicitly declared [INTEGER PRIMARY KEY] column, then the output
-** parameters are set as follows:
+** [INTEGER PRIMARY KEY] column, then the outputs
+** for the [rowid] are set as follows:
**
**
** data type: "INTEGER"
@@ -5306,13 +5399,9 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
** auto increment: 0
**
)^
**
-** ^(This function may load one or more schemas from database files. If an
-** error occurs during this process, or if the requested table or column
-** cannot be found, an [error code] is returned and an error message left
-** in the [database connection] (to be retrieved using sqlite3_errmsg()).)^
-**
-** ^This API is only available if the library was compiled with the
-** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined.
+** ^This function causes all database schemas to be read from disk and
+** parsed, if that has not already been done, and returns an error if
+** any errors are encountered while loading the schema.
*/
SQLITE_API int sqlite3_table_column_metadata(
sqlite3 *db, /* Connection handle */
@@ -5765,26 +5854,42 @@ typedef struct sqlite3_blob sqlite3_blob;
** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow;
** )^
**
+** ^(Parameter zDb is not the filename that contains the database, but
+** rather the symbolic name of the database. For attached databases, this is
+** the name that appears after the AS keyword in the [ATTACH] statement.
+** For the main database file, the database name is "main". For TEMP
+** tables, the database name is "temp".)^
+**
** ^If the flags parameter is non-zero, then the BLOB is opened for read
-** and write access. ^If it is zero, the BLOB is opened for read access.
-** ^It is not possible to open a column that is part of an index or primary
-** key for writing. ^If [foreign key constraints] are enabled, it is
-** not possible to open a column that is part of a [child key] for writing.
+** and write access. ^If the flags parameter is zero, the BLOB is opened for
+** read-only access.
**
-** ^Note that the database name is not the filename that contains
-** the database but rather the symbolic name of the database that
-** appears after the AS keyword when the database is connected using [ATTACH].
-** ^For the main database file, the database name is "main".
-** ^For TEMP tables, the database name is "temp".
+** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored
+** in *ppBlob. Otherwise an [error code] is returned and, unless the error
+** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
+** the API is not misused, it is always safe to call [sqlite3_blob_close()]
+** on *ppBlob after this function it returns.
+**
+** This function fails with SQLITE_ERROR if any of the following are true:
+**
+**
^(Database zDb does not exist)^,
+**
^(Table zTable does not exist within database zDb)^,
+**
^(Table zTable is a WITHOUT ROWID table)^,
+**
^(Column zColumn does not exist)^,
+**
^(Row iRow is not present in the table)^,
+**
^(The specified column of row iRow contains a value that is not
+** a TEXT or BLOB value)^,
+**
^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE
+** constraint and the blob is being opened for read/write access)^,
+**
^([foreign key constraints | Foreign key constraints] are enabled,
+** column zColumn is part of a [child key] definition and the blob is
+** being opened for read/write access)^.
+**
+**
+** ^Unless it returns SQLITE_MISUSE, this function sets the
+** [database connection] error code and message accessible via
+** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions.
**
-** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is written
-** to *ppBlob. Otherwise an [error code] is returned and *ppBlob is set
-** to be a null pointer.)^
-** ^This function sets the [database connection] error code and message
-** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()] and related
-** functions. ^Note that the *ppBlob variable is always initialized in a
-** way that makes it safe to invoke [sqlite3_blob_close()] on *ppBlob
-** regardless of the success or failure of this routine.
**
** ^(If the row that a BLOB handle points to is modified by an
** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects
@@ -5802,13 +5907,9 @@ typedef struct sqlite3_blob sqlite3_blob;
** interface. Use the [UPDATE] SQL command to change the size of a
** blob.
**
-** ^The [sqlite3_blob_open()] interface will fail for a [WITHOUT ROWID]
-** table. Incremental BLOB I/O is not possible on [WITHOUT ROWID] tables.
-**
** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces
-** and the built-in [zeroblob] SQL function can be used, if desired,
-** to create an empty, zero-filled blob in which to read or write using
-** this interface.
+** and the built-in [zeroblob] SQL function may be used to create a
+** zero-filled blob to read or write using the incremental-blob interface.
**
** To avoid a resource leak, every open [BLOB handle] should eventually
** be released by a call to [sqlite3_blob_close()].
@@ -5850,24 +5951,22 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_i
/*
** CAPI3REF: Close A BLOB Handle
**
-** ^Closes an open [BLOB handle].
+** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed
+** unconditionally. Even if this routine returns an error code, the
+** handle is still closed.)^
**
-** ^Closing a BLOB shall cause the current transaction to commit
-** if there are no other BLOBs, no pending prepared statements, and the
-** database connection is in [autocommit mode].
-** ^If any writes were made to the BLOB, they might be held in cache
-** until the close operation if they will fit.
+** ^If the blob handle being closed was opened for read-write access, and if
+** the database is in auto-commit mode and there are no other open read-write
+** blob handles or active write statements, the current transaction is
+** committed. ^If an error occurs while committing the transaction, an error
+** code is returned and the transaction rolled back.
**
-** ^(Closing the BLOB often forces the changes
-** out to disk and so if any I/O errors occur, they will likely occur
-** at the time when the BLOB is closed. Any errors that occur during
-** closing are reported as a non-zero return value.)^
-**
-** ^(The BLOB is closed unconditionally. Even if this routine returns
-** an error code, the BLOB is still closed.)^
-**
-** ^Calling this routine with a null pointer (such as would be returned
-** by a failed call to [sqlite3_blob_open()]) is a harmless no-op.
+** Calling this function with an argument that is not a NULL pointer or an
+** open blob handle results in undefined behaviour. ^Calling this routine
+** with a null pointer (such as would be returned by a failed call to
+** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function
+** is passed a valid open blob handle, the values returned by the
+** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning.
*/
SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
@@ -5917,21 +6016,27 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
/*
** CAPI3REF: Write Data Into A BLOB Incrementally
**
-** ^This function is used to write data into an open [BLOB handle] from a
-** caller-supplied buffer. ^N bytes of data are copied from the buffer Z
-** into the open BLOB, starting at offset iOffset.
+** ^(This function is used to write data into an open [BLOB handle] from a
+** caller-supplied buffer. N bytes of data are copied from the buffer Z
+** into the open BLOB, starting at offset iOffset.)^
+**
+** ^(On success, sqlite3_blob_write() returns SQLITE_OK.
+** Otherwise, an [error code] or an [extended error code] is returned.)^
+** ^Unless SQLITE_MISUSE is returned, this function sets the
+** [database connection] error code and message accessible via
+** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions.
**
** ^If the [BLOB handle] passed as the first argument was not opened for
** writing (the flags parameter to [sqlite3_blob_open()] was zero),
** this function returns [SQLITE_READONLY].
**
-** ^This function may only modify the contents of the BLOB; it is
+** This function may only modify the contents of the BLOB; it is
** not possible to increase the size of a BLOB using this API.
** ^If offset iOffset is less than N bytes from the end of the BLOB,
-** [SQLITE_ERROR] is returned and no data is written. ^If N is
-** less than zero [SQLITE_ERROR] is returned and no data is written.
-** The size of the BLOB (and hence the maximum value of N+iOffset)
-** can be determined using the [sqlite3_blob_bytes()] interface.
+** [SQLITE_ERROR] is returned and no data is written. The size of the
+** BLOB (and hence the maximum value of N+iOffset) can be determined
+** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less
+** than zero [SQLITE_ERROR] is returned and no data is written.
**
** ^An attempt to write to an expired [BLOB handle] fails with an
** error code of [SQLITE_ABORT]. ^Writes to the BLOB that occurred
@@ -5940,9 +6045,6 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
** have been overwritten by the statement that expired the BLOB handle
** or by other independent statements.
**
-** ^(On success, sqlite3_blob_write() returns SQLITE_OK.
-** Otherwise, an [error code] or an [extended error code] is returned.)^
-**
** This routine only works on a [BLOB handle] which has been created
** by a prior successful call to [sqlite3_blob_open()] and which has not
** been closed by [sqlite3_blob_close()]. Passing any other pointer in
@@ -5995,34 +6097,34 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
**
** The SQLite source code contains multiple implementations
** of these mutex routines. An appropriate implementation
-** is selected automatically at compile-time. ^(The following
+** is selected automatically at compile-time. The following
** implementations are available in the SQLite core:
**
**
**
SQLITE_MUTEX_PTHREADS
**
SQLITE_MUTEX_W32
**
SQLITE_MUTEX_NOOP
-**
)^
+**
**
-** ^The SQLITE_MUTEX_NOOP implementation is a set of routines
+** The SQLITE_MUTEX_NOOP implementation is a set of routines
** that does no real locking and is appropriate for use in
-** a single-threaded application. ^The SQLITE_MUTEX_PTHREADS and
+** a single-threaded application. The SQLITE_MUTEX_PTHREADS and
** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix
** and Windows.
**
-** ^(If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor
+** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor
** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex
** implementation is included with the library. In this case the
** application must supply a custom mutex implementation using the
** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function
** before calling sqlite3_initialize() or any other public sqlite3_
-** function that calls sqlite3_initialize().)^
+** function that calls sqlite3_initialize().
**
** ^The sqlite3_mutex_alloc() routine allocates a new
-** mutex and returns a pointer to it. ^If it returns NULL
-** that means that a mutex could not be allocated. ^SQLite
-** will unwind its stack and return an error. ^(The argument
-** to sqlite3_mutex_alloc() is one of these integer constants:
+** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
+** routine returns NULL if it is unable to allocate the requested
+** mutex. The argument to sqlite3_mutex_alloc() must one of these
+** integer constants:
**
**
**
SQLITE_MUTEX_FAST
@@ -6035,7 +6137,8 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
**
SQLITE_MUTEX_STATIC_PMEM
**
SQLITE_MUTEX_STATIC_APP1
**
SQLITE_MUTEX_STATIC_APP2
-**
)^
+**
SQLITE_MUTEX_STATIC_APP3
+**
**
** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE)
** cause sqlite3_mutex_alloc() to create
@@ -6043,14 +6146,14 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** is used but not necessarily so when SQLITE_MUTEX_FAST is used.
** The mutex implementation does not need to make a distinction
** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does
-** not want to. ^SQLite will only request a recursive mutex in
-** cases where it really needs one. ^If a faster non-recursive mutex
+** not want to. SQLite will only request a recursive mutex in
+** cases where it really needs one. If a faster non-recursive mutex
** implementation is available on the host platform, the mutex subsystem
** might return such a mutex in response to SQLITE_MUTEX_FAST.
**
** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other
** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return
-** a pointer to a static preexisting mutex. ^Six static mutexes are
+** a pointer to a static preexisting mutex. ^Nine static mutexes are
** used by the current version of SQLite. Future versions of SQLite
** may add additional static mutexes. Static mutexes are for internal
** use by SQLite only. Applications that use SQLite mutexes should
@@ -6059,16 +6162,13 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
**
** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
-** returns a different mutex on every call. ^But for the static
+** returns a different mutex on every call. ^For the static
** mutex types, the same mutex is returned on every call that has
** the same type number.
**
** ^The sqlite3_mutex_free() routine deallocates a previously
-** allocated dynamic mutex. ^SQLite is careful to deallocate every
-** dynamic mutex that it allocates. The dynamic mutexes must not be in
-** use when they are deallocated. Attempting to deallocate a static
-** mutex results in undefined behavior. ^SQLite never deallocates
-** a static mutex.
+** allocated dynamic mutex. Attempting to deallocate a static
+** mutex results in undefined behavior.
**
** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
** to enter a mutex. ^If another thread is already within the mutex,
@@ -6076,23 +6176,21 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK]
** upon successful entry. ^(Mutexes created using
** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread.
-** In such cases the,
+** In such cases, the
** mutex must be exited an equal number of times before another thread
-** can enter.)^ ^(If the same thread tries to enter any other
-** kind of mutex more than once, the behavior is undefined.
-** SQLite will never exhibit
-** such behavior in its own use of mutexes.)^
+** can enter.)^ If the same thread tries to enter any mutex other
+** than an SQLITE_MUTEX_RECURSIVE more than once, the behavior is undefined.
**
** ^(Some systems (for example, Windows 95) do not support the operation
** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try()
-** will always return SQLITE_BUSY. The SQLite core only ever uses
-** sqlite3_mutex_try() as an optimization so this is acceptable behavior.)^
+** will always return SQLITE_BUSY. The SQLite core only ever uses
+** sqlite3_mutex_try() as an optimization so this is acceptable
+** behavior.)^
**
** ^The sqlite3_mutex_leave() routine exits a mutex that was
-** previously entered by the same thread. ^(The behavior
+** previously entered by the same thread. The behavior
** is undefined if the mutex is not currently entered by the
-** calling thread or is not currently allocated. SQLite will
-** never do either.)^
+** calling thread or is not currently allocated.
**
** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or
** sqlite3_mutex_leave() is a NULL pointer, then all three routines
@@ -6113,9 +6211,9 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
** used to allocate and use mutexes.
**
** Usually, the default mutex implementations provided by SQLite are
-** sufficient, however the user has the option of substituting a custom
+** sufficient, however the application has the option of substituting a custom
** implementation for specialized deployments or systems for which SQLite
-** does not provide a suitable implementation. In this case, the user
+** does not provide a suitable implementation. In this case, the application
** creates and populates an instance of this structure to pass
** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option.
** Additionally, an instance of this structure can be used as an
@@ -6156,13 +6254,13 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
** (i.e. it is acceptable to provide an implementation that segfaults if
** it is passed a NULL pointer).
**
-** The xMutexInit() method must be threadsafe. ^It must be harmless to
+** The xMutexInit() method must be threadsafe. It must be harmless to
** invoke xMutexInit() multiple times within the same process and without
** intervening calls to xMutexEnd(). Second and subsequent calls to
** xMutexInit() must be no-ops.
**
-** ^xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()]
-** and its associates). ^Similarly, xMutexAlloc() must not use SQLite memory
+** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()]
+** and its associates). Similarly, xMutexAlloc() must not use SQLite memory
** allocation for a static mutex. ^However xMutexAlloc() may use SQLite
** memory allocation for a fast or recursive mutex.
**
@@ -6188,29 +6286,29 @@ struct sqlite3_mutex_methods {
** CAPI3REF: Mutex Verification Routines
**
** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines
-** are intended for use inside assert() statements. ^The SQLite core
+** are intended for use inside assert() statements. The SQLite core
** never uses these routines except inside an assert() and applications
-** are advised to follow the lead of the core. ^The SQLite core only
+** are advised to follow the lead of the core. The SQLite core only
** provides implementations for these routines when it is compiled
-** with the SQLITE_DEBUG flag. ^External mutex implementations
+** with the SQLITE_DEBUG flag. External mutex implementations
** are only required to provide these routines if SQLITE_DEBUG is
** defined and if NDEBUG is not defined.
**
-** ^These routines should return true if the mutex in their argument
+** These routines should return true if the mutex in their argument
** is held or not held, respectively, by the calling thread.
**
-** ^The implementation is not required to provide versions of these
+** The implementation is not required to provide versions of these
** routines that actually work. If the implementation does not provide working
** versions of these routines, it should at least provide stubs that always
** return true so that one does not get spurious assertion failures.
**
-** ^If the argument to sqlite3_mutex_held() is a NULL pointer then
+** If the argument to sqlite3_mutex_held() is a NULL pointer then
** the routine should return 1. This seems counter-intuitive since
** clearly the mutex cannot be held if it does not exist. But
** the reason the mutex does not exist is because the build is not
** using mutexes. And we do not want the assert() containing the
** call to sqlite3_mutex_held() to fail, so a non-zero return is
-** the appropriate thing to do. ^The sqlite3_mutex_notheld()
+** the appropriate thing to do. The sqlite3_mutex_notheld()
** interface should also return 1 when given a NULL pointer.
*/
#ifndef NDEBUG
@@ -6943,6 +7041,10 @@ typedef struct sqlite3_backup sqlite3_backup;
** must be different or else sqlite3_backup_init(D,N,S,M) will fail with
** an error.
**
+** ^A call to sqlite3_backup_init() will fail, returning SQLITE_ERROR, if
+** there is already a read or read-write transaction open on the
+** destination database.
+**
** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is
** returned and an error code and error message are stored in the
** destination [database connection] D.
@@ -7266,12 +7368,10 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** CAPI3REF: Write-Ahead Log Commit Hook
**
** ^The [sqlite3_wal_hook()] function is used to register a callback that
-** will be invoked each time a database connection commits data to a
-** [write-ahead log] (i.e. whenever a transaction is committed in
-** [journal_mode | journal_mode=WAL mode]).
+** is invoked each time data is committed to a database in wal mode.
**
-** ^The callback is invoked by SQLite after the commit has taken place and
-** the associated write-lock on the database released, so the implementation
+** ^(The callback is invoked by SQLite after the commit has taken place and
+** the associated write-lock on the database released)^, so the implementation
** may read, write or [checkpoint] the database as required.
**
** ^The first parameter passed to the callback function when it is invoked
@@ -7336,97 +7436,114 @@ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
/*
** CAPI3REF: Checkpoint a database
**
-** ^The [sqlite3_wal_checkpoint(D,X)] interface causes database named X
-** on [database connection] D to be [checkpointed]. ^If X is NULL or an
-** empty string, then a checkpoint is run on all databases of
-** connection D. ^If the database connection D is not in
-** [WAL | write-ahead log mode] then this interface is a harmless no-op.
-** ^The [sqlite3_wal_checkpoint(D,X)] interface initiates a
-** [sqlite3_wal_checkpoint_v2|PASSIVE] checkpoint.
-** Use the [sqlite3_wal_checkpoint_v2()] interface to get a FULL
-** or RESET checkpoint.
+** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to
+** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^
**
-** ^The [wal_checkpoint pragma] can be used to invoke this interface
-** from SQL. ^The [sqlite3_wal_autocheckpoint()] interface and the
-** [wal_autocheckpoint pragma] can be used to cause this interface to be
-** run whenever the WAL reaches a certain size threshold.
+** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the
+** [write-ahead log] for database X on [database connection] D to be
+** transferred into the database file and for the write-ahead log to
+** be reset. See the [checkpointing] documentation for addition
+** information.
**
-** See also: [sqlite3_wal_checkpoint_v2()]
+** This interface used to be the only way to cause a checkpoint to
+** occur. But then the newer and more powerful [sqlite3_wal_checkpoint_v2()]
+** interface was added. This interface is retained for backwards
+** compatibility and as a convenience for applications that need to manually
+** start a callback but which do not need the full power (and corresponding
+** complication) of [sqlite3_wal_checkpoint_v2()].
*/
SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
/*
** CAPI3REF: Checkpoint a database
**
-** Run a checkpoint operation on WAL database zDb attached to database
-** handle db. The specific operation is determined by the value of the
-** eMode parameter:
+** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint
+** operation on database X of [database connection] D in mode M. Status
+** information is written back into integers pointed to by L and C.)^
+** ^(The M parameter must be a valid [checkpoint mode]:)^
**
**
**
SQLITE_CHECKPOINT_PASSIVE
-** Checkpoint as many frames as possible without waiting for any database
-** readers or writers to finish. Sync the db file if all frames in the log
-** are checkpointed. This mode is the same as calling
-** sqlite3_wal_checkpoint(). The [sqlite3_busy_handler|busy-handler callback]
-** is never invoked.
+** ^Checkpoint as many frames as possible without waiting for any database
+** readers or writers to finish, then sync the database file if all frames
+** in the log were checkpointed. ^The [busy-handler callback]
+** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode.
+** ^On the other hand, passive mode might leave the checkpoint unfinished
+** if there are concurrent readers or writers.
**
**
SQLITE_CHECKPOINT_FULL
-** This mode blocks (it invokes the
+** ^This mode blocks (it invokes the
** [sqlite3_busy_handler|busy-handler callback]) until there is no
** database writer and all readers are reading from the most recent database
-** snapshot. It then checkpoints all frames in the log file and syncs the
-** database file. This call blocks database writers while it is running,
-** but not database readers.
+** snapshot. ^It then checkpoints all frames in the log file and syncs the
+** database file. ^This mode blocks new database writers while it is pending,
+** but new database readers are allowed to continue unimpeded.
**
**
SQLITE_CHECKPOINT_RESTART
-** This mode works the same way as SQLITE_CHECKPOINT_FULL, except after
-** checkpointing the log file it blocks (calls the
-** [sqlite3_busy_handler|busy-handler callback])
-** until all readers are reading from the database file only. This ensures
-** that the next client to write to the database file restarts the log file
-** from the beginning. This call blocks database writers while it is running,
-** but not database readers.
+** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition
+** that after checkpointing the log file it blocks (calls the
+** [busy-handler callback])
+** until all readers are reading from the database file only. ^This ensures
+** that the next writer will restart the log file from the beginning.
+** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new
+** database writer attempts while it is pending, but does not impede readers.
+**
+**
SQLITE_CHECKPOINT_TRUNCATE
+** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the
+** addition that it also truncates the log file to zero bytes just prior
+** to a successful return.
**
**
-** If pnLog is not NULL, then *pnLog is set to the total number of frames in
-** the log file before returning. If pnCkpt is not NULL, then *pnCkpt is set to
-** the total number of checkpointed frames (including any that were already
-** checkpointed when this function is called). *pnLog and *pnCkpt may be
-** populated even if sqlite3_wal_checkpoint_v2() returns other than SQLITE_OK.
-** If no values are available because of an error, they are both set to -1
-** before returning to communicate this to the caller.
+** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in
+** the log file or to -1 if the checkpoint could not run because
+** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not
+** NULL,then *pnCkpt is set to the total number of checkpointed frames in the
+** log file (including any that were already checkpointed before the function
+** was called) or to -1 if the checkpoint could not run due to an error or
+** because the database is not in WAL mode. ^Note that upon successful
+** completion of an SQLITE_CHECKPOINT_TRUNCATE, the log file will have been
+** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero.
**
-** All calls obtain an exclusive "checkpoint" lock on the database file. If
+** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If
** any other process is running a checkpoint operation at the same time, the
-** lock cannot be obtained and SQLITE_BUSY is returned. Even if there is a
+** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a
** busy-handler configured, it will not be invoked in this case.
**
-** The SQLITE_CHECKPOINT_FULL and RESTART modes also obtain the exclusive
-** "writer" lock on the database file. If the writer lock cannot be obtained
-** immediately, and a busy-handler is configured, it is invoked and the writer
-** lock retried until either the busy-handler returns 0 or the lock is
-** successfully obtained. The busy-handler is also invoked while waiting for
-** database readers as described above. If the busy-handler returns 0 before
+** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the
+** exclusive "writer" lock on the database file. ^If the writer lock cannot be
+** obtained immediately, and a busy-handler is configured, it is invoked and
+** the writer lock retried until either the busy-handler returns 0 or the lock
+** is successfully obtained. ^The busy-handler is also invoked while waiting for
+** database readers as described above. ^If the busy-handler returns 0 before
** the writer lock is obtained or while waiting for database readers, the
** checkpoint operation proceeds from that point in the same way as
** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible
-** without blocking any further. SQLITE_BUSY is returned in this case.
+** without blocking any further. ^SQLITE_BUSY is returned in this case.
**
-** If parameter zDb is NULL or points to a zero length string, then the
-** specified operation is attempted on all WAL databases. In this case the
-** values written to output parameters *pnLog and *pnCkpt are undefined. If
+** ^If parameter zDb is NULL or points to a zero length string, then the
+** specified operation is attempted on all WAL databases [attached] to
+** [database connection] db. In this case the
+** values written to output parameters *pnLog and *pnCkpt are undefined. ^If
** an SQLITE_BUSY error is encountered when processing one or more of the
** attached WAL databases, the operation is still attempted on any remaining
-** attached databases and SQLITE_BUSY is returned to the caller. If any other
+** attached databases and SQLITE_BUSY is returned at the end. ^If any other
** error occurs while processing an attached database, processing is abandoned
-** and the error code returned to the caller immediately. If no error
+** and the error code is returned to the caller immediately. ^If no error
** (SQLITE_BUSY or otherwise) is encountered while processing the attached
** databases, SQLITE_OK is returned.
**
-** If database zDb is the name of an attached database that is not in WAL
-** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. If
+** ^If database zDb is the name of an attached database that is not in WAL
+** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. ^If
** zDb is not NULL (or a zero length string) and is not the name of any
** attached database, SQLITE_ERROR is returned to the caller.
+**
+** ^Unless it returns SQLITE_MISUSE,
+** the sqlite3_wal_checkpoint_v2() interface
+** sets the error information that is queried by
+** [sqlite3_errcode()] and [sqlite3_errmsg()].
+**
+** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface
+** from SQL.
*/
SQLITE_API int sqlite3_wal_checkpoint_v2(
sqlite3 *db, /* Database handle */
@@ -7437,16 +7554,18 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
);
/*
-** CAPI3REF: Checkpoint operation parameters
+** CAPI3REF: Checkpoint Mode Values
+** KEYWORDS: {checkpoint mode}
**
-** These constants can be used as the 3rd parameter to
-** [sqlite3_wal_checkpoint_v2()]. See the [sqlite3_wal_checkpoint_v2()]
-** documentation for additional information about the meaning and use of
-** each of these values.
+** These constants define all valid values for the "checkpoint mode" passed
+** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface.
+** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the
+** meaning of each of these checkpoint modes.
*/
-#define SQLITE_CHECKPOINT_PASSIVE 0
-#define SQLITE_CHECKPOINT_FULL 1
-#define SQLITE_CHECKPOINT_RESTART 2
+#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
+#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */
+#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */
+#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */
/*
** CAPI3REF: Virtual Table Interface Configuration
@@ -7535,6 +7654,106 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
/* #define SQLITE_ABORT 4 // Also an error code */
#define SQLITE_REPLACE 5
+/*
+** CAPI3REF: Prepared Statement Scan Status Opcodes
+** KEYWORDS: {scanstatus options}
+**
+** The following constants can be used for the T parameter to the
+** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a
+** different metric for sqlite3_stmt_scanstatus() to return.
+**
+** When the value returned to V is a string, space to hold that string is
+** managed by the prepared statement S and will be automatically freed when
+** S is finalized.
+**
+**
+** [[SQLITE_SCANSTAT_NLOOP]]
SQLITE_SCANSTAT_NLOOP
+**
^The [sqlite3_int64] variable pointed to by the T parameter will be
+** set to the total number of times that the X-th loop has run.
+**
+** [[SQLITE_SCANSTAT_NVISIT]]
SQLITE_SCANSTAT_NVISIT
+**
^The [sqlite3_int64] variable pointed to by the T parameter will be set
+** to the total number of rows examined by all iterations of the X-th loop.
+**
+** [[SQLITE_SCANSTAT_EST]]
SQLITE_SCANSTAT_EST
+**
^The "double" variable pointed to by the T parameter will be set to the
+** query planner's estimate for the average number of rows output from each
+** iteration of the X-th loop. If the query planner's estimates was accurate,
+** then this value will approximate the quotient NVISIT/NLOOP and the
+** product of this value for all prior loops with the same SELECTID will
+** be the NLOOP value for the current loop.
+**
+** [[SQLITE_SCANSTAT_NAME]]
SQLITE_SCANSTAT_NAME
+**
^The "const char *" variable pointed to by the T parameter will be set
+** to a zero-terminated UTF-8 string containing the name of the index or table
+** used for the X-th loop.
+**
+** [[SQLITE_SCANSTAT_EXPLAIN]]
SQLITE_SCANSTAT_EXPLAIN
+**
^The "const char *" variable pointed to by the T parameter will be set
+** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
+** description for the X-th loop.
+**
+** [[SQLITE_SCANSTAT_SELECTID]]
SQLITE_SCANSTAT_SELECT
+**
^The "int" variable pointed to by the T parameter will be set to the
+** "select-id" for the X-th loop. The select-id identifies which query or
+** subquery the loop is part of. The main query has a select-id of zero.
+** The select-id is the same value as is output in the first column
+** of an [EXPLAIN QUERY PLAN] query.
+**
+*/
+#define SQLITE_SCANSTAT_NLOOP 0
+#define SQLITE_SCANSTAT_NVISIT 1
+#define SQLITE_SCANSTAT_EST 2
+#define SQLITE_SCANSTAT_NAME 3
+#define SQLITE_SCANSTAT_EXPLAIN 4
+#define SQLITE_SCANSTAT_SELECTID 5
+
+/*
+** CAPI3REF: Prepared Statement Scan Status
+**
+** This interface returns information about the predicted and measured
+** performance for pStmt. Advanced applications can use this
+** interface to compare the predicted and the measured performance and
+** issue warnings and/or rerun [ANALYZE] if discrepancies are found.
+**
+** Since this interface is expected to be rarely used, it is only
+** available if SQLite is compiled using the [SQLITE_ENABLE_STMT_SCANSTATUS]
+** compile-time option.
+**
+** The "iScanStatusOp" parameter determines which status information to return.
+** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior
+** of this interface is undefined.
+** ^The requested measurement is written into a variable pointed to by
+** the "pOut" parameter.
+** Parameter "idx" identifies the specific loop to retrieve statistics for.
+** Loops are numbered starting from zero. ^If idx is out of range - less than
+** zero or greater than or equal to the total number of loops used to implement
+** the statement - a non-zero value is returned and the variable that pOut
+** points to is unchanged.
+**
+** ^Statistics might not be available for all loops in all statements. ^In cases
+** where there exist loops with no available statistics, this function behaves
+** as if the loop did not exist - it returns non-zero and leave the variable
+** that pOut points to unchanged.
+**
+** See also: [sqlite3_stmt_scanstatus_reset()]
+*/
+SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_stmt_scanstatus(
+ sqlite3_stmt *pStmt, /* Prepared statement for which info desired */
+ int idx, /* Index of loop to report on */
+ int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */
+ void *pOut /* Result written here */
+);
+
+/*
+** CAPI3REF: Zero Scan-Status Counters
+**
+** ^Zero all [sqlite3_stmt_scanstatus()] related event counters.
+**
+** This API is only available if the library is built with pre-processor
+** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined.
+*/
+SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
/*
@@ -7980,10 +8199,9 @@ struct sqlite3_rtree_query_info {
#endif
/*
-** The SQLITE_DEFAULT_MEMSTATUS macro must be defined as either 0 or 1.
-** It determines whether or not the features related to
-** SQLITE_CONFIG_MEMSTATUS are available by default or not. This value can
-** be overridden at runtime using the sqlite3_config() API.
+** EVIDENCE-OF: R-25715-37072 Memory allocation statistics are enabled by
+** default unless SQLite is compiled with SQLITE_DEFAULT_MEMSTATUS=0 in
+** which case memory allocation statistics are disabled by default.
*/
#if !defined(SQLITE_DEFAULT_MEMSTATUS)
# define SQLITE_DEFAULT_MEMSTATUS 1
@@ -8613,7 +8831,7 @@ typedef INT8_TYPE i8; /* 1-byte signed integer */
** gives a possible range of values of approximately 1.0e986 to 1e-986.
** But the allowed values are "grainy". Not every value is representable.
** For example, quantities 16 and 17 are both represented by a LogEst
-** of 40. However, since LogEst quantaties are suppose to be estimates,
+** of 40. However, since LogEst quantities are suppose to be estimates,
** not exact values, this imprecision is not a problem.
**
** "LogEst" is short for "Logarithmic Estimate".
@@ -8949,7 +9167,7 @@ typedef struct With With;
/* TODO: This definition is just included so other modules compile. It
** needs to be revisited.
*/
-#define SQLITE_N_BTREE_META 10
+#define SQLITE_N_BTREE_META 16
/*
** If defined as non-zero, auto-vacuum is enabled by default. Otherwise
@@ -9064,6 +9282,11 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p);
** For example, the free-page-count field is located at byte offset 36 of
** the database file header. The incr-vacuum-flag field is located at
** byte offset 64 (== 36+4*7).
+**
+** The BTREE_DATA_VERSION value is not really a value stored in the header.
+** It is a read-only number computed by the pager. But we merge it with
+** the header value access routines since its access pattern is the same.
+** Call it a "virtual meta value".
*/
#define BTREE_FREE_PAGE_COUNT 0
#define BTREE_SCHEMA_VERSION 1
@@ -9074,6 +9297,7 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p);
#define BTREE_USER_VERSION 6
#define BTREE_INCR_VACUUM 7
#define BTREE_APPLICATION_ID 8
+#define BTREE_DATA_VERSION 15 /* A virtual meta-value */
/*
** Values that may be OR'd together to form the second argument of an
@@ -9126,6 +9350,7 @@ SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *);
SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBt, int iVersion);
SQLITE_PRIVATE void sqlite3BtreeCursorHints(BtCursor *, unsigned int mask);
SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *pBt);
+SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void);
#ifndef NDEBUG
SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor*);
@@ -9668,6 +9893,12 @@ SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe*,int);
# define VDBE_OFFSET_LINENO(x) 0
#endif
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+SQLITE_PRIVATE void sqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const char*);
+#else
+# define sqlite3VdbeScanStatus(a,b,c,d,e)
+#endif
+
#endif
/************** End of vdbe.h ************************************************/
@@ -9848,6 +10079,7 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager);
/* Functions used to query pager state and configuration. */
SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager*);
+SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager*);
SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*);
SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager*);
SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager*, int);
@@ -9864,6 +10096,8 @@ SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *);
/* Functions used to truncate the database file. */
SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno);
+SQLITE_PRIVATE void sqlite3PagerRekey(DbPage*, Pgno, u16);
+
#if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL)
SQLITE_PRIVATE void *sqlite3PagerCodec(DbPage *);
#endif
@@ -10051,6 +10285,10 @@ SQLITE_PRIVATE void sqlite3PcacheStats(int*,int*,int*,int*);
SQLITE_PRIVATE void sqlite3PCacheSetDefault(void);
+/* Return the header size */
+SQLITE_PRIVATE int sqlite3HeaderSizePcache(void);
+SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void);
+
#endif /* _PCACHE_H_ */
/************** End of pcache.h **********************************************/
@@ -10583,6 +10821,7 @@ struct sqlite3 {
int errCode; /* Most recent error code (SQLITE_*) */
int errMask; /* & result codes with this before returning */
u16 dbOptFlags; /* Flags to enable/disable optimizations */
+ u8 enc; /* Text encoding */
u8 autoCommit; /* The auto-commit flag. */
u8 temp_store; /* 1: file 2: memory 0: default */
u8 mallocFailed; /* True if we have seen a malloc failure */
@@ -10684,7 +10923,8 @@ struct sqlite3 {
/*
** A macro to discover the encoding of a database.
*/
-#define ENC(db) ((db)->aDb[0].pSchema->enc)
+#define SCHEMA_ENC(db) ((db)->aDb[0].pSchema->enc)
+#define ENC(db) ((db)->enc)
/*
** Possible values for the sqlite3.flags.
@@ -10737,7 +10977,7 @@ struct sqlite3 {
#define SQLITE_SubqCoroutine 0x0100 /* Evaluate subqueries as coroutines */
#define SQLITE_Transitive 0x0200 /* Transitive constraints */
#define SQLITE_OmitNoopJoin 0x0400 /* Omit unused tables in joins */
-#define SQLITE_Stat3 0x0800 /* Use the SQLITE_STAT3 table */
+#define SQLITE_Stat34 0x0800 /* Use STAT3 or STAT4 data */
#define SQLITE_AllOpts 0xffff /* All optimizations */
/*
@@ -11308,7 +11548,6 @@ struct Index {
u8 *aSortOrder; /* for each column: True==DESC, False==ASC */
char **azColl; /* Array of collation sequence names for index */
Expr *pPartIdxWhere; /* WHERE clause for partial indices */
- KeyInfo *pKeyInfo; /* A KeyInfo object suitable for this index */
int tnum; /* DB Page containing root of this index */
LogEst szIdxRow; /* Estimated average row size in bytes */
u16 nKeyCol; /* Number of columns forming the key */
@@ -11319,12 +11558,14 @@ struct Index {
unsigned uniqNotNull:1; /* True if UNIQUE and NOT NULL for all columns */
unsigned isResized:1; /* True if resizeIndexObject() has been called */
unsigned isCovering:1; /* True if this is a covering index */
+ unsigned noSkipScan:1; /* Do not try to use skip-scan if true */
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
int nSample; /* Number of elements in aSample[] */
int nSampleCol; /* Size of IndexSample.anEq[] and so on */
tRowcnt *aAvgEq; /* Average nEq values for keys not in aSample */
IndexSample *aSample; /* Samples of the left-most key */
- tRowcnt *aiRowEst; /* Non-logarithmic stat1 data for this table */
+ tRowcnt *aiRowEst; /* Non-logarithmic stat1 data for this index */
+ tRowcnt nRowEst0; /* Non-logarithmic number of rows in the index */
#endif
};
@@ -11522,7 +11763,7 @@ struct Expr {
int iTable; /* TK_COLUMN: cursor number of table holding column
** TK_REGISTER: register number
** TK_TRIGGER: 1 -> new, 0 -> old
- ** EP_Unlikely: 1000 times likelihood */
+ ** EP_Unlikely: 134217728 times likelihood */
ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid.
** TK_VARIABLE: variable number (always >= 1). */
i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */
@@ -11870,7 +12111,7 @@ struct Select {
#define SF_HasTypeInfo 0x0020 /* FROM subqueries have Table metadata */
#define SF_Compound 0x0040 /* Part of a compound query */
#define SF_Values 0x0080 /* Synthesized from VALUES clause */
- /* 0x0100 NOT USED */
+#define SF_AllValues 0x0100 /* All terms of compound are VALUES */
#define SF_NestedFrom 0x0200 /* Part of a parenthesized FROM clause */
#define SF_MaybeConvert 0x0400 /* Need convertCompoundSelectToSubquery() */
#define SF_Recursive 0x0800 /* The recursive part of a recursive CTE */
@@ -12360,6 +12601,7 @@ struct Sqlite3Config {
int nPage; /* Number of pages in pPage[] */
int mxParserStack; /* maximum depth of the parser stack */
int sharedCacheEnabled; /* true if shared-cache mode enabled */
+ u32 szPma; /* Maximum Sorter PMA size */
/* The above might be initialized to non-zero. The following need to always
** initially be zero, however. */
int isInit; /* True after initialization has finished */
@@ -12415,9 +12657,11 @@ struct Walker {
void (*xSelectCallback2)(Walker*,Select*);/* Second callback for SELECTs */
Parse *pParse; /* Parser context. */
int walkerDepth; /* Number of subqueries */
+ u8 eCode; /* A small processing code */
union { /* Extra data for callback */
NameContext *pNC; /* Naming context */
- int i; /* Integer value */
+ int n; /* A counter */
+ int iCur; /* A cursor number */
SrcList *pSrcList; /* FROM clause */
struct SrcCount *pSrcCount; /* Counting column references */
} u;
@@ -12495,7 +12739,7 @@ SQLITE_PRIVATE int sqlite3CantopenError(int);
** the SQLITE_ENABLE_FTS4 macro to serve as an alias for SQLITE_ENABLE_FTS3.
*/
#if defined(SQLITE_ENABLE_FTS4) && !defined(SQLITE_ENABLE_FTS3)
-# define SQLITE_ENABLE_FTS3
+# define SQLITE_ENABLE_FTS3 1
#endif
/*
@@ -12818,6 +13062,7 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3*);
SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr*);
SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*);
SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*, u8);
+SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int);
SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr*, int*);
SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*);
SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);
@@ -13279,7 +13524,7 @@ SQLITE_PRIVATE void sqlite3ParserTrace(FILE*, char *);
#ifdef SQLITE_ENABLE_IOTRACE
# define IOTRACE(A) if( sqlite3IoTrace ){ sqlite3IoTrace A; }
SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe*);
-SQLITE_PRIVATE void (*sqlite3IoTrace)(const char*,...);
+void (*sqlite3IoTrace)(const char*,...);
#else
# define IOTRACE(A)
# define sqlite3VdbeIOTraceSql(X)
@@ -13475,15 +13720,30 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = {
**
** EVIDENCE-OF: R-38799-08373 URI filenames can be enabled or disabled
** using the SQLITE_USE_URI=1 or SQLITE_USE_URI=0 compile-time options.
+**
+** EVIDENCE-OF: R-43642-56306 By default, URI handling is globally
+** disabled. The default value may be changed by compiling with the
+** SQLITE_USE_URI symbol defined.
*/
#ifndef SQLITE_USE_URI
# define SQLITE_USE_URI 0
#endif
+/* EVIDENCE-OF: R-38720-18127 The default setting is determined by the
+** SQLITE_ALLOW_COVERING_INDEX_SCAN compile-time option, or is "on" if
+** that compile-time option is omitted.
+*/
#ifndef SQLITE_ALLOW_COVERING_INDEX_SCAN
# define SQLITE_ALLOW_COVERING_INDEX_SCAN 1
#endif
+/* The minimum PMA size is set to this value multiplied by the database
+** page size in bytes.
+*/
+#ifndef SQLITE_SORTER_PMASZ
+# define SQLITE_SORTER_PMASZ 250
+#endif
+
/*
** The following singleton contains the global configuration for
** the SQLite library.
@@ -13514,6 +13774,7 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = {
0, /* nPage */
0, /* mxParserStack */
0, /* sharedCacheEnabled */
+ SQLITE_SORTER_PMASZ, /* szPma */
/* All the rest should always be initialized to zero */
0, /* isInit */
0, /* inProgress */
@@ -13569,8 +13830,8 @@ SQLITE_PRIVATE const Token sqlite3IntTokens[] = {
**
** IMPORTANT: Changing the pending byte to any value other than
** 0x40000000 results in an incompatible database file format!
-** Changing the pending byte during operating results in undefined
-** and dileterious behavior.
+** Changing the pending byte during operation will result in undefined
+** and incorrect behavior.
*/
#ifndef SQLITE_OMIT_WSD
SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000;
@@ -13620,88 +13881,91 @@ static const char * const azCompileOpt[] = {
#define CTIMEOPT_VAL_(opt) #opt
#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
-#ifdef SQLITE_32BIT_ROWID
+#if SQLITE_32BIT_ROWID
"32BIT_ROWID",
#endif
-#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC
+#if SQLITE_4_BYTE_ALIGNED_MALLOC
"4_BYTE_ALIGNED_MALLOC",
#endif
-#ifdef SQLITE_CASE_SENSITIVE_LIKE
+#if SQLITE_CASE_SENSITIVE_LIKE
"CASE_SENSITIVE_LIKE",
#endif
-#ifdef SQLITE_CHECK_PAGES
+#if SQLITE_CHECK_PAGES
"CHECK_PAGES",
#endif
-#ifdef SQLITE_COVERAGE_TEST
+#if SQLITE_COVERAGE_TEST
"COVERAGE_TEST",
#endif
-#ifdef SQLITE_DEBUG
+#if SQLITE_DEBUG
"DEBUG",
#endif
-#ifdef SQLITE_DEFAULT_LOCKING_MODE
+#if SQLITE_DEFAULT_LOCKING_MODE
"DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE),
#endif
#if defined(SQLITE_DEFAULT_MMAP_SIZE) && !defined(SQLITE_DEFAULT_MMAP_SIZE_xc)
"DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE),
#endif
-#ifdef SQLITE_DISABLE_DIRSYNC
+#if SQLITE_DISABLE_DIRSYNC
"DISABLE_DIRSYNC",
#endif
-#ifdef SQLITE_DISABLE_LFS
+#if SQLITE_DISABLE_LFS
"DISABLE_LFS",
#endif
-#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+#if SQLITE_ENABLE_API_ARMOR
+ "ENABLE_API_ARMOR",
+#endif
+#if SQLITE_ENABLE_ATOMIC_WRITE
"ENABLE_ATOMIC_WRITE",
#endif
-#ifdef SQLITE_ENABLE_CEROD
+#if SQLITE_ENABLE_CEROD
"ENABLE_CEROD",
#endif
-#ifdef SQLITE_ENABLE_COLUMN_METADATA
+#if SQLITE_ENABLE_COLUMN_METADATA
"ENABLE_COLUMN_METADATA",
#endif
-#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT
+#if SQLITE_ENABLE_EXPENSIVE_ASSERT
"ENABLE_EXPENSIVE_ASSERT",
#endif
-#ifdef SQLITE_ENABLE_FTS1
+#if SQLITE_ENABLE_FTS1
"ENABLE_FTS1",
#endif
-#ifdef SQLITE_ENABLE_FTS2
+#if SQLITE_ENABLE_FTS2
"ENABLE_FTS2",
#endif
-#ifdef SQLITE_ENABLE_FTS3
+#if SQLITE_ENABLE_FTS3
"ENABLE_FTS3",
#endif
-#ifdef SQLITE_ENABLE_FTS3_PARENTHESIS
+#if SQLITE_ENABLE_FTS3_PARENTHESIS
"ENABLE_FTS3_PARENTHESIS",
#endif
-#ifdef SQLITE_ENABLE_FTS4
+#if SQLITE_ENABLE_FTS4
"ENABLE_FTS4",
#endif
-#ifdef SQLITE_ENABLE_ICU
+#if SQLITE_ENABLE_ICU
"ENABLE_ICU",
#endif
-#ifdef SQLITE_ENABLE_IOTRACE
+#if SQLITE_ENABLE_IOTRACE
"ENABLE_IOTRACE",
#endif
-#ifdef SQLITE_ENABLE_LOAD_EXTENSION
+#if SQLITE_ENABLE_LOAD_EXTENSION
"ENABLE_LOAD_EXTENSION",
#endif
-#ifdef SQLITE_ENABLE_LOCKING_STYLE
+#if SQLITE_ENABLE_LOCKING_STYLE
"ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE),
#endif
-#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+#if SQLITE_ENABLE_MEMORY_MANAGEMENT
"ENABLE_MEMORY_MANAGEMENT",
#endif
-#ifdef SQLITE_ENABLE_MEMSYS3
+#if SQLITE_ENABLE_MEMSYS3
"ENABLE_MEMSYS3",
#endif
-#ifdef SQLITE_ENABLE_MEMSYS5
+#if SQLITE_ENABLE_MEMSYS5
"ENABLE_MEMSYS5",
#endif
-#ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK
+#if SQLITE_ENABLE_OVERSIZE_CELL_CHECK
"ENABLE_OVERSIZE_CELL_CHECK",
#endif
-#ifdef SQLITE_ENABLE_RTREE
+#if SQLITE_ENABLE_RTREE
"ENABLE_RTREE",
#endif
#if defined(SQLITE_ENABLE_STAT4)
@@ -13709,31 +13973,31 @@ static const char * const azCompileOpt[] = {
#elif defined(SQLITE_ENABLE_STAT3)
"ENABLE_STAT3",
#endif
-#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+#if SQLITE_ENABLE_UNLOCK_NOTIFY
"ENABLE_UNLOCK_NOTIFY",
#endif
-#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
+#if SQLITE_ENABLE_UPDATE_DELETE_LIMIT
"ENABLE_UPDATE_DELETE_LIMIT",
#endif
-#ifdef SQLITE_HAS_CODEC
+#if SQLITE_HAS_CODEC
"HAS_CODEC",
#endif
-#ifdef SQLITE_HAVE_ISNAN
+#if HAVE_ISNAN || SQLITE_HAVE_ISNAN
"HAVE_ISNAN",
#endif
-#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX
+#if SQLITE_HOMEGROWN_RECURSIVE_MUTEX
"HOMEGROWN_RECURSIVE_MUTEX",
#endif
-#ifdef SQLITE_IGNORE_AFP_LOCK_ERRORS
+#if SQLITE_IGNORE_AFP_LOCK_ERRORS
"IGNORE_AFP_LOCK_ERRORS",
#endif
-#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS
+#if SQLITE_IGNORE_FLOCK_LOCK_ERRORS
"IGNORE_FLOCK_LOCK_ERRORS",
#endif
#ifdef SQLITE_INT64_TYPE
"INT64_TYPE",
#endif
-#ifdef SQLITE_LOCK_TRACE
+#if SQLITE_LOCK_TRACE
"LOCK_TRACE",
#endif
#if defined(SQLITE_MAX_MMAP_SIZE) && !defined(SQLITE_MAX_MMAP_SIZE_xc)
@@ -13742,226 +14006,226 @@ static const char * const azCompileOpt[] = {
#ifdef SQLITE_MAX_SCHEMA_RETRY
"MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY),
#endif
-#ifdef SQLITE_MEMDEBUG
+#if SQLITE_MEMDEBUG
"MEMDEBUG",
#endif
-#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT
+#if SQLITE_MIXED_ENDIAN_64BIT_FLOAT
"MIXED_ENDIAN_64BIT_FLOAT",
#endif
-#ifdef SQLITE_NO_SYNC
+#if SQLITE_NO_SYNC
"NO_SYNC",
#endif
-#ifdef SQLITE_OMIT_ALTERTABLE
+#if SQLITE_OMIT_ALTERTABLE
"OMIT_ALTERTABLE",
#endif
-#ifdef SQLITE_OMIT_ANALYZE
+#if SQLITE_OMIT_ANALYZE
"OMIT_ANALYZE",
#endif
-#ifdef SQLITE_OMIT_ATTACH
+#if SQLITE_OMIT_ATTACH
"OMIT_ATTACH",
#endif
-#ifdef SQLITE_OMIT_AUTHORIZATION
+#if SQLITE_OMIT_AUTHORIZATION
"OMIT_AUTHORIZATION",
#endif
-#ifdef SQLITE_OMIT_AUTOINCREMENT
+#if SQLITE_OMIT_AUTOINCREMENT
"OMIT_AUTOINCREMENT",
#endif
-#ifdef SQLITE_OMIT_AUTOINIT
+#if SQLITE_OMIT_AUTOINIT
"OMIT_AUTOINIT",
#endif
-#ifdef SQLITE_OMIT_AUTOMATIC_INDEX
+#if SQLITE_OMIT_AUTOMATIC_INDEX
"OMIT_AUTOMATIC_INDEX",
#endif
-#ifdef SQLITE_OMIT_AUTORESET
+#if SQLITE_OMIT_AUTORESET
"OMIT_AUTORESET",
#endif
-#ifdef SQLITE_OMIT_AUTOVACUUM
+#if SQLITE_OMIT_AUTOVACUUM
"OMIT_AUTOVACUUM",
#endif
-#ifdef SQLITE_OMIT_BETWEEN_OPTIMIZATION
+#if SQLITE_OMIT_BETWEEN_OPTIMIZATION
"OMIT_BETWEEN_OPTIMIZATION",
#endif
-#ifdef SQLITE_OMIT_BLOB_LITERAL
+#if SQLITE_OMIT_BLOB_LITERAL
"OMIT_BLOB_LITERAL",
#endif
-#ifdef SQLITE_OMIT_BTREECOUNT
+#if SQLITE_OMIT_BTREECOUNT
"OMIT_BTREECOUNT",
#endif
-#ifdef SQLITE_OMIT_BUILTIN_TEST
+#if SQLITE_OMIT_BUILTIN_TEST
"OMIT_BUILTIN_TEST",
#endif
-#ifdef SQLITE_OMIT_CAST
+#if SQLITE_OMIT_CAST
"OMIT_CAST",
#endif
-#ifdef SQLITE_OMIT_CHECK
+#if SQLITE_OMIT_CHECK
"OMIT_CHECK",
#endif
-#ifdef SQLITE_OMIT_COMPLETE
+#if SQLITE_OMIT_COMPLETE
"OMIT_COMPLETE",
#endif
-#ifdef SQLITE_OMIT_COMPOUND_SELECT
+#if SQLITE_OMIT_COMPOUND_SELECT
"OMIT_COMPOUND_SELECT",
#endif
-#ifdef SQLITE_OMIT_CTE
+#if SQLITE_OMIT_CTE
"OMIT_CTE",
#endif
-#ifdef SQLITE_OMIT_DATETIME_FUNCS
+#if SQLITE_OMIT_DATETIME_FUNCS
"OMIT_DATETIME_FUNCS",
#endif
-#ifdef SQLITE_OMIT_DECLTYPE
+#if SQLITE_OMIT_DECLTYPE
"OMIT_DECLTYPE",
#endif
-#ifdef SQLITE_OMIT_DEPRECATED
+#if SQLITE_OMIT_DEPRECATED
"OMIT_DEPRECATED",
#endif
-#ifdef SQLITE_OMIT_DISKIO
+#if SQLITE_OMIT_DISKIO
"OMIT_DISKIO",
#endif
-#ifdef SQLITE_OMIT_EXPLAIN
+#if SQLITE_OMIT_EXPLAIN
"OMIT_EXPLAIN",
#endif
-#ifdef SQLITE_OMIT_FLAG_PRAGMAS
+#if SQLITE_OMIT_FLAG_PRAGMAS
"OMIT_FLAG_PRAGMAS",
#endif
-#ifdef SQLITE_OMIT_FLOATING_POINT
+#if SQLITE_OMIT_FLOATING_POINT
"OMIT_FLOATING_POINT",
#endif
-#ifdef SQLITE_OMIT_FOREIGN_KEY
+#if SQLITE_OMIT_FOREIGN_KEY
"OMIT_FOREIGN_KEY",
#endif
-#ifdef SQLITE_OMIT_GET_TABLE
+#if SQLITE_OMIT_GET_TABLE
"OMIT_GET_TABLE",
#endif
-#ifdef SQLITE_OMIT_INCRBLOB
+#if SQLITE_OMIT_INCRBLOB
"OMIT_INCRBLOB",
#endif
-#ifdef SQLITE_OMIT_INTEGRITY_CHECK
+#if SQLITE_OMIT_INTEGRITY_CHECK
"OMIT_INTEGRITY_CHECK",
#endif
-#ifdef SQLITE_OMIT_LIKE_OPTIMIZATION
+#if SQLITE_OMIT_LIKE_OPTIMIZATION
"OMIT_LIKE_OPTIMIZATION",
#endif
-#ifdef SQLITE_OMIT_LOAD_EXTENSION
+#if SQLITE_OMIT_LOAD_EXTENSION
"OMIT_LOAD_EXTENSION",
#endif
-#ifdef SQLITE_OMIT_LOCALTIME
+#if SQLITE_OMIT_LOCALTIME
"OMIT_LOCALTIME",
#endif
-#ifdef SQLITE_OMIT_LOOKASIDE
+#if SQLITE_OMIT_LOOKASIDE
"OMIT_LOOKASIDE",
#endif
-#ifdef SQLITE_OMIT_MEMORYDB
+#if SQLITE_OMIT_MEMORYDB
"OMIT_MEMORYDB",
#endif
-#ifdef SQLITE_OMIT_OR_OPTIMIZATION
+#if SQLITE_OMIT_OR_OPTIMIZATION
"OMIT_OR_OPTIMIZATION",
#endif
-#ifdef SQLITE_OMIT_PAGER_PRAGMAS
+#if SQLITE_OMIT_PAGER_PRAGMAS
"OMIT_PAGER_PRAGMAS",
#endif
-#ifdef SQLITE_OMIT_PRAGMA
+#if SQLITE_OMIT_PRAGMA
"OMIT_PRAGMA",
#endif
-#ifdef SQLITE_OMIT_PROGRESS_CALLBACK
+#if SQLITE_OMIT_PROGRESS_CALLBACK
"OMIT_PROGRESS_CALLBACK",
#endif
-#ifdef SQLITE_OMIT_QUICKBALANCE
+#if SQLITE_OMIT_QUICKBALANCE
"OMIT_QUICKBALANCE",
#endif
-#ifdef SQLITE_OMIT_REINDEX
+#if SQLITE_OMIT_REINDEX
"OMIT_REINDEX",
#endif
-#ifdef SQLITE_OMIT_SCHEMA_PRAGMAS
+#if SQLITE_OMIT_SCHEMA_PRAGMAS
"OMIT_SCHEMA_PRAGMAS",
#endif
-#ifdef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
+#if SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
"OMIT_SCHEMA_VERSION_PRAGMAS",
#endif
-#ifdef SQLITE_OMIT_SHARED_CACHE
+#if SQLITE_OMIT_SHARED_CACHE
"OMIT_SHARED_CACHE",
#endif
-#ifdef SQLITE_OMIT_SUBQUERY
+#if SQLITE_OMIT_SUBQUERY
"OMIT_SUBQUERY",
#endif
-#ifdef SQLITE_OMIT_TCL_VARIABLE
+#if SQLITE_OMIT_TCL_VARIABLE
"OMIT_TCL_VARIABLE",
#endif
-#ifdef SQLITE_OMIT_TEMPDB
+#if SQLITE_OMIT_TEMPDB
"OMIT_TEMPDB",
#endif
-#ifdef SQLITE_OMIT_TRACE
+#if SQLITE_OMIT_TRACE
"OMIT_TRACE",
#endif
-#ifdef SQLITE_OMIT_TRIGGER
+#if SQLITE_OMIT_TRIGGER
"OMIT_TRIGGER",
#endif
-#ifdef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
+#if SQLITE_OMIT_TRUNCATE_OPTIMIZATION
"OMIT_TRUNCATE_OPTIMIZATION",
#endif
-#ifdef SQLITE_OMIT_UTF16
+#if SQLITE_OMIT_UTF16
"OMIT_UTF16",
#endif
-#ifdef SQLITE_OMIT_VACUUM
+#if SQLITE_OMIT_VACUUM
"OMIT_VACUUM",
#endif
-#ifdef SQLITE_OMIT_VIEW
+#if SQLITE_OMIT_VIEW
"OMIT_VIEW",
#endif
-#ifdef SQLITE_OMIT_VIRTUALTABLE
+#if SQLITE_OMIT_VIRTUALTABLE
"OMIT_VIRTUALTABLE",
#endif
-#ifdef SQLITE_OMIT_WAL
+#if SQLITE_OMIT_WAL
"OMIT_WAL",
#endif
-#ifdef SQLITE_OMIT_WSD
+#if SQLITE_OMIT_WSD
"OMIT_WSD",
#endif
-#ifdef SQLITE_OMIT_XFER_OPT
+#if SQLITE_OMIT_XFER_OPT
"OMIT_XFER_OPT",
#endif
-#ifdef SQLITE_PERFORMANCE_TRACE
+#if SQLITE_PERFORMANCE_TRACE
"PERFORMANCE_TRACE",
#endif
-#ifdef SQLITE_PROXY_DEBUG
+#if SQLITE_PROXY_DEBUG
"PROXY_DEBUG",
#endif
-#ifdef SQLITE_RTREE_INT_ONLY
+#if SQLITE_RTREE_INT_ONLY
"RTREE_INT_ONLY",
#endif
-#ifdef SQLITE_SECURE_DELETE
+#if SQLITE_SECURE_DELETE
"SECURE_DELETE",
#endif
-#ifdef SQLITE_SMALL_STACK
+#if SQLITE_SMALL_STACK
"SMALL_STACK",
#endif
-#ifdef SQLITE_SOUNDEX
+#if SQLITE_SOUNDEX
"SOUNDEX",
#endif
-#ifdef SQLITE_SYSTEM_MALLOC
+#if SQLITE_SYSTEM_MALLOC
"SYSTEM_MALLOC",
#endif
-#ifdef SQLITE_TCL
+#if SQLITE_TCL
"TCL",
#endif
#if defined(SQLITE_TEMP_STORE) && !defined(SQLITE_TEMP_STORE_xc)
"TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE),
#endif
-#ifdef SQLITE_TEST
+#if SQLITE_TEST
"TEST",
#endif
#if defined(SQLITE_THREADSAFE)
"THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE),
#endif
-#ifdef SQLITE_USE_ALLOCA
+#if SQLITE_USE_ALLOCA
"USE_ALLOCA",
#endif
-#ifdef SQLITE_USER_AUTHENTICATION
+#if SQLITE_USER_AUTHENTICATION
"USER_AUTHENTICATION",
#endif
-#ifdef SQLITE_WIN32_MALLOC
+#if SQLITE_WIN32_MALLOC
"WIN32_MALLOC",
#endif
-#ifdef SQLITE_ZERO_MALLOC
+#if SQLITE_ZERO_MALLOC
"ZERO_MALLOC"
#endif
};
@@ -13975,6 +14239,13 @@ static const char * const azCompileOpt[] = {
*/
SQLITE_API int sqlite3_compileoption_used(const char *zOptName){
int i, n;
+
+#if SQLITE_ENABLE_API_ARMOR
+ if( zOptName==0 ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
if( sqlite3StrNICmp(zOptName, "SQLITE_", 7)==0 ) zOptName += 7;
n = sqlite3Strlen30(zOptName);
@@ -14156,6 +14427,7 @@ struct VdbeFrame {
Vdbe *v; /* VM this frame belongs to */
VdbeFrame *pParent; /* Parent of this frame, or NULL if parent is main */
Op *aOp; /* Program instructions for parent frame */
+ i64 *anExec; /* Event counters from parent frame */
Mem *aMem; /* Array of memory cells for parent frame */
u8 *aOnceFlag; /* Array of OP_Once flags for parent frame */
VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */
@@ -14168,7 +14440,8 @@ struct VdbeFrame {
int nOnceFlag; /* Number of entries in aOnceFlag */
int nChildMem; /* Number of memory cells for child frame */
int nChildCsr; /* Number of cursors for child frame */
- int nChange; /* Statement changes (Vdbe.nChanges) */
+ int nChange; /* Statement changes (Vdbe.nChange) */
+ int nDbChange; /* Value of db->nChange */
};
#define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))])
@@ -14319,6 +14592,16 @@ struct Explain {
*/
typedef unsigned bft; /* Bit Field Type */
+typedef struct ScanStatus ScanStatus;
+struct ScanStatus {
+ int addrExplain; /* OP_Explain for loop */
+ int addrLoop; /* Address of "loops" counter */
+ int addrVisit; /* Address of "rows visited" counter */
+ int iSelectID; /* The "Select-ID" for this loop */
+ LogEst nEst; /* Estimated output rows per loop */
+ char *zName; /* Name of table or index */
+};
+
/*
** An instance of the virtual machine. This structure contains the complete
** state of the virtual machine.
@@ -14391,6 +14674,11 @@ struct Vdbe {
int nOnceFlag; /* Size of array aOnceFlag[] */
u8 *aOnceFlag; /* Flags for OP_Once */
AuxData *pAuxData; /* Linked list of auxdata allocations */
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ i64 *anExec; /* Number of times each op has been executed */
+ int nScan; /* Entries in aScan[] */
+ ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */
+#endif
};
/*
@@ -14580,6 +14868,9 @@ SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetF
if( op<0 || op>=ArraySize(wsdStat.nowValue) ){
return SQLITE_MISUSE_BKPT;
}
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( pCurrent==0 || pHighwater==0 ) return SQLITE_MISUSE_BKPT;
+#endif
*pCurrent = wsdStat.nowValue[op];
*pHighwater = wsdStat.mxValue[op];
if( resetFlag ){
@@ -14599,6 +14890,11 @@ SQLITE_API int sqlite3_db_status(
int resetFlag /* Reset high-water mark if true */
){
int rc = SQLITE_OK; /* Return code */
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){
+ return SQLITE_MISUSE_BKPT;
+ }
+#endif
sqlite3_mutex_enter(db->mutex);
switch( op ){
case SQLITE_DBSTATUS_LOOKASIDE_USED: {
@@ -14777,7 +15073,7 @@ SQLITE_API int sqlite3_db_status(
** sqlite3RegisterDateTimeFunctions() found at the bottom of the file.
** All other code has file scope.
**
-** SQLite processes all times and dates as Julian Day numbers. The
+** SQLite processes all times and dates as julian day numbers. The
** dates and times are stored as the number of days since noon
** in Greenwich on November 24, 4714 B.C. according to the Gregorian
** calendar system.
@@ -14792,7 +15088,7 @@ SQLITE_API int sqlite3_db_status(
**
** The Gregorian calendar system is used for all dates and times,
** even those that predate the Gregorian calendar. Historians usually
-** use the Julian calendar for dates prior to 1582-10-15 and for some
+** use the julian calendar for dates prior to 1582-10-15 and for some
** dates afterwards, depending on locale. Beware of this difference.
**
** The conversion algorithms are implemented based on descriptions
@@ -15064,7 +15360,7 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
}
/*
-** Attempt to parse the given string into a Julian Day Number. Return
+** Attempt to parse the given string into a julian day number. Return
** the number of errors.
**
** The following are acceptable forms for the input string:
@@ -15172,8 +15468,9 @@ static void clearYMD_HMS_TZ(DateTime *p){
** already, check for an MSVC build environment that provides
** localtime_s().
*/
-#if !defined(HAVE_LOCALTIME_R) && !defined(HAVE_LOCALTIME_S) && \
- defined(_MSC_VER) && defined(_CRT_INSECURE_DEPRECATE)
+#if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S \
+ && defined(_MSC_VER) && defined(_CRT_INSECURE_DEPRECATE)
+#undef HAVE_LOCALTIME_S
#define HAVE_LOCALTIME_S 1
#endif
@@ -15193,8 +15490,7 @@ static void clearYMD_HMS_TZ(DateTime *p){
*/
static int osLocaltime(time_t *t, struct tm *pTm){
int rc;
-#if (!defined(HAVE_LOCALTIME_R) || !HAVE_LOCALTIME_R) \
- && (!defined(HAVE_LOCALTIME_S) || !HAVE_LOCALTIME_S)
+#if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S
struct tm *pX;
#if SQLITE_THREADSAFE>0
sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
@@ -15211,7 +15507,7 @@ static int osLocaltime(time_t *t, struct tm *pTm){
#ifndef SQLITE_OMIT_BUILTIN_TEST
if( sqlite3GlobalConfig.bLocaltimeFault ) return 1;
#endif
-#if defined(HAVE_LOCALTIME_R) && HAVE_LOCALTIME_R
+#if HAVE_LOCALTIME_R
rc = localtime_r(t, pTm)==0;
#else
rc = localtime_s(pTm, t);
@@ -15635,7 +15931,7 @@ static void dateFunc(
** %f ** fractional seconds SS.SSS
** %H hour 00-24
** %j day of year 000-366
-** %J ** Julian day number
+** %J ** julian day number
** %m month 01-12
** %M minute 00-59
** %s seconds since 1970-01-01
@@ -15655,8 +15951,10 @@ static void strftimeFunc(
size_t i,j;
char *z;
sqlite3 *db;
- const char *zFmt = (const char*)sqlite3_value_text(argv[0]);
+ const char *zFmt;
char zBuf[100];
+ if( argc==0 ) return;
+ zFmt = (const char*)sqlite3_value_text(argv[0]);
if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return;
db = sqlite3_context_db_handle(context);
for(i=0, n=1; zFmt[i]; i++, n++){
@@ -15850,7 +16148,7 @@ static void currentTimeFunc(
iT = sqlite3StmtCurrentTime(context);
if( iT<=0 ) return;
t = iT/1000 - 10000*(sqlite3_int64)21086676;
-#ifdef HAVE_GMTIME_R
+#if HAVE_GMTIME_R
pTm = gmtime_r(&t, &sNow);
#else
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
@@ -16260,6 +16558,10 @@ SQLITE_API int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){
int rc = sqlite3_initialize();
if( rc ) return rc;
#endif
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( pVfs==0 ) return SQLITE_MISUSE_BKPT;
+#endif
+
MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
sqlite3_mutex_enter(mutex);
vfsUnlink(pVfs);
@@ -16520,9 +16822,9 @@ static malloc_zone_t* _sqliteZone_;
** The malloc.h header file is needed for malloc_usable_size() function
** on some systems (e.g. Linux).
*/
-#if defined(HAVE_MALLOC_H) && defined(HAVE_MALLOC_USABLE_SIZE)
-# define SQLITE_USE_MALLOC_H
-# define SQLITE_USE_MALLOC_USABLE_SIZE
+#if HAVE_MALLOC_H && HAVE_MALLOC_USABLE_SIZE
+# define SQLITE_USE_MALLOC_H 1
+# define SQLITE_USE_MALLOC_USABLE_SIZE 1
/*
** The MSVCRT has malloc_usable_size(), but it is called _msize(). The
** use of _msize() is automatic, but can be disabled by compiling with
@@ -18617,6 +18919,7 @@ SQLITE_PRIVATE int sqlite3MutexEnd(void){
SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int id){
#ifndef SQLITE_OMIT_AUTOINIT
if( id<=SQLITE_MUTEX_RECURSIVE && sqlite3_initialize() ) return 0;
+ if( id>SQLITE_MUTEX_RECURSIVE && sqlite3MutexInit() ) return 0;
#endif
return sqlite3GlobalConfig.mutex.xMutexAlloc(id);
}
@@ -19073,8 +19376,12 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){
break;
}
default: {
- assert( iType-2 >= 0 );
- assert( iType-2 < ArraySize(staticMutexes) );
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( iType-2<0 || iType-2>=ArraySize(staticMutexes) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
p = &staticMutexes[iType-2];
#if SQLITE_MUTEX_NREF
p->id = iType;
@@ -19755,6 +20062,12 @@ static sqlite3_mutex *winMutexAlloc(int iType){
break;
}
default: {
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( iType-2<0 || iType-2>=ArraySize(winMutex_staticMutexes) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
assert( iType-2 >= 0 );
assert( iType-2 < ArraySize(winMutex_staticMutexes) );
assert( winMutex_isInit==1 );
@@ -20296,11 +20609,12 @@ SQLITE_PRIVATE void *sqlite3ScratchMalloc(int n){
#if SQLITE_THREADSAFE==0 && !defined(NDEBUG)
- /* Verify that no more than two scratch allocations per thread
- ** are outstanding at one time. (This is only checked in the
- ** single-threaded case since checking in the multi-threaded case
- ** would be much more complicated.) */
- assert( scratchAllocOut<=1 );
+ /* EVIDENCE-OF: R-12970-05880 SQLite will not use more than one scratch
+ ** buffers per thread.
+ **
+ ** This can only be checked in single-threaded mode.
+ */
+ assert( scratchAllocOut==0 );
if( p ) scratchAllocOut++;
#endif
@@ -20750,17 +21064,6 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){
** SQLlite.
*/
-/*
-** If the strchrnul() library function is available, then set
-** HAVE_STRCHRNUL. If that routine is not available, this module
-** will supply its own. The built-in version is slower than
-** the glibc version so the glibc version is definitely preferred.
-*/
-#if !defined(HAVE_STRCHRNUL)
-# define HAVE_STRCHRNUL 0
-#endif
-
-
/*
** Conversion types fall into various categories as defined by the
** following enumeration.
@@ -20959,6 +21262,13 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
PrintfArguments *pArgList = 0; /* Arguments for SQLITE_PRINTF_SQLFUNC */
char buf[etBUFSIZE]; /* Conversion buffer */
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( ap==0 ){
+ (void)SQLITE_MISUSE_BKPT;
+ sqlite3StrAccumReset(pAccum);
+ return;
+ }
+#endif
bufpt = 0;
if( bFlags ){
if( (bArgList = (bFlags & SQLITE_PRINTF_SQLFUNC))!=0 ){
@@ -21499,6 +21809,11 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){
char *zOld = (p->zText==p->zBase ? 0 : p->zText);
i64 szNew = p->nChar;
szNew += N + 1;
+ if( szNew+p->nChar<=p->mxAlloc ){
+ /* Force exponential buffer size growth as long as it does not overflow,
+ ** to avoid having to call this routine too often */
+ szNew += p->nChar;
+ }
if( szNew > p->mxAlloc ){
sqlite3StrAccumReset(p);
setStrAccumError(p, STRACCUM_TOOBIG);
@@ -21515,6 +21830,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){
assert( p->zText!=0 || p->nChar==0 );
if( zOld==0 && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar);
p->zText = zNew;
+ p->nAlloc = sqlite3DbMallocSize(p->db, zNew);
}else{
sqlite3StrAccumReset(p);
setStrAccumError(p, STRACCUM_NOMEM);
@@ -21684,6 +22000,13 @@ SQLITE_API char *sqlite3_vmprintf(const char *zFormat, va_list ap){
char *z;
char zBase[SQLITE_PRINT_BUF_SIZE];
StrAccum acc;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( zFormat==0 ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
#ifndef SQLITE_OMIT_AUTOINIT
if( sqlite3_initialize() ) return 0;
#endif
@@ -21726,6 +22049,13 @@ SQLITE_API char *sqlite3_mprintf(const char *zFormat, ...){
SQLITE_API char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){
StrAccum acc;
if( n<=0 ) return zBuf;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( zBuf==0 || zFormat==0 ) {
+ (void)SQLITE_MISUSE_BKPT;
+ if( zBuf && n>0 ) zBuf[0] = 0;
+ return zBuf;
+ }
+#endif
sqlite3StrAccumInit(&acc, zBuf, n, 0);
acc.useMalloc = 0;
sqlite3VXPrintf(&acc, 0, zFormat, ap);
@@ -21917,11 +22247,19 @@ SQLITE_API void sqlite3_randomness(int N, void *pBuf){
#endif
#if SQLITE_THREADSAFE
- sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PRNG);
- sqlite3_mutex_enter(mutex);
+ sqlite3_mutex *mutex;
#endif
- if( N<=0 ){
+#ifndef SQLITE_OMIT_AUTOINIT
+ if( sqlite3_initialize() ) return;
+#endif
+
+#if SQLITE_THREADSAFE
+ mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PRNG);
+#endif
+
+ sqlite3_mutex_enter(mutex);
+ if( N<=0 || pBuf==0 ){
wsdPrng.isInit = 0;
sqlite3_mutex_leave(mutex);
return;
@@ -22023,6 +22361,8 @@ SQLITE_PRIVATE void sqlite3PrngRestoreState(void){
** of multiple cores can do so, while also allowing applications to stay
** single-threaded if desired.
*/
+#if SQLITE_OS_WIN
+#endif
#if SQLITE_MAX_WORKER_THREADS>0
@@ -22809,7 +23149,7 @@ SQLITE_PRIVATE void sqlite3UtfSelfTest(void){
**
*/
/* #include */
-#ifdef SQLITE_HAVE_ISNAN
+#if HAVE_ISNAN || SQLITE_HAVE_ISNAN
# include
#endif
@@ -22850,7 +23190,7 @@ SQLITE_PRIVATE int sqlite3FaultSim(int iTest){
*/
SQLITE_PRIVATE int sqlite3IsNaN(double x){
int rc; /* The value return */
-#if !defined(SQLITE_HAVE_ISNAN)
+#if !SQLITE_HAVE_ISNAN && !HAVE_ISNAN
/*
** Systems that support the isnan() library function should probably
** make use of it by compiling with -DSQLITE_HAVE_ISNAN. But we have
@@ -22880,9 +23220,9 @@ SQLITE_PRIVATE int sqlite3IsNaN(double x){
volatile double y = x;
volatile double z = y;
rc = (y!=z);
-#else /* if defined(SQLITE_HAVE_ISNAN) */
+#else /* if HAVE_ISNAN */
rc = isnan(x);
-#endif /* SQLITE_HAVE_ISNAN */
+#endif /* HAVE_ISNAN */
testcase( rc );
return rc;
}
@@ -23043,6 +23383,11 @@ SQLITE_PRIVATE int sqlite3Dequote(char *z){
*/
SQLITE_API int sqlite3_stricmp(const char *zLeft, const char *zRight){
register unsigned char *a, *b;
+ if( zLeft==0 ){
+ return zRight ? -1 : 0;
+ }else if( zRight==0 ){
+ return 1;
+ }
a = (unsigned char *)zLeft;
b = (unsigned char *)zRight;
while( *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; }
@@ -23050,6 +23395,11 @@ SQLITE_API int sqlite3_stricmp(const char *zLeft, const char *zRight){
}
SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){
register unsigned char *a, *b;
+ if( zLeft==0 ){
+ return zRight ? -1 : 0;
+ }else if( zRight==0 ){
+ return 1;
+ }
a = (unsigned char *)zLeft;
b = (unsigned char *)zRight;
while( N-- > 0 && *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; }
@@ -28193,9 +28543,9 @@ SQLITE_API int sqlite3_fullsync_count = 0;
** We do not trust systems to provide a working fdatasync(). Some do.
** Others do no. To be safe, we will stick with the (slightly slower)
** fsync(). If you know that your system does support fdatasync() correctly,
-** then simply compile with -Dfdatasync=fdatasync
+** then simply compile with -Dfdatasync=fdatasync or -DHAVE_FDATASYNC
*/
-#if !defined(fdatasync)
+#if !defined(fdatasync) && !HAVE_FDATASYNC
# define fdatasync fsync
#endif
@@ -28516,24 +28866,28 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){
}while( err==EINTR );
if( err ) return SQLITE_IOERR_WRITE;
#else
- /* If the OS does not have posix_fallocate(), fake it. First use
- ** ftruncate() to set the file size, then write a single byte to
- ** the last byte in each block within the extended region. This
- ** is the same technique used by glibc to implement posix_fallocate()
- ** on systems that do not have a real fallocate() system call.
+ /* If the OS does not have posix_fallocate(), fake it. Write a
+ ** single byte to the last byte in each block that falls entirely
+ ** within the extended region. Then, if required, a single byte
+ ** at offset (nSize-1), to set the size of the file correctly.
+ ** This is a similar technique to that used by glibc on systems
+ ** that do not have a real fallocate() call.
*/
int nBlk = buf.st_blksize; /* File-system block size */
+ int nWrite = 0; /* Number of bytes written by seekAndWrite */
i64 iWrite; /* Next offset to write to */
- if( robust_ftruncate(pFile->h, nSize) ){
- pFile->lastErrno = errno;
- return unixLogError(SQLITE_IOERR_TRUNCATE, "ftruncate", pFile->zPath);
- }
iWrite = ((buf.st_size + 2*nBlk - 1)/nBlk)*nBlk-1;
- while( iWrite=buf.st_size );
+ assert( (iWrite/nBlk)==((buf.st_size+nBlk-1)/nBlk) );
+ assert( ((iWrite+1)%nBlk)==0 );
+ for(/*no-op*/; iWrite0
+# error "Memory mapped files require support from the Windows NT kernel,\
+ compile with SQLITE_MAX_MMAP_SIZE=0."
+#endif
+
/*
** Are most of the Win32 ANSI APIs available (i.e. with certain exceptions
** based on the sub-platform)?
@@ -32711,10 +33070,11 @@ SQLITE_API int sqlite3_open_file_count = 0;
/*
** Do we need to manually define the Win32 file mapping APIs for use with WAL
-** mode (e.g. these APIs are available in the Windows CE SDK; however, they
-** are not present in the header file)?
+** mode or memory mapped files (e.g. these APIs are available in the Windows
+** CE SDK; however, they are not present in the header file)?
*/
-#if SQLITE_WIN32_FILEMAPPING_API && !defined(SQLITE_OMIT_WAL)
+#if SQLITE_WIN32_FILEMAPPING_API && \
+ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)
/*
** Two of the file mapping APIs are different under WinRT. Figure out which
** set we need.
@@ -32742,7 +33102,7 @@ WINBASEAPI LPVOID WINAPI MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, SIZE_T);
** This file mapping API is common to both Win32 and WinRT.
*/
WINBASEAPI BOOL WINAPI UnmapViewOfFile(LPCVOID);
-#endif /* SQLITE_WIN32_FILEMAPPING_API && !defined(SQLITE_OMIT_WAL) */
+#endif /* SQLITE_WIN32_FILEMAPPING_API */
/*
** Some Microsoft compilers lack this definition.
@@ -33035,7 +33395,7 @@ static struct win_syscall {
LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[5].pCurrent)
#if (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_ANSI) && \
- !defined(SQLITE_OMIT_WAL))
+ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0))
{ "CreateFileMappingA", (SYSCALL)CreateFileMappingA, 0 },
#else
{ "CreateFileMappingA", (SYSCALL)0, 0 },
@@ -33045,7 +33405,7 @@ static struct win_syscall {
DWORD,DWORD,DWORD,LPCSTR))aSyscall[6].pCurrent)
#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \
- !defined(SQLITE_OMIT_WAL))
+ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0))
{ "CreateFileMappingW", (SYSCALL)CreateFileMappingW, 0 },
#else
{ "CreateFileMappingW", (SYSCALL)0, 0 },
@@ -33385,7 +33745,8 @@ static struct win_syscall {
LPOVERLAPPED))aSyscall[48].pCurrent)
#endif
-#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && !defined(SQLITE_OMIT_WAL))
+#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && \
+ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0))
{ "MapViewOfFile", (SYSCALL)MapViewOfFile, 0 },
#else
{ "MapViewOfFile", (SYSCALL)0, 0 },
@@ -33455,7 +33816,7 @@ static struct win_syscall {
#define osUnlockFileEx ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \
LPOVERLAPPED))aSyscall[58].pCurrent)
-#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL)
+#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0
{ "UnmapViewOfFile", (SYSCALL)UnmapViewOfFile, 0 },
#else
{ "UnmapViewOfFile", (SYSCALL)0, 0 },
@@ -33518,7 +33879,7 @@ static struct win_syscall {
#define osGetFileInformationByHandleEx ((BOOL(WINAPI*)(HANDLE, \
FILE_INFO_BY_HANDLE_CLASS,LPVOID,DWORD))aSyscall[66].pCurrent)
-#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_WAL)
+#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)
{ "MapViewOfFileFromApp", (SYSCALL)MapViewOfFileFromApp, 0 },
#else
{ "MapViewOfFileFromApp", (SYSCALL)0, 0 },
@@ -33582,7 +33943,7 @@ static struct win_syscall {
#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[74].pCurrent)
-#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_WAL)
+#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)
{ "CreateFileMappingFromApp", (SYSCALL)CreateFileMappingFromApp, 0 },
#else
{ "CreateFileMappingFromApp", (SYSCALL)0, 0 },
@@ -33744,8 +34105,8 @@ SQLITE_API int sqlite3_win32_reset_heap(){
int rc;
MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */
MUTEX_LOGIC( sqlite3_mutex *pMem; ) /* The memsys static mutex */
- MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
- MUTEX_LOGIC( pMem = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); )
+ MUTEX_LOGIC( pMaster = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); )
+ MUTEX_LOGIC( pMem = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM); )
sqlite3_mutex_enter(pMaster);
sqlite3_mutex_enter(pMem);
winMemAssertMagic();
@@ -35020,7 +35381,7 @@ static int winRead(
int amt, /* Number of bytes to read */
sqlite3_int64 offset /* Begin reading at this offset */
){
-#if !SQLITE_OS_WINCE
+#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED)
OVERLAPPED overlapped; /* The offset for ReadFile. */
#endif
winFile *pFile = (winFile*)id; /* file handle */
@@ -35052,7 +35413,7 @@ static int winRead(
}
#endif
-#if SQLITE_OS_WINCE
+#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED)
if( winSeekFile(pFile, offset) ){
OSTRACE(("READ file=%p, rc=SQLITE_FULL\n", pFile->h));
return SQLITE_FULL;
@@ -35124,13 +35485,13 @@ static int winWrite(
}
#endif
-#if SQLITE_OS_WINCE
+#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED)
rc = winSeekFile(pFile, offset);
if( rc==0 ){
#else
{
#endif
-#if !SQLITE_OS_WINCE
+#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED)
OVERLAPPED overlapped; /* The offset for WriteFile. */
#endif
u8 *aRem = (u8 *)pBuf; /* Data yet to be written */
@@ -35138,14 +35499,14 @@ static int winWrite(
DWORD nWrite; /* Bytes written by each WriteFile() call */
DWORD lastErrno = NO_ERROR; /* Value returned by GetLastError() */
-#if !SQLITE_OS_WINCE
+#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED)
memset(&overlapped, 0, sizeof(OVERLAPPED));
overlapped.Offset = (LONG)(offset & 0xffffffff);
overlapped.OffsetHigh = (LONG)((offset>>32) & 0x7fffffff);
#endif
while( nRem>0 ){
-#if SQLITE_OS_WINCE
+#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED)
if( !osWriteFile(pFile->h, aRem, nRem, &nWrite, 0) ){
#else
if( !osWriteFile(pFile->h, aRem, nRem, &nWrite, &overlapped) ){
@@ -35158,7 +35519,7 @@ static int winWrite(
lastErrno = osGetLastError();
break;
}
-#if !SQLITE_OS_WINCE
+#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED)
offset += nWrite;
overlapped.Offset = (LONG)(offset & 0xffffffff);
overlapped.OffsetHigh = (LONG)((offset>>32) & 0x7fffffff);
@@ -38539,18 +38900,6 @@ struct PCache {
PgHdr *pPage1; /* Reference to page 1 */
};
-/*
-** Some of the assert() macros in this code are too expensive to run
-** even during normal debugging. Use them only rarely on long-running
-** tests. Enable the expensive asserts using the
-** -DSQLITE_ENABLE_EXPENSIVE_ASSERT=1 compile-time option.
-*/
-#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT
-# define expensive_assert(X) assert(X)
-#else
-# define expensive_assert(X)
-#endif
-
/********************************** Linked List Management ********************/
/* Allowed values for second argument to pcacheManageDirtyList() */
@@ -38704,7 +39053,8 @@ SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *pCache, int szPage){
if( pCache->szPage ){
sqlite3_pcache *pNew;
pNew = sqlite3GlobalConfig.pcache2.xCreate(
- szPage, pCache->szExtra + sizeof(PgHdr), pCache->bPurgeable
+ szPage, pCache->szExtra + ROUND8(sizeof(PgHdr)),
+ pCache->bPurgeable
);
if( pNew==0 ) return SQLITE_NOMEM;
sqlite3GlobalConfig.pcache2.xCachesize(pNew, numberOfCachePages(pCache));
@@ -39159,6 +39509,13 @@ SQLITE_PRIVATE void sqlite3PcacheShrink(PCache *pCache){
sqlite3GlobalConfig.pcache2.xShrink(pCache->pCache);
}
+/*
+** Return the size of the header added by this middleware layer
+** in the page-cache hierarchy.
+*/
+SQLITE_PRIVATE int sqlite3HeaderSizePcache(void){ return ROUND8(sizeof(PgHdr)); }
+
+
#if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG)
/*
** For all dirty pages currently in the cache, invoke the specified
@@ -39472,7 +39829,7 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){
pPg = 0;
}
#else
- pPg = pcache1Alloc(sizeof(PgHdr1) + pCache->szPage + pCache->szExtra);
+ pPg = pcache1Alloc(ROUND8(sizeof(PgHdr1)) + pCache->szPage + pCache->szExtra);
p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage];
#endif
pcache1EnterMutex(pCache->pGroup);
@@ -40157,6 +40514,11 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void){
sqlite3_config(SQLITE_CONFIG_PCACHE2, &defaultMethods);
}
+/*
+** Return the size of the header on each page of this PCACHE implementation.
+*/
+SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void){ return ROUND8(sizeof(PgHdr1)); }
+
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
/*
** This function is called to free superfluous dynamically allocated memory
@@ -41513,6 +41875,8 @@ struct Pager {
u8 setMaster; /* True if a m-j name has been written to jrnl */
u8 doNotSpill; /* Do not spill the cache when non-zero */
u8 subjInMemory; /* True to use in-memory sub-journals */
+ u8 bUseFetch; /* True to use xFetch() */
+ u8 hasBeenUsed; /* True if any content previously read from this pager*/
Pgno dbSize; /* Number of pages in the database */
Pgno dbOrigSize; /* dbSize before the current transaction */
Pgno dbFileSize; /* Number of pages in the database file */
@@ -41530,9 +41894,9 @@ struct Pager {
sqlite3_backup *pBackup; /* Pointer to list of ongoing backup processes */
PagerSavepoint *aSavepoint; /* Array of active savepoints */
int nSavepoint; /* Number of elements in aSavepoint[] */
+ u32 iDataVersion; /* Changes whenever database content changes */
char dbFileVers[16]; /* Changes whenever database file changes */
- u8 bUseFetch; /* True to use xFetch() */
int nMmapOut; /* Number of mmap pages currently outstanding */
sqlite3_int64 szMmap; /* Desired maximum mmap size */
PgHdr *pMmapFreelist; /* List of free mmap page headers (pDirty) */
@@ -42548,10 +42912,19 @@ static int writeMasterJournal(Pager *pPager, const char *zMaster){
** Discard the entire contents of the in-memory page-cache.
*/
static void pager_reset(Pager *pPager){
+ pPager->iDataVersion++;
sqlite3BackupRestart(pPager->pBackup);
sqlite3PcacheClear(pPager->pPCache);
}
+/*
+** Return the pPager->iDataVersion value
+*/
+SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager *pPager){
+ assert( pPager->eState>PAGER_OPEN );
+ return pPager->iDataVersion;
+}
+
/*
** Free all structures in the Pager.aSavepoint[] array and set both
** Pager.aSavepoint and Pager.nSavepoint to zero. Close the sub-journal
@@ -43766,7 +44139,7 @@ static int readDbPage(PgHdr *pPg, u32 iFrame){
**
** For an encrypted database, the situation is more complex: bytes
** 24..39 of the database are white noise. But the probability of
- ** white noising equaling 16 bytes of 0xff is vanishingly small so
+ ** white noise equaling 16 bytes of 0xff is vanishingly small so
** we should still be ok.
*/
memset(pPager->dbFileVers, 0xff, sizeof(pPager->dbFileVers));
@@ -44754,7 +45127,7 @@ static int pagerAcquireMapPage(
PgHdr **ppPage /* OUT: Acquired page object */
){
PgHdr *p; /* Memory mapped page to return */
-
+
if( pPager->pMmapFreelist ){
*ppPage = p = pPager->pMmapFreelist;
pPager->pMmapFreelist = p->pDirty;
@@ -45985,16 +46358,12 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){
);
}
- if( !pPager->tempFile && (
- pPager->pBackup
- || sqlite3PcachePagecount(pPager->pPCache)>0
- || USEFETCH(pPager)
- )){
- /* The shared-lock has just been acquired on the database file
- ** and there are already pages in the cache (from a previous
- ** read or write transaction). Check to see if the database
- ** has been modified. If the database has changed, flush the
- ** cache.
+ if( !pPager->tempFile && pPager->hasBeenUsed ){
+ /* The shared-lock has just been acquired then check to
+ ** see if the database has been modified. If the database has changed,
+ ** flush the cache. The pPager->hasBeenUsed flag prevents this from
+ ** occurring on the very first access to a file, in order to save a
+ ** single unnecessary sqlite3OsRead() call at the start-up.
**
** Database changes is detected by looking at 15 bytes beginning
** at offset 24 into the file. The first 4 of these 16 bytes are
@@ -46159,6 +46528,7 @@ SQLITE_PRIVATE int sqlite3PagerAcquire(
if( pgno==0 ){
return SQLITE_CORRUPT_BKPT;
}
+ pPager->hasBeenUsed = 1;
/* If the pager is in the error state, return an error immediately.
** Otherwise, request the page from the PCache layer. */
@@ -46308,6 +46678,7 @@ SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){
assert( pgno!=0 );
assert( pPager->pPCache!=0 );
pPage = sqlite3PcacheFetch(pPager->pPCache, pgno, 0);
+ assert( pPage==0 || pPager->hasBeenUsed );
return sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pPage);
}
@@ -47174,6 +47545,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){
}
PAGERTRACE(("COMMIT %d\n", PAGERID(pPager)));
+ pPager->iDataVersion++;
rc = pager_end_transaction(pPager, pPager->setMaster, 1);
return pager_error(pPager, rc);
}
@@ -47714,6 +48086,18 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i
}
#endif
+/*
+** The page handle passed as the first argument refers to a dirty page
+** with a page number other than iNew. This function changes the page's
+** page number to iNew and sets the value of the PgHdr.flags field to
+** the value passed as the third parameter.
+*/
+SQLITE_PRIVATE void sqlite3PagerRekey(DbPage *pPg, Pgno iNew, u16 flags){
+ assert( pPg->pgno!=iNew );
+ pPg->flags = flags;
+ sqlite3PcacheMove(pPg, iNew);
+}
+
/*
** Return a pointer to the data for the specified page.
*/
@@ -47930,7 +48314,8 @@ SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, int eMode, int *pnLog,
int rc = SQLITE_OK;
if( pPager->pWal ){
rc = sqlite3WalCheckpoint(pPager->pWal, eMode,
- pPager->xBusyHandler, pPager->pBusyHandlerArg,
+ (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler),
+ pPager->pBusyHandlerArg,
pPager->ckptSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace,
pnLog, pnCkpt
);
@@ -48112,6 +48497,7 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){
}
#endif
+
#endif /* SQLITE_OMIT_DISKIO */
/************** End of pager.c ***********************************************/
@@ -49621,7 +50007,7 @@ static void walMergesort(
** Free an iterator allocated by walIteratorInit().
*/
static void walIteratorFree(WalIterator *p){
- sqlite3ScratchFree(p);
+ sqlite3_free(p);
}
/*
@@ -49656,7 +50042,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){
nByte = sizeof(WalIterator)
+ (nSegment-1)*sizeof(struct WalSegment)
+ iLast*sizeof(ht_slot);
- p = (WalIterator *)sqlite3ScratchMalloc(nByte);
+ p = (WalIterator *)sqlite3_malloc(nByte);
if( !p ){
return SQLITE_NOMEM;
}
@@ -49666,7 +50052,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){
/* Allocate temporary space used by the merge-sort routine. This block
** of memory will be freed before this function returns.
*/
- aTmp = (ht_slot *)sqlite3ScratchMalloc(
+ aTmp = (ht_slot *)sqlite3_malloc(
sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast)
);
if( !aTmp ){
@@ -49703,7 +50089,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){
p->aSegment[i].aPgno = (u32 *)aPgno;
}
}
- sqlite3ScratchFree(aTmp);
+ sqlite3_free(aTmp);
if( rc!=SQLITE_OK ){
walIteratorFree(p);
@@ -49740,6 +50126,38 @@ static int walPagesize(Wal *pWal){
return (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16);
}
+/*
+** The following is guaranteed when this function is called:
+**
+** a) the WRITER lock is held,
+** b) the entire log file has been checkpointed, and
+** c) any existing readers are reading exclusively from the database
+** file - there are no readers that may attempt to read a frame from
+** the log file.
+**
+** This function updates the shared-memory structures so that the next
+** client to write to the database (which may be this one) does so by
+** writing frames into the start of the log file.
+**
+** The value of parameter salt1 is used as the aSalt[1] value in the
+** new wal-index header. It should be passed a pseudo-random value (i.e.
+** one obtained from sqlite3_randomness()).
+*/
+static void walRestartHdr(Wal *pWal, u32 salt1){
+ volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
+ int i; /* Loop counter */
+ u32 *aSalt = pWal->hdr.aSalt; /* Big-endian salt values */
+ pWal->nCkpt++;
+ pWal->hdr.mxFrame = 0;
+ sqlite3Put4byte((u8*)&aSalt[0], 1 + sqlite3Get4byte((u8*)&aSalt[0]));
+ memcpy(&pWal->hdr.aSalt[1], &salt1, 4);
+ walIndexWriteHdr(pWal);
+ pInfo->nBackfill = 0;
+ pInfo->aReadMark[1] = 0;
+ for(i=2; iaReadMark[i] = READMARK_NOT_USED;
+ assert( pInfo->aReadMark[0]==0 );
+}
+
/*
** Copy as much content as we can from the WAL back into the database file
** in response to an sqlite3_wal_checkpoint() request or the equivalent.
@@ -49774,7 +50192,7 @@ static int walPagesize(Wal *pWal){
static int walCheckpoint(
Wal *pWal, /* Wal connection */
int eMode, /* One of PASSIVE, FULL or RESTART */
- int (*xBusyCall)(void*), /* Function to call when busy */
+ int (*xBusy)(void*), /* Function to call when busy */
void *pBusyArg, /* Context argument for xBusyHandler */
int sync_flags, /* Flags for OsSync() (or 0) */
u8 *zBuf /* Temporary buffer to use */
@@ -49788,7 +50206,6 @@ static int walCheckpoint(
u32 mxPage; /* Max database page to write */
int i; /* Loop counter */
volatile WalCkptInfo *pInfo; /* The checkpoint status information */
- int (*xBusy)(void*) = 0; /* Function to call when waiting for locks */
szPage = walPagesize(pWal);
testcase( szPage<=32768 );
@@ -49803,7 +50220,9 @@ static int walCheckpoint(
}
assert( pIter );
- if( eMode!=SQLITE_CHECKPOINT_PASSIVE ) xBusy = xBusyCall;
+ /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked
+ ** in the SQLITE_CHECKPOINT_PASSIVE mode. */
+ assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 );
/* Compute in mxSafeFrame the index of the last frame of the WAL that is
** safe to write into the database. Frames beyond mxSafeFrame might
@@ -49892,19 +50311,38 @@ static int walCheckpoint(
rc = SQLITE_OK;
}
- /* If this is an SQLITE_CHECKPOINT_RESTART operation, and the entire wal
- ** file has been copied into the database file, then block until all
- ** readers have finished using the wal file. This ensures that the next
- ** process to write to the database restarts the wal file.
+ /* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the
+ ** entire wal file has been copied into the database file, then block
+ ** until all readers have finished using the wal file. This ensures that
+ ** the next process to write to the database restarts the wal file.
*/
if( rc==SQLITE_OK && eMode!=SQLITE_CHECKPOINT_PASSIVE ){
assert( pWal->writeLock );
if( pInfo->nBackfillhdr.mxFrame ){
rc = SQLITE_BUSY;
- }else if( eMode==SQLITE_CHECKPOINT_RESTART ){
+ }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){
+ u32 salt1;
+ sqlite3_randomness(4, &salt1);
assert( mxSafeFrame==pWal->hdr.mxFrame );
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1);
if( rc==SQLITE_OK ){
+ if( eMode==SQLITE_CHECKPOINT_TRUNCATE ){
+ /* IMPLEMENTATION-OF: R-44699-57140 This mode works the same way as
+ ** SQLITE_CHECKPOINT_RESTART with the addition that it also
+ ** truncates the log file to zero bytes just prior to a
+ ** successful return.
+ **
+ ** In theory, it might be safe to do this without updating the
+ ** wal-index header in shared memory, as all subsequent reader or
+ ** writer clients should see that the entire log file has been
+ ** checkpointed and behave accordingly. This seems unsafe though,
+ ** as it would leave the system in a state where the contents of
+ ** the wal-index header do not match the contents of the
+ ** file-system. To avoid this, update the wal-index header to
+ ** indicate that the log file contains zero valid frames. */
+ walRestartHdr(pWal, salt1);
+ rc = sqlite3OsTruncate(pWal->pWalFd, 0);
+ }
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
}
}
@@ -50477,7 +50915,7 @@ SQLITE_PRIVATE int sqlite3WalFindFrame(
for(iKey=walHash(pgno); aHash[iKey]; iKey=walNextHash(iKey)){
u32 iFrame = aHash[iKey] + iZero;
if( iFrame<=iLast && aPgno[aHash[iKey]]==pgno ){
- /* assert( iFrame>iRead ); -- not true if there is corruption */
+ assert( iFrame>iRead || CORRUPT_DB );
iRead = iFrame;
}
if( (nCollide--)==0 ){
@@ -50690,7 +51128,6 @@ SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
return rc;
}
-
/*
** This function is called just before writing a set of frames to the log
** file (see sqlite3WalFrames()). It checks to see if, instead of appending
@@ -50723,20 +51160,8 @@ static int walRestartLog(Wal *pWal){
** In theory it would be Ok to update the cache of the header only
** at this point. But updating the actual wal-index header is also
** safe and means there is no special case for sqlite3WalUndo()
- ** to handle if this transaction is rolled back.
- */
- int i; /* Loop counter */
- u32 *aSalt = pWal->hdr.aSalt; /* Big-endian salt values */
-
- pWal->nCkpt++;
- pWal->hdr.mxFrame = 0;
- sqlite3Put4byte((u8*)&aSalt[0], 1 + sqlite3Get4byte((u8*)&aSalt[0]));
- aSalt[1] = salt1;
- walIndexWriteHdr(pWal);
- pInfo->nBackfill = 0;
- pInfo->aReadMark[1] = 0;
- for(i=2; iaReadMark[i] = READMARK_NOT_USED;
- assert( pInfo->aReadMark[0]==0 );
+ ** to handle if this transaction is rolled back. */
+ walRestartHdr(pWal, salt1);
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
}else if( rc!=SQLITE_BUSY ){
return rc;
@@ -51024,7 +51449,7 @@ SQLITE_PRIVATE int sqlite3WalFrames(
*/
SQLITE_PRIVATE int sqlite3WalCheckpoint(
Wal *pWal, /* Wal connection */
- int eMode, /* PASSIVE, FULL or RESTART */
+ int eMode, /* PASSIVE, FULL, RESTART, or TRUNCATE */
int (*xBusy)(void*), /* Function to call when busy */
void *pBusyArg, /* Context argument for xBusyHandler */
int sync_flags, /* Flags to sync db file with (or 0) */
@@ -51036,29 +51461,42 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
int rc; /* Return code */
int isChanged = 0; /* True if a new wal-index header is loaded */
int eMode2 = eMode; /* Mode to pass to walCheckpoint() */
+ int (*xBusy2)(void*) = xBusy; /* Busy handler for eMode2 */
assert( pWal->ckptLock==0 );
assert( pWal->writeLock==0 );
+ /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked
+ ** in the SQLITE_CHECKPOINT_PASSIVE mode. */
+ assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 );
+
if( pWal->readOnly ) return SQLITE_READONLY;
WALTRACE(("WAL%p: checkpoint begins\n", pWal));
+
+ /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive
+ ** "checkpoint" lock on the database file. */
rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
if( rc ){
- /* Usually this is SQLITE_BUSY meaning that another thread or process
- ** is already running a checkpoint, or maybe a recovery. But it might
- ** also be SQLITE_IOERR. */
+ /* EVIDENCE-OF: R-10421-19736 If any other process is running a
+ ** checkpoint operation at the same time, the lock cannot be obtained and
+ ** SQLITE_BUSY is returned.
+ ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured,
+ ** it will not be invoked in this case.
+ */
+ testcase( rc==SQLITE_BUSY );
+ testcase( xBusy!=0 );
return rc;
}
pWal->ckptLock = 1;
- /* If this is a blocking-checkpoint, then obtain the write-lock as well
- ** to prevent any writers from running while the checkpoint is underway.
- ** This has to be done before the call to walIndexReadHdr() below.
+ /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and
+ ** TRUNCATE modes also obtain the exclusive "writer" lock on the database
+ ** file.
**
- ** If the writer lock cannot be obtained, then a passive checkpoint is
- ** run instead. Since the checkpointer is not holding the writer lock,
- ** there is no point in blocking waiting for any readers. Assuming no
- ** other error occurs, this function will return SQLITE_BUSY to the caller.
+ ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained
+ ** immediately, and a busy-handler is configured, it is invoked and the
+ ** writer lock retried until either the busy-handler returns 0 or the
+ ** lock is successfully obtained.
*/
if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1);
@@ -51066,6 +51504,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
pWal->writeLock = 1;
}else if( rc==SQLITE_BUSY ){
eMode2 = SQLITE_CHECKPOINT_PASSIVE;
+ xBusy2 = 0;
rc = SQLITE_OK;
}
}
@@ -51083,7 +51522,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){
rc = SQLITE_CORRUPT_BKPT;
}else{
- rc = walCheckpoint(pWal, eMode2, xBusy, pBusyArg, sync_flags, zBuf);
+ rc = walCheckpoint(pWal, eMode2, xBusy2, pBusyArg, sync_flags, zBuf);
}
/* If no error occurred, set the output variables. */
@@ -51582,6 +52021,7 @@ struct Btree {
u8 locked; /* True if db currently has pBt locked */
int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */
int nBackup; /* Number of backup operations reading this btree */
+ u32 iDataVersion; /* Combines with pBt->pPager->iDataVersion */
Btree *pNext; /* List of other sharable Btrees from the same db */
Btree *pPrev; /* Back pointer of the same list */
#ifndef SQLITE_OMIT_SHARED_CACHE
@@ -53334,6 +53774,11 @@ static void ptrmapPutOvflPtr(MemPage *pPage, u8 *pCell, int *pRC){
** end of the page and all free space is collected into one
** big FreeBlk that occurs in between the header and cell
** pointer array and the cell content area.
+**
+** EVIDENCE-OF: R-44582-60138 SQLite may from time to time reorganize a
+** b-tree page so that there are no freeblocks or fragment bytes, all
+** unused bytes are contained in the unallocated space region, and all
+** cells are packed tightly at the end of the page.
*/
static int defragmentPage(MemPage *pPage){
int i; /* Loop counter */
@@ -53346,6 +53791,7 @@ static int defragmentPage(MemPage *pPage){
int nCell; /* Number of cells on the page */
unsigned char *data; /* The page data */
unsigned char *temp; /* Temp area for cell content */
+ unsigned char *src; /* Source of content */
int iCellFirst; /* First allowable cell index */
int iCellLast; /* Last possible cell index */
@@ -53355,15 +53801,13 @@ static int defragmentPage(MemPage *pPage){
assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE );
assert( pPage->nOverflow==0 );
assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- temp = sqlite3PagerTempSpace(pPage->pBt->pPager);
- data = pPage->aData;
+ temp = 0;
+ src = data = pPage->aData;
hdr = pPage->hdrOffset;
cellOffset = pPage->cellOffset;
nCell = pPage->nCell;
assert( nCell==get2byte(&data[hdr+3]) );
usableSize = pPage->pBt->usableSize;
- cbrk = get2byte(&data[hdr+5]);
- memcpy(&temp[cbrk], &data[cbrk], usableSize - cbrk);
cbrk = usableSize;
iCellFirst = cellOffset + 2*nCell;
iCellLast = usableSize - 4;
@@ -53382,7 +53826,7 @@ static int defragmentPage(MemPage *pPage){
}
#endif
assert( pc>=iCellFirst && pc<=iCellLast );
- size = cellSizePtr(pPage, &temp[pc]);
+ size = cellSizePtr(pPage, &src[pc]);
cbrk -= size;
#if defined(SQLITE_ENABLE_OVERSIZE_CELL_CHECK)
if( cbrk=iCellFirst );
testcase( cbrk+size==usableSize );
testcase( pc+size==usableSize );
- memcpy(&data[cbrk], &temp[pc], size);
put2byte(pAddr, cbrk);
+ if( temp==0 ){
+ int x;
+ if( cbrk==pc ) continue;
+ temp = sqlite3PagerTempSpace(pPage->pBt->pPager);
+ x = get2byte(&data[hdr+5]);
+ memcpy(&temp[x], &data[x], (cbrk+size) - x);
+ src = temp;
+ }
+ memcpy(&data[cbrk], &src[pc], size);
}
assert( cbrk>=iCellFirst );
put2byte(&data[hdr+5], cbrk);
@@ -53412,6 +53864,69 @@ static int defragmentPage(MemPage *pPage){
return SQLITE_OK;
}
+/*
+** Search the free-list on page pPg for space to store a cell nByte bytes in
+** size. If one can be found, return a pointer to the space and remove it
+** from the free-list.
+**
+** If no suitable space can be found on the free-list, return NULL.
+**
+** This function may detect corruption within pPg. If corruption is
+** detected then *pRc is set to SQLITE_CORRUPT and NULL is returned.
+**
+** If a slot of at least nByte bytes is found but cannot be used because
+** there are already at least 60 fragmented bytes on the page, return NULL.
+** In this case, if pbDefrag parameter is not NULL, set *pbDefrag to true.
+*/
+static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc, int *pbDefrag){
+ const int hdr = pPg->hdrOffset;
+ u8 * const aData = pPg->aData;
+ int iAddr;
+ int pc;
+ int usableSize = pPg->pBt->usableSize;
+
+ for(iAddr=hdr+1; (pc = get2byte(&aData[iAddr]))>0; iAddr=pc){
+ int size; /* Size of the free slot */
+ /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of
+ ** increasing offset. */
+ if( pc>usableSize-4 || pc=nByte ){
+ int x = size - nByte;
+ testcase( x==4 );
+ testcase( x==3 );
+ if( x<4 ){
+ /* EVIDENCE-OF: R-11498-58022 In a well-formed b-tree page, the total
+ ** number of bytes in fragments may not exceed 60. */
+ if( aData[hdr+7]>=60 ){
+ if( pbDefrag ) *pbDefrag = 1;
+ return 0;
+ }
+ /* Remove the slot from the free-list. Update the number of
+ ** fragmented bytes within the page. */
+ memcpy(&aData[iAddr], &aData[pc], 2);
+ aData[hdr+7] += (u8)x;
+ }else if( size+pc > usableSize ){
+ *pRc = SQLITE_CORRUPT_BKPT;
+ return 0;
+ }else{
+ /* The slot remains on the free-list. Reduce its size to account
+ ** for the portion used by the new allocation. */
+ put2byte(&aData[pc+2], x);
+ }
+ return &aData[pc + x];
+ }
+ }
+
+ return 0;
+}
+
/*
** Allocate nByte bytes of space from within the B-Tree page passed
** as the first argument. Write into *pIdx the index into pPage->aData[]
@@ -53429,9 +53944,8 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
const int hdr = pPage->hdrOffset; /* Local cache of pPage->hdrOffset */
u8 * const data = pPage->aData; /* Local cache of pPage->aData */
int top; /* First byte of cell content area */
+ int rc = SQLITE_OK; /* Integer return code */
int gap; /* First byte of gap between cell pointers and cell content */
- int rc; /* Integer return code */
- int usableSize; /* Usable size of the page */
assert( sqlite3PagerIswriteable(pPage->pDbPage) );
assert( pPage->pBt );
@@ -53439,20 +53953,18 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
assert( nByte>=0 ); /* Minimum cell size is 4 */
assert( pPage->nFree>=nByte );
assert( pPage->nOverflow==0 );
- usableSize = pPage->pBt->usableSize;
- assert( nByte < usableSize-8 );
+ assert( nByte < (int)(pPage->pBt->usableSize-8) );
assert( pPage->cellOffset == hdr + 12 - 4*pPage->leaf );
gap = pPage->cellOffset + 2*pPage->nCell;
assert( gap<=65536 );
- top = get2byte(&data[hdr+5]);
- if( gap>top ){
- if( top==0 ){
- top = 65536;
- }else{
- return SQLITE_CORRUPT_BKPT;
- }
- }
+ /* EVIDENCE-OF: R-29356-02391 If the database uses a 65536-byte page size
+ ** and the reserved space is zero (the usual value for reserved space)
+ ** then the cell content offset of an empty page wants to be 65536.
+ ** However, that integer is too large to be stored in a 2-byte unsigned
+ ** integer, so a value of 0 is used in its place. */
+ top = get2byteNotZero(&data[hdr+5]);
+ if( gap>top ) return SQLITE_CORRUPT_BKPT;
/* If there is enough space between gap and top for one more cell pointer
** array entry offset, and if the freelist is not empty, then search the
@@ -53462,33 +53974,14 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
testcase( gap+1==top );
testcase( gap==top );
if( gap+2<=top && (data[hdr+1] || data[hdr+2]) ){
- int pc, addr;
- for(addr=hdr+1; (pc = get2byte(&data[addr]))>0; addr=pc){
- int size; /* Size of the free slot */
- if( pc>usableSize-4 || pc=nByte ){
- int x = size - nByte;
- testcase( x==4 );
- testcase( x==3 );
- if( x<4 ){
- if( data[hdr+7]>=60 ) goto defragment_page;
- /* Remove the slot from the free-list. Update the number of
- ** fragmented bytes within the page. */
- memcpy(&data[addr], &data[pc], 2);
- data[hdr+7] += (u8)x;
- }else if( size+pc > usableSize ){
- return SQLITE_CORRUPT_BKPT;
- }else{
- /* The slot remains on the free-list. Reduce its size to account
- ** for the portion used by the new allocation. */
- put2byte(&data[pc+2], x);
- }
- *pIdx = pc + x;
- return SQLITE_OK;
- }
+ int bDefrag = 0;
+ u8 *pSpace = pageFindSlot(pPage, nByte, &rc, &bDefrag);
+ if( rc ) return rc;
+ if( bDefrag ) goto defragment_page;
+ if( pSpace ){
+ assert( pSpace>=data && (pSpace - data)<65536 );
+ *pIdx = (int)(pSpace - data);
+ return SQLITE_OK;
}
}
@@ -53497,8 +53990,8 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
*/
testcase( gap+2+nByte==top );
if( gap+2+nByte>top ){
-defragment_page:
- testcase( pPage->nCell==0 );
+ defragment_page:
+ assert( pPage->nCell>0 || CORRUPT_DB );
rc = defragmentPage(pPage);
if( rc ) return rc;
top = get2byteNotZero(&data[hdr+5]);
@@ -53545,7 +54038,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){
assert( pPage->pBt!=0 );
assert( sqlite3PagerIswriteable(pPage->pDbPage) );
assert( iStart>=pPage->hdrOffset+6+pPage->childPtrSize );
- assert( iEnd <= pPage->pBt->usableSize );
+ assert( CORRUPT_DB || iEnd <= pPage->pBt->usableSize );
assert( sqlite3_mutex_held(pPage->pBt->mutex) );
assert( iSize>=4 ); /* Minimum cell size is 4 */
assert( iStart<=iLast );
@@ -53640,18 +54133,32 @@ static int decodeFlags(MemPage *pPage, int flagByte){
pPage->childPtrSize = 4-4*pPage->leaf;
pBt = pPage->pBt;
if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){
+ /* EVIDENCE-OF: R-03640-13415 A value of 5 means the page is an interior
+ ** table b-tree page. */
+ assert( (PTF_LEAFDATA|PTF_INTKEY)==5 );
+ /* EVIDENCE-OF: R-20501-61796 A value of 13 means the page is a leaf
+ ** table b-tree page. */
+ assert( (PTF_LEAFDATA|PTF_INTKEY|PTF_LEAF)==13 );
pPage->intKey = 1;
pPage->intKeyLeaf = pPage->leaf;
pPage->noPayload = !pPage->leaf;
pPage->maxLocal = pBt->maxLeaf;
pPage->minLocal = pBt->minLeaf;
}else if( flagByte==PTF_ZERODATA ){
+ /* EVIDENCE-OF: R-27225-53936 A value of 2 means the page is an interior
+ ** index b-tree page. */
+ assert( (PTF_ZERODATA)==2 );
+ /* EVIDENCE-OF: R-16571-11615 A value of 10 means the page is a leaf
+ ** index b-tree page. */
+ assert( (PTF_ZERODATA|PTF_LEAF)==10 );
pPage->intKey = 0;
pPage->intKeyLeaf = 0;
pPage->noPayload = 0;
pPage->maxLocal = pBt->maxLocal;
pPage->minLocal = pBt->minLocal;
}else{
+ /* EVIDENCE-OF: R-47608-56469 Any other value for the b-tree page type is
+ ** an error. */
return SQLITE_CORRUPT_BKPT;
}
pPage->max1bytePayload = pBt->max1bytePayload;
@@ -53691,21 +54198,33 @@ static int btreeInitPage(MemPage *pPage){
hdr = pPage->hdrOffset;
data = pPage->aData;
+ /* EVIDENCE-OF: R-28594-02890 The one-byte flag at offset 0 indicating
+ ** the b-tree page type. */
if( decodeFlags(pPage, data[hdr]) ) return SQLITE_CORRUPT_BKPT;
assert( pBt->pageSize>=512 && pBt->pageSize<=65536 );
pPage->maskPage = (u16)(pBt->pageSize - 1);
pPage->nOverflow = 0;
usableSize = pBt->usableSize;
- pPage->cellOffset = cellOffset = hdr + 12 - 4*pPage->leaf;
+ pPage->cellOffset = cellOffset = hdr + 8 + pPage->childPtrSize;
pPage->aDataEnd = &data[usableSize];
pPage->aCellIdx = &data[cellOffset];
+ /* EVIDENCE-OF: R-58015-48175 The two-byte integer at offset 5 designates
+ ** the start of the cell content area. A zero value for this integer is
+ ** interpreted as 65536. */
top = get2byteNotZero(&data[hdr+5]);
+ /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the
+ ** number of cells on the page. */
pPage->nCell = get2byte(&data[hdr+3]);
if( pPage->nCell>MX_CELL(pBt) ){
/* To many cells for a single page. The page must be corrupt */
return SQLITE_CORRUPT_BKPT;
}
testcase( pPage->nCell==MX_CELL(pBt) );
+ /* EVIDENCE-OF: R-24089-57979 If a page contains no cells (which is only
+ ** possible for a root page of a table that contains no rows) then the
+ ** offset to the cell content area will equal the page size minus the
+ ** bytes of reserved space. */
+ assert( pPage->nCell>0 || top==usableSize || CORRUPT_DB );
/* A malformed database page might cause us to read past the end
** of page when parsing a cell.
@@ -53739,13 +54258,20 @@ static int btreeInitPage(MemPage *pPage){
}
#endif
- /* Compute the total free space on the page */
+ /* Compute the total free space on the page
+ ** EVIDENCE-OF: R-23588-34450 The two-byte integer at offset 1 gives the
+ ** start of the first freeblock on the page, or is zero if there are no
+ ** freeblocks. */
pc = get2byte(&data[hdr+1]);
- nFree = data[hdr+7] + top;
+ nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */
while( pc>0 ){
u16 next, size;
if( pciCellLast ){
- /* Start of free block is off the page */
+ /* EVIDENCE-OF: R-55530-52930 In a well-formed b-tree page, there will
+ ** always be at least one cell before the first freeblock.
+ **
+ ** Or, the freeblock is off the end of the page
+ */
return SQLITE_CORRUPT_BKPT;
}
next = get2byte(&data[pc]);
@@ -54151,6 +54677,9 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
#ifdef SQLITE_SECURE_DELETE
pBt->btsFlags |= BTS_SECURE_DELETE;
#endif
+ /* EVIDENCE-OF: R-51873-39618 The page size for a database file is
+ ** determined by the 2-byte integer located at an offset of 16 bytes from
+ ** the beginning of the database file. */
pBt->pageSize = (zDbHeader[16]<<8) | (zDbHeader[17]<<16);
if( pBt->pageSize<512 || pBt->pageSize>SQLITE_MAX_PAGE_SIZE
|| ((pBt->pageSize-1)&pBt->pageSize)!=0 ){
@@ -54169,6 +54698,9 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
#endif
nReserve = 0;
}else{
+ /* EVIDENCE-OF: R-37497-42412 The size of the reserved region is
+ ** determined by the one-byte unsigned integer found at an offset of 20
+ ** into the database file header. */
nReserve = zDbHeader[20];
pBt->btsFlags |= BTS_PAGESIZE_FIXED;
#ifndef SQLITE_OMIT_AUTOVACUUM
@@ -54678,6 +55210,9 @@ static int lockBtree(BtShared *pBt){
u32 usableSize;
u8 *page1 = pPage1->aData;
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
+ ** 61 74 20 33 00. */
if( memcmp(page1, zMagicHeader, 16)!=0 ){
goto page1_init_failed;
}
@@ -54718,15 +55253,21 @@ static int lockBtree(BtShared *pBt){
}
#endif
- /* The maximum embedded fraction must be exactly 25%. And the minimum
- ** embedded fraction must be 12.5% for both leaf-data and non-leaf-data.
+ /* EVIDENCE-OF: R-15465-20813 The maximum and minimum embedded payload
+ ** fractions and the leaf payload fraction values must be 64, 32, and 32.
+ **
** The original design allowed these amounts to vary, but as of
** version 3.6.0, we require them to be fixed.
*/
if( memcmp(&page1[21], "\100\040\040",3)!=0 ){
goto page1_init_failed;
}
+ /* EVIDENCE-OF: R-51873-39618 The page size for a database file is
+ ** determined by the 2-byte integer located at an offset of 16 bytes from
+ ** the beginning of the database file. */
pageSize = (page1[16]<<8) | (page1[17]<<16);
+ /* EVIDENCE-OF: R-25008-21688 The size of a page is a power of two
+ ** between 512 and 65536 inclusive. */
if( ((pageSize-1)&pageSize)!=0
|| pageSize>SQLITE_MAX_PAGE_SIZE
|| pageSize<=256
@@ -54734,6 +55275,13 @@ static int lockBtree(BtShared *pBt){
goto page1_init_failed;
}
assert( (pageSize & 7)==0 );
+ /* EVIDENCE-OF: R-59310-51205 The "reserved space" size in the 1-byte
+ ** integer at offset 20 is the number of bytes of space at the end of
+ ** each page to reserve for extensions.
+ **
+ ** EVIDENCE-OF: R-37497-42412 The size of the reserved region is
+ ** determined by the one-byte unsigned integer found at an offset of 20
+ ** into the database file header. */
usableSize = pageSize - page1[20];
if( (u32)pageSize!=pBt->pageSize ){
/* After reading the first page of the database assuming a page size
@@ -54754,6 +55302,9 @@ static int lockBtree(BtShared *pBt){
rc = SQLITE_CORRUPT_BKPT;
goto page1_init_failed;
}
+ /* EVIDENCE-OF: R-28312-64704 However, the usable size is not allowed to
+ ** be less than 480. In other words, if the page size is 512, then the
+ ** reserved space size cannot exceed 32. */
if( usableSize<480 ){
goto page1_init_failed;
}
@@ -55634,6 +56185,7 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){
sqlite3BtreeLeave(p);
return rc;
}
+ p->iDataVersion--; /* Compensate for pPager->iDataVersion++; */
pBt->inTransaction = TRANS_READ;
btreeClearHasContent(pBt);
}
@@ -55997,7 +56549,7 @@ SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){
releasePage(pCur->apPage[i]);
}
unlockBtreeIfUnused(pBt);
- sqlite3DbFree(pBtree->db, pCur->aOverflow);
+ sqlite3_free(pCur->aOverflow);
/* sqlite3_free(pCur); */
sqlite3BtreeLeave(pBtree);
}
@@ -56292,6 +56844,7 @@ static int accessPayload(
offset -= pCur->info.nLocal;
}
+
if( rc==SQLITE_OK && amt>0 ){
const u32 ovflSize = pBt->usableSize - 4; /* Bytes content per ovfl page */
Pgno nextPage;
@@ -56309,8 +56862,8 @@ static int accessPayload(
if( eOp!=2 && (pCur->curFlags & BTCF_ValidOvfl)==0 ){
int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize;
if( nOvfl>pCur->nOvflAlloc ){
- Pgno *aNew = (Pgno*)sqlite3DbRealloc(
- pCur->pBtree->db, pCur->aOverflow, nOvfl*2*sizeof(Pgno)
+ Pgno *aNew = (Pgno*)sqlite3Realloc(
+ pCur->aOverflow, nOvfl*2*sizeof(Pgno)
);
if( aNew==0 ){
rc = SQLITE_NOMEM;
@@ -56357,6 +56910,7 @@ static int accessPayload(
*/
assert( eOp!=2 );
assert( pCur->curFlags & BTCF_ValidOvfl );
+ assert( pCur->pBtree->db==pBt->db );
if( pCur->aOverflow[iIdx+1] ){
nextPage = pCur->aOverflow[iIdx+1];
}else{
@@ -57331,6 +57885,8 @@ static int allocateBtreePage(
assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) );
pPage1 = pBt->pPage1;
mxPage = btreePagecount(pBt);
+ /* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36
+ ** stores stores the total number of pages on the freelist. */
n = get4byte(&pPage1->aData[36]);
testcase( n==mxPage-1 );
if( n>=mxPage ){
@@ -57377,8 +57933,14 @@ static int allocateBtreePage(
do {
pPrevTrunk = pTrunk;
if( pPrevTrunk ){
+ /* EVIDENCE-OF: R-01506-11053 The first integer on a freelist trunk page
+ ** is the page number of the next freelist trunk page in the list or
+ ** zero if this is the last freelist trunk page. */
iTrunk = get4byte(&pPrevTrunk->aData[0]);
}else{
+ /* EVIDENCE-OF: R-59841-13798 The 4-byte big-endian integer at offset 32
+ ** stores the page number of the first page of the freelist, or zero if
+ ** the freelist is empty. */
iTrunk = get4byte(&pPage1->aData[32]);
}
testcase( iTrunk==mxPage );
@@ -57393,8 +57955,9 @@ static int allocateBtreePage(
}
assert( pTrunk!=0 );
assert( pTrunk->aData!=0 );
-
- k = get4byte(&pTrunk->aData[4]); /* # of leaves on this trunk page */
+ /* EVIDENCE-OF: R-13523-04394 The second integer on a freelist trunk page
+ ** is the number of leaf page pointers to follow. */
+ k = get4byte(&pTrunk->aData[4]);
if( k==0 && !searchList ){
/* The trunk has no leaves and the list is not being searched.
** So extract the trunk page itself and use it as the newly
@@ -57712,6 +58275,11 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
** for now. At some point in the future (once everyone has upgraded
** to 3.6.0 or later) we should consider fixing the conditional above
** to read "usableSize/4-2" instead of "usableSize/4-8".
+ **
+ ** EVIDENCE-OF: R-19920-11576 However, newer versions of SQLite still
+ ** avoid using the last six entries in the freelist trunk page array in
+ ** order that database files created by newer versions of SQLite can be
+ ** read by older versions of SQLite.
*/
rc = sqlite3PagerWrite(pTrunk->pDbPage);
if( rc==SQLITE_OK ){
@@ -58063,9 +58631,17 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){
return;
}
pPage->nCell--;
- memmove(ptr, ptr+2, 2*(pPage->nCell - idx));
- put2byte(&data[hdr+3], pPage->nCell);
- pPage->nFree += 2;
+ if( pPage->nCell==0 ){
+ memset(&data[hdr+1], 0, 4);
+ data[hdr+7] = 0;
+ put2byte(&data[hdr+5], pPage->pBt->usableSize);
+ pPage->nFree = pPage->pBt->usableSize - pPage->hdrOffset
+ - pPage->childPtrSize - 8;
+ }else{
+ memmove(ptr, ptr+2, 2*(pPage->nCell - idx));
+ put2byte(&data[hdr+3], pPage->nCell);
+ pPage->nFree += 2;
+ }
}
/*
@@ -58160,45 +58736,271 @@ static void insertCell(
}
/*
-** Add a list of cells to a page. The page should be initially empty.
-** The cells are guaranteed to fit on the page.
+** Array apCell[] contains pointers to nCell b-tree page cells. The
+** szCell[] array contains the size in bytes of each cell. This function
+** replaces the current contents of page pPg with the contents of the cell
+** array.
+**
+** Some of the cells in apCell[] may currently be stored in pPg. This
+** function works around problems caused by this by making a copy of any
+** such cells before overwriting the page data.
+**
+** The MemPage.nFree field is invalidated by this function. It is the
+** responsibility of the caller to set it correctly.
*/
-static void assemblePage(
- MemPage *pPage, /* The page to be assembled */
- int nCell, /* The number of cells to add to this page */
- u8 **apCell, /* Pointers to cell bodies */
- u16 *aSize /* Sizes of the cells */
+static void rebuildPage(
+ MemPage *pPg, /* Edit this page */
+ int nCell, /* Final number of cells on page */
+ u8 **apCell, /* Array of cells */
+ u16 *szCell /* Array of cell sizes */
){
- int i; /* Loop counter */
- u8 *pCellptr; /* Address of next cell pointer */
- int cellbody; /* Address of next cell body */
- u8 * const data = pPage->aData; /* Pointer to data for pPage */
- const int hdr = pPage->hdrOffset; /* Offset of header on pPage */
- const int nUsable = pPage->pBt->usableSize; /* Usable size of page */
+ const int hdr = pPg->hdrOffset; /* Offset of header on pPg */
+ u8 * const aData = pPg->aData; /* Pointer to data for pPg */
+ const int usableSize = pPg->pBt->usableSize;
+ u8 * const pEnd = &aData[usableSize];
+ int i;
+ u8 *pCellptr = pPg->aCellIdx;
+ u8 *pTmp = sqlite3PagerTempSpace(pPg->pBt->pPager);
+ u8 *pData;
- assert( pPage->nOverflow==0 );
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- assert( nCell>=0 && nCell<=(int)MX_CELL(pPage->pBt)
- && (int)MX_CELL(pPage->pBt)<=10921);
- assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ i = get2byte(&aData[hdr+5]);
+ memcpy(&pTmp[i], &aData[i], usableSize - i);
- /* Check that the page has just been zeroed by zeroPage() */
- assert( pPage->nCell==0 );
- assert( get2byteNotZero(&data[hdr+5])==nUsable );
-
- pCellptr = &pPage->aCellIdx[nCell*2];
- cellbody = nUsable;
- for(i=nCell-1; i>=0; i--){
- u16 sz = aSize[i];
- pCellptr -= 2;
- cellbody -= sz;
- put2byte(pCellptr, cellbody);
- memcpy(&data[cellbody], apCell[i], sz);
+ pData = pEnd;
+ for(i=0; iaData && pCellnFree -= (nCell*2 + nUsable - cellbody);
- pPage->nCell = (u16)nCell;
+
+ /* The pPg->nFree field is now set incorrectly. The caller will fix it. */
+ pPg->nCell = nCell;
+ pPg->nOverflow = 0;
+
+ put2byte(&aData[hdr+1], 0);
+ put2byte(&aData[hdr+3], pPg->nCell);
+ put2byte(&aData[hdr+5], pData - aData);
+ aData[hdr+7] = 0x00;
+}
+
+/*
+** Array apCell[] contains nCell pointers to b-tree cells. Array szCell
+** contains the size in bytes of each such cell. This function attempts to
+** add the cells stored in the array to page pPg. If it cannot (because
+** the page needs to be defragmented before the cells will fit), non-zero
+** is returned. Otherwise, if the cells are added successfully, zero is
+** returned.
+**
+** Argument pCellptr points to the first entry in the cell-pointer array
+** (part of page pPg) to populate. After cell apCell[0] is written to the
+** page body, a 16-bit offset is written to pCellptr. And so on, for each
+** cell in the array. It is the responsibility of the caller to ensure
+** that it is safe to overwrite this part of the cell-pointer array.
+**
+** When this function is called, *ppData points to the start of the
+** content area on page pPg. If the size of the content area is extended,
+** *ppData is updated to point to the new start of the content area
+** before returning.
+**
+** Finally, argument pBegin points to the byte immediately following the
+** end of the space required by this page for the cell-pointer area (for
+** all cells - not just those inserted by the current call). If the content
+** area must be extended to before this point in order to accomodate all
+** cells in apCell[], then the cells do not fit and non-zero is returned.
+*/
+static int pageInsertArray(
+ MemPage *pPg, /* Page to add cells to */
+ u8 *pBegin, /* End of cell-pointer array */
+ u8 **ppData, /* IN/OUT: Page content -area pointer */
+ u8 *pCellptr, /* Pointer to cell-pointer area */
+ int nCell, /* Number of cells to add to pPg */
+ u8 **apCell, /* Array of cells */
+ u16 *szCell /* Array of cell sizes */
+){
+ int i;
+ u8 *aData = pPg->aData;
+ u8 *pData = *ppData;
+ const int bFreelist = aData[1] || aData[2];
+ assert( CORRUPT_DB || pPg->hdrOffset==0 ); /* Never called on page 1 */
+ for(i=0; iaData;
+ u8 * const pEnd = &aData[pPg->pBt->usableSize];
+ u8 * const pStart = &aData[pPg->hdrOffset + 8 + pPg->childPtrSize];
+ int nRet = 0;
+ int i;
+ u8 *pFree = 0;
+ int szFree = 0;
+
+ for(i=0; i=pStart && pCellaData && (pFree - aData)<65536 );
+ freeSpace(pPg, (u16)(pFree - aData), szFree);
+ }
+ pFree = pCell;
+ szFree = sz;
+ if( pFree+sz>pEnd ) return 0;
+ }else{
+ pFree = pCell;
+ szFree += sz;
+ }
+ nRet++;
+ }
+ }
+ if( pFree ){
+ assert( pFree>aData && (pFree - aData)<65536 );
+ freeSpace(pPg, (u16)(pFree - aData), szFree);
+ }
+ return nRet;
+}
+
+/*
+** apCell[] and szCell[] contains pointers to and sizes of all cells in the
+** pages being balanced. The current page, pPg, has pPg->nCell cells starting
+** with apCell[iOld]. After balancing, this page should hold nNew cells
+** starting at apCell[iNew].
+**
+** This routine makes the necessary adjustments to pPg so that it contains
+** the correct cells after being balanced.
+**
+** The pPg->nFree field is invalid when this function returns. It is the
+** responsibility of the caller to set it correctly.
+*/
+static void editPage(
+ MemPage *pPg, /* Edit this page */
+ int iOld, /* Index of first cell currently on page */
+ int iNew, /* Index of new first cell on page */
+ int nNew, /* Final number of cells on page */
+ u8 **apCell, /* Array of cells */
+ u16 *szCell /* Array of cell sizes */
+){
+ u8 * const aData = pPg->aData;
+ const int hdr = pPg->hdrOffset;
+ u8 *pBegin = &pPg->aCellIdx[nNew * 2];
+ int nCell = pPg->nCell; /* Cells stored on pPg */
+ u8 *pData;
+ u8 *pCellptr;
+ int i;
+ int iOldEnd = iOld + pPg->nCell + pPg->nOverflow;
+ int iNewEnd = iNew + nNew;
+
+#ifdef SQLITE_DEBUG
+ u8 *pTmp = sqlite3PagerTempSpace(pPg->pBt->pPager);
+ memcpy(pTmp, aData, pPg->pBt->usableSize);
+#endif
+
+ /* Remove cells from the start and end of the page */
+ if( iOldaCellIdx, &pPg->aCellIdx[nShift*2], nCell*2);
+ nCell -= nShift;
+ }
+ if( iNewEnd < iOldEnd ){
+ nCell -= pageFreeArray(
+ pPg, iOldEnd-iNewEnd, &apCell[iNewEnd], &szCell[iNewEnd]
+ );
+ }
+
+ pData = &aData[get2byteNotZero(&aData[hdr+5])];
+ if( pDataaCellIdx;
+ memmove(&pCellptr[nAdd*2], pCellptr, nCell*2);
+ if( pageInsertArray(
+ pPg, pBegin, &pData, pCellptr,
+ nAdd, &apCell[iNew], &szCell[iNew]
+ ) ) goto editpage_fail;
+ nCell += nAdd;
+ }
+
+ /* Add any overflow cells */
+ for(i=0; inOverflow; i++){
+ int iCell = (iOld + pPg->aiOvfl[i]) - iNew;
+ if( iCell>=0 && iCellaCellIdx[iCell * 2];
+ memmove(&pCellptr[2], pCellptr, (nCell - iCell) * 2);
+ nCell++;
+ if( pageInsertArray(
+ pPg, pBegin, &pData, pCellptr,
+ 1, &apCell[iCell + iNew], &szCell[iCell + iNew]
+ ) ) goto editpage_fail;
+ }
+ }
+
+ /* Append cells to the end of the page */
+ pCellptr = &pPg->aCellIdx[nCell*2];
+ if( pageInsertArray(
+ pPg, pBegin, &pData, pCellptr,
+ nNew-nCell, &apCell[iNew+nCell], &szCell[iNew+nCell]
+ ) ) goto editpage_fail;
+
+ pPg->nCell = nNew;
+ pPg->nOverflow = 0;
+
+ put2byte(&aData[hdr+3], pPg->nCell);
+ put2byte(&aData[hdr+5], pData - aData);
+
+#ifdef SQLITE_DEBUG
+ for(i=0; iaCellIdx[i*2]);
+ if( pCell>=aData && pCell<&aData[pPg->pBt->usableSize] ){
+ pCell = &pTmp[pCell - aData];
+ }
+ assert( 0==memcmp(pCell, &aData[iOff], szCell[i+iNew]) );
+ }
+#endif
+
+ return;
+ editpage_fail:
+ /* Unable to edit this page. Rebuild it from scratch instead. */
+ rebuildPage(pPg, nNew, &apCell[iNew], &szCell[iNew]);
}
/*
@@ -58252,7 +59054,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){
assert( pPage->nOverflow==1 );
/* This error condition is now caught prior to reaching this function */
- if( pPage->nCell==0 ) return SQLITE_CORRUPT_BKPT;
+ if( NEVER(pPage->nCell==0) ) return SQLITE_CORRUPT_BKPT;
/* Allocate a new page. This page will become the right-sibling of
** pPage. Make the parent page writable, so that the new divider cell
@@ -58270,7 +59072,8 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){
assert( sqlite3PagerIswriteable(pNew->pDbPage) );
assert( pPage->aData[0]==(PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF) );
zeroPage(pNew, PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF);
- assemblePage(pNew, 1, &pCell, &szCell);
+ rebuildPage(pNew, 1, &pCell, &szCell);
+ pNew->nFree = pBt->usableSize - pNew->cellOffset - 2 - szCell;
/* If this is an auto-vacuum database, update the pointer map
** with entries for the new page, and any pointer from the
@@ -58489,17 +59292,22 @@ static int balance_nonroot(
int iOvflSpace = 0; /* First unused byte of aOvflSpace[] */
int szScratch; /* Size of scratch memory requested */
MemPage *apOld[NB]; /* pPage and up to two siblings */
- MemPage *apCopy[NB]; /* Private copies of apOld[] pages */
MemPage *apNew[NB+2]; /* pPage and up to NB siblings after balancing */
u8 *pRight; /* Location in parent of right-sibling pointer */
u8 *apDiv[NB-1]; /* Divider cells in pParent */
int cntNew[NB+2]; /* Index in aCell[] of cell after i-th page */
- int szNew[NB+2]; /* Combined size of cells place on i-th page */
+ int cntOld[NB+2]; /* Old index in aCell[] after i-th page */
+ int szNew[NB+2]; /* Combined size of cells placed on i-th page */
u8 **apCell = 0; /* All cells begin balanced */
u16 *szCell; /* Local size of all cells in apCell[] */
u8 *aSpace1; /* Space for copies of dividers cells */
Pgno pgno; /* Temp var to store a page number in */
+ u8 abDone[NB+2]; /* True after i'th new page is populated */
+ Pgno aPgno[NB+2]; /* Page numbers of new pages before shuffling */
+ Pgno aPgOrder[NB+2]; /* Copy of aPgno[] used for sorting pages */
+ u16 aPgFlags[NB+2]; /* flags field of new pages before shuffling */
+ memset(abDone, 0, sizeof(abDone));
pBt = pParent->pBt;
assert( sqlite3_mutex_held(pBt->mutex) );
assert( sqlite3PagerIswriteable(pParent->pDbPage) );
@@ -58608,12 +59416,14 @@ static int balance_nonroot(
/*
** Allocate space for memory structures
*/
- k = pBt->pageSize + ROUND8(sizeof(MemPage));
szScratch =
nMaxCells*sizeof(u8*) /* apCell */
+ nMaxCells*sizeof(u16) /* szCell */
- + pBt->pageSize /* aSpace1 */
- + k*nOld; /* Page copies (apCopy) */
+ + pBt->pageSize; /* aSpace1 */
+
+ /* EVIDENCE-OF: R-28375-38319 SQLite will never request a scratch buffer
+ ** that is more than 6 times the database page size. */
+ assert( szScratch<=6*(int)pBt->pageSize );
apCell = sqlite3ScratchMalloc( szScratch );
if( apCell==0 ){
rc = SQLITE_NOMEM;
@@ -58626,8 +59436,8 @@ static int balance_nonroot(
/*
** Load pointers to all cells on sibling pages and the divider cells
** into the local apCell[] array. Make copies of the divider cells
- ** into space obtained from aSpace1[] and remove the divider cells
- ** from pParent.
+ ** into space obtained from aSpace1[]. The divider cells have already
+ ** been removed from pParent.
**
** If the siblings are on leaf pages, then the child pointers of the
** divider cells are stripped from the cells before they are copied
@@ -58643,15 +59453,7 @@ static int balance_nonroot(
leafData = apOld[0]->intKeyLeaf;
for(i=0; ipageSize + k*i];
- memcpy(pOld, apOld[i], sizeof(MemPage));
- pOld->aData = (void*)&pOld[1];
- memcpy(pOld->aData, apOld[i]->aData, pBt->pageSize);
+ MemPage *pOld = apOld[i];
limit = pOld->nCell+pOld->nOverflow;
if( pOld->nOverflow>0 ){
@@ -58672,6 +59474,7 @@ static int balance_nonroot(
nCell++;
}
}
+ cntOld[i] = nCell;
if( i usableSpace ){
- szNew[k] = subtotal - szCell[i];
+ szNew[k] = subtotal - szCell[i] - 2;
cntNew[k] = i;
if( leafData ){ i--; }
subtotal = 0;
@@ -58737,9 +59544,10 @@ static int balance_nonroot(
/*
** The packing computed by the previous block is biased toward the siblings
- ** on the left side. The left siblings are always nearly full, while the
- ** right-most sibling might be nearly empty. This block of code attempts
- ** to adjust the packing of siblings to get a better balance.
+ ** on the left side (siblings with smaller keys). The left siblings are
+ ** always nearly full, while the right-most sibling might be nearly empty.
+ ** The next block of code attempts to adjust the packing of siblings to
+ ** get a better balance.
**
** This adjustment is more than an optimization. The packing above might
** be so out of balance as to be illegal. For example, the right-most
@@ -58768,22 +59576,18 @@ static int balance_nonroot(
szNew[i-1] = szLeft;
}
- /* Either we found one or more cells (cntnew[0])>0) or pPage is
- ** a virtual root page. A virtual root page is when the real root
- ** page is page 1 and we are the only child of that page.
- **
- ** UPDATE: The assert() below is not necessarily true if the database
- ** file is corrupt. The corruption will be detected and reported later
- ** in this procedure so there is no need to act upon it now.
+ /* Sanity check: For a non-corrupt database file one of the follwing
+ ** must be true:
+ ** (1) We found one or more cells (cntNew[0])>0), or
+ ** (2) pPage is a virtual root page. A virtual root page is when
+ ** the real root page is page 1 and we are the only child of
+ ** that page.
*/
-#if 0
- assert( cntNew[0]>0 || (pParent->pgno==1 && pParent->nCell==0) );
-#endif
-
- TRACE(("BALANCE: old: %d %d %d ",
- apOld[0]->pgno,
- nOld>=2 ? apOld[1]->pgno : 0,
- nOld>=3 ? apOld[2]->pgno : 0
+ assert( cntNew[0]>0 || (pParent->pgno==1 && pParent->nCell==0) || CORRUPT_DB);
+ TRACE(("BALANCE: old: %d(nc=%d) %d(nc=%d) %d(nc=%d)\n",
+ apOld[0]->pgno, apOld[0]->nCell,
+ nOld>=2 ? apOld[1]->pgno : 0, nOld>=2 ? apOld[1]->nCell : 0,
+ nOld>=3 ? apOld[2]->pgno : 0, nOld>=3 ? apOld[2]->nCell : 0
));
/*
@@ -58806,8 +59610,10 @@ static int balance_nonroot(
assert( i>0 );
rc = allocateBtreePage(pBt, &pNew, &pgno, (bBulk ? 1 : pgno), 0);
if( rc ) goto balance_cleanup;
+ zeroPage(pNew, pageFlags);
apNew[i] = pNew;
nNew++;
+ cntOld[i] = nCell;
/* Set the pointer-map entry for the new sibling page. */
if( ISAUTOVACUUM ){
@@ -58819,135 +59625,247 @@ static int balance_nonroot(
}
}
- /* Free any old pages that were not reused as new pages.
- */
- while( ipgno;
- int minI = i;
- for(j=i+1; jpgno<(unsigned)minV ){
- minI = j;
- minV = apNew[j]->pgno;
+ for(i=0; ipgno;
+ aPgFlags[i] = apNew[i]->pDbPage->flags;
+ for(j=0; ji ){
- MemPage *pT;
- pT = apNew[i];
- apNew[i] = apNew[minI];
- apNew[minI] = pT;
+ }
+ for(i=0; ii ){
+ sqlite3PagerRekey(apNew[iBest]->pDbPage, pBt->nPage+iBest+1, 0);
+ }
+ sqlite3PagerRekey(apNew[i]->pDbPage, pgno, aPgFlags[iBest]);
+ apNew[i]->pgno = pgno;
}
}
- TRACE(("new: %d(%d) %d(%d) %d(%d) %d(%d) %d(%d)\n",
- apNew[0]->pgno, szNew[0],
+
+ TRACE(("BALANCE: new: %d(%d nc=%d) %d(%d nc=%d) %d(%d nc=%d) "
+ "%d(%d nc=%d) %d(%d nc=%d)\n",
+ apNew[0]->pgno, szNew[0], cntNew[0],
nNew>=2 ? apNew[1]->pgno : 0, nNew>=2 ? szNew[1] : 0,
+ nNew>=2 ? cntNew[1] - cntNew[0] - !leafData : 0,
nNew>=3 ? apNew[2]->pgno : 0, nNew>=3 ? szNew[2] : 0,
+ nNew>=3 ? cntNew[2] - cntNew[1] - !leafData : 0,
nNew>=4 ? apNew[3]->pgno : 0, nNew>=4 ? szNew[3] : 0,
- nNew>=5 ? apNew[4]->pgno : 0, nNew>=5 ? szNew[4] : 0));
+ nNew>=4 ? cntNew[3] - cntNew[2] - !leafData : 0,
+ nNew>=5 ? apNew[4]->pgno : 0, nNew>=5 ? szNew[4] : 0,
+ nNew>=5 ? cntNew[4] - cntNew[3] - !leafData : 0
+ ));
assert( sqlite3PagerIswriteable(pParent->pDbPage) );
put4byte(pRight, apNew[nNew-1]->pgno);
- /*
- ** Evenly distribute the data in apCell[] across the new pages.
- ** Insert divider cells into pParent as necessary.
+ /* If the sibling pages are not leaves, ensure that the right-child pointer
+ ** of the right-most new sibling page is set to the value that was
+ ** originally in the same field of the right-most old sibling page. */
+ if( (pageFlags & PTF_LEAF)==0 && nOld!=nNew ){
+ MemPage *pOld = (nNew>nOld ? apNew : apOld)[nOld-1];
+ memcpy(&apNew[nNew-1]->aData[8], &pOld->aData[8], 4);
+ }
+
+ /* Make any required updates to pointer map entries associated with
+ ** cells stored on sibling pages following the balance operation. Pointer
+ ** map entries associated with divider cells are set by the insertCell()
+ ** routine. The associated pointer map entries are:
+ **
+ ** a) if the cell contains a reference to an overflow chain, the
+ ** entry associated with the first page in the overflow chain, and
+ **
+ ** b) if the sibling pages are not leaves, the child page associated
+ ** with the cell.
+ **
+ ** If the sibling pages are not leaves, then the pointer map entry
+ ** associated with the right-child of each sibling may also need to be
+ ** updated. This happens below, after the sibling pages have been
+ ** populated, not here.
*/
- j = 0;
- for(i=0; inCell>0 || (nNew==1 && cntNew[0]==0) );
- assert( pNew->nOverflow==0 );
+ if( ISAUTOVACUUM ){
+ MemPage *pNew = apNew[0];
+ u8 *aOld = pNew->aData;
+ int cntOldNext = pNew->nCell + pNew->nOverflow;
+ int usableSize = pBt->usableSize;
+ int iNew = 0;
+ int iOld = 0;
- j = cntNew[i];
+ for(i=0; inCell + pOld->nOverflow + !leafData;
+ aOld = pOld->aData;
+ }
+ if( i==cntNew[iNew] ){
+ pNew = apNew[++iNew];
+ if( !leafData ) continue;
+ }
- /* If the sibling page assembled above was not the right-most sibling,
- ** insert a divider cell into the parent page.
- */
- assert( ileaf ){
- memcpy(&pNew->aData[8], pCell, 4);
- }else if( leafData ){
- /* If the tree is a leaf-data tree, and the siblings are leaves,
- ** then there is no divider cell in apCell[]. Instead, the divider
- ** cell consists of the integer key for the right-most cell of
- ** the sibling-page assembled above only.
- */
- CellInfo info;
- j--;
- btreeParseCellPtr(pNew, apCell[j], &info);
- pCell = pTemp;
- sz = 4 + putVarint(&pCell[4], info.nKey);
- pTemp = 0;
- }else{
- pCell -= 4;
- /* Obscure case for non-leaf-data trees: If the cell at pCell was
- ** previously stored on a leaf node, and its reported size was 4
- ** bytes, then it may actually be smaller than this
- ** (see btreeParseCellPtr(), 4 bytes is the minimum size of
- ** any cell). But it is important to pass the correct size to
- ** insertCell(), so reparse the cell now.
- **
- ** Note that this can never happen in an SQLite data file, as all
- ** cells are at least 4 bytes. It only happens in b-trees used
- ** to evaluate "IN (SELECT ...)" and similar clauses.
- */
- if( szCell[j]==4 ){
- assert(leafCorrection==4);
- sz = cellSizePtr(pParent, pCell);
+ /* Cell pCell is destined for new sibling page pNew. Originally, it
+ ** was either part of sibling page iOld (possibly an overflow cell),
+ ** or else the divider cell to the left of sibling page iOld. So,
+ ** if sibling page iOld had the same page number as pNew, and if
+ ** pCell really was a part of sibling page iOld (not a divider or
+ ** overflow cell), we can skip updating the pointer map entries. */
+ if( iOld>=nNew
+ || pNew->pgno!=aPgno[iOld]
+ || pCell=&aOld[usableSize]
+ ){
+ if( !leafCorrection ){
+ ptrmapPut(pBt, get4byte(pCell), PTRMAP_BTREE, pNew->pgno, &rc);
+ }
+ if( szCell[i]>pNew->minLocal ){
+ ptrmapPutOvflPtr(pNew, pCell, &rc);
}
}
- iOvflSpace += sz;
- assert( sz<=pBt->maxLocal+23 );
- assert( iOvflSpace <= (int)pBt->pageSize );
- insertCell(pParent, nxDiv, pCell, sz, pTemp, pNew->pgno, &rc);
- if( rc!=SQLITE_OK ) goto balance_cleanup;
- assert( sqlite3PagerIswriteable(pParent->pDbPage) );
-
- j++;
- nxDiv++;
}
}
- assert( j==nCell );
+
+ /* Insert new divider cells into pParent. */
+ for(i=0; ileaf ){
+ memcpy(&pNew->aData[8], pCell, 4);
+ }else if( leafData ){
+ /* If the tree is a leaf-data tree, and the siblings are leaves,
+ ** then there is no divider cell in apCell[]. Instead, the divider
+ ** cell consists of the integer key for the right-most cell of
+ ** the sibling-page assembled above only.
+ */
+ CellInfo info;
+ j--;
+ btreeParseCellPtr(pNew, apCell[j], &info);
+ pCell = pTemp;
+ sz = 4 + putVarint(&pCell[4], info.nKey);
+ pTemp = 0;
+ }else{
+ pCell -= 4;
+ /* Obscure case for non-leaf-data trees: If the cell at pCell was
+ ** previously stored on a leaf node, and its reported size was 4
+ ** bytes, then it may actually be smaller than this
+ ** (see btreeParseCellPtr(), 4 bytes is the minimum size of
+ ** any cell). But it is important to pass the correct size to
+ ** insertCell(), so reparse the cell now.
+ **
+ ** Note that this can never happen in an SQLite data file, as all
+ ** cells are at least 4 bytes. It only happens in b-trees used
+ ** to evaluate "IN (SELECT ...)" and similar clauses.
+ */
+ if( szCell[j]==4 ){
+ assert(leafCorrection==4);
+ sz = cellSizePtr(pParent, pCell);
+ }
+ }
+ iOvflSpace += sz;
+ assert( sz<=pBt->maxLocal+23 );
+ assert( iOvflSpace <= (int)pBt->pageSize );
+ insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno, &rc);
+ if( rc!=SQLITE_OK ) goto balance_cleanup;
+ assert( sqlite3PagerIswriteable(pParent->pDbPage) );
+ }
+
+ /* Now update the actual sibling pages. The order in which they are updated
+ ** is important, as this code needs to avoid disrupting any page from which
+ ** cells may still to be read. In practice, this means:
+ **
+ ** (1) If cells are moving left (from apNew[iPg] to apNew[iPg-1])
+ ** then it is not safe to update page apNew[iPg] until after
+ ** the left-hand sibling apNew[iPg-1] has been updated.
+ **
+ ** (2) If cells are moving right (from apNew[iPg] to apNew[iPg+1])
+ ** then it is not safe to update page apNew[iPg] until after
+ ** the right-hand sibling apNew[iPg+1] has been updated.
+ **
+ ** If neither of the above apply, the page is safe to update.
+ **
+ ** The iPg value in the following loop starts at nNew-1 goes down
+ ** to 0, then back up to nNew-1 again, thus making two passes over
+ ** the pages. On the initial downward pass, only condition (1) above
+ ** needs to be tested because (2) will always be true from the previous
+ ** step. On the upward pass, both conditions are always true, so the
+ ** upwards pass simply processes pages that were missed on the downward
+ ** pass.
+ */
+ for(i=1-nNew; i=0 && iPg=0 /* On the upwards pass, or... */
+ || cntOld[iPg-1]>=cntNew[iPg-1] /* Condition (1) is true */
+ ){
+ int iNew;
+ int iOld;
+ int nNewCell;
+
+ /* Verify condition (1): If cells are moving left, update iPg
+ ** only after iPg-1 has already been updated. */
+ assert( iPg==0 || cntOld[iPg-1]>=cntNew[iPg-1] || abDone[iPg-1] );
+
+ /* Verify condition (2): If cells are moving right, update iPg
+ ** only after iPg+1 has already been updated. */
+ assert( cntNew[iPg]>=cntOld[iPg] || abDone[iPg+1] );
+
+ if( iPg==0 ){
+ iNew = iOld = 0;
+ nNewCell = cntNew[0];
+ }else{
+ iOld = iPgnFree = usableSpace-szNew[iPg];
+ assert( apNew[iPg]->nOverflow==0 );
+ assert( apNew[iPg]->nCell==nNewCell );
+ }
+ }
+
+ /* All pages have been processed exactly once */
+ assert( memcmp(abDone, "\01\01\01\01\01", nNew)==0 );
+
assert( nOld>0 );
assert( nNew>0 );
- if( (pageFlags & PTF_LEAF)==0 ){
- u8 *zChild = &apCopy[nOld-1]->aData[8];
- memcpy(&apNew[nNew-1]->aData[8], zChild, 4);
- }
if( isRoot && pParent->nCell==0 && pParent->hdrOffset<=apNew[0]->nFree ){
/* The root page of the b-tree now contains no cells. The only sibling
@@ -58960,126 +59878,50 @@ static int balance_nonroot(
** sets all pointer-map entries corresponding to database image pages
** for which the pointer is stored within the content being copied.
**
- ** The second assert below verifies that the child page is defragmented
- ** (it must be, as it was just reconstructed using assemblePage()). This
- ** is important if the parent page happens to be page 1 of the database
- ** image. */
+ ** It is critical that the child page be defragmented before being
+ ** copied into the parent, because if the parent is page 1 then it will
+ ** by smaller than the child due to the database header, and so all the
+ ** free space needs to be up front.
+ */
assert( nNew==1 );
+ rc = defragmentPage(apNew[0]);
+ testcase( rc!=SQLITE_OK );
assert( apNew[0]->nFree ==
- (get2byte(&apNew[0]->aData[5])-apNew[0]->cellOffset-apNew[0]->nCell*2)
+ (get2byte(&apNew[0]->aData[5])-apNew[0]->cellOffset-apNew[0]->nCell*2)
+ || rc!=SQLITE_OK
);
copyNodeContent(apNew[0], pParent, &rc);
freePage(apNew[0], &rc);
- }else if( ISAUTOVACUUM ){
- /* Fix the pointer-map entries for all the cells that were shifted around.
- ** There are several different types of pointer-map entries that need to
- ** be dealt with by this routine. Some of these have been set already, but
- ** many have not. The following is a summary:
- **
- ** 1) The entries associated with new sibling pages that were not
- ** siblings when this function was called. These have already
- ** been set. We don't need to worry about old siblings that were
- ** moved to the free-list - the freePage() code has taken care
- ** of those.
- **
- ** 2) The pointer-map entries associated with the first overflow
- ** page in any overflow chains used by new divider cells. These
- ** have also already been taken care of by the insertCell() code.
- **
- ** 3) If the sibling pages are not leaves, then the child pages of
- ** cells stored on the sibling pages may need to be updated.
- **
- ** 4) If the sibling pages are not internal intkey nodes, then any
- ** overflow pages used by these cells may need to be updated
- ** (internal intkey nodes never contain pointers to overflow pages).
- **
- ** 5) If the sibling pages are not leaves, then the pointer-map
- ** entries for the right-child pages of each sibling may need
- ** to be updated.
- **
- ** Cases 1 and 2 are dealt with above by other code. The next
- ** block deals with cases 3 and 4 and the one after that, case 5. Since
- ** setting a pointer map entry is a relatively expensive operation, this
- ** code only sets pointer map entries for child or overflow pages that have
- ** actually moved between pages. */
- MemPage *pNew = apNew[0];
- MemPage *pOld = apCopy[0];
- int nOverflow = pOld->nOverflow;
- int iNextOld = pOld->nCell + nOverflow;
- int iOverflow = (nOverflow ? pOld->aiOvfl[0] : -1);
- j = 0; /* Current 'old' sibling page */
- k = 0; /* Current 'new' sibling page */
- for(i=0; inCell + pOld->nOverflow;
- if( pOld->nOverflow ){
- nOverflow = pOld->nOverflow;
- iOverflow = i + !leafData + pOld->aiOvfl[0];
- }
- isDivider = !leafData;
- }
-
- assert(nOverflow>0 || iOverflowaiOvfl[0]==pOld->aiOvfl[1]-1);
- assert(nOverflow<3 || pOld->aiOvfl[1]==pOld->aiOvfl[2]-1);
- if( i==iOverflow ){
- isDivider = 1;
- if( (--nOverflow)>0 ){
- iOverflow++;
- }
- }
-
- if( i==cntNew[k] ){
- /* Cell i is the cell immediately following the last cell on new
- ** sibling page k. If the siblings are not leaf pages of an
- ** intkey b-tree, then cell i is a divider cell. */
- pNew = apNew[++k];
- if( !leafData ) continue;
- }
- assert( jpgno!=pNew->pgno ){
- if( !leafCorrection ){
- ptrmapPut(pBt, get4byte(apCell[i]), PTRMAP_BTREE, pNew->pgno, &rc);
- }
- if( szCell[i]>pNew->minLocal ){
- ptrmapPutOvflPtr(pNew, apCell[i], &rc);
- }
- }
+ }else if( ISAUTOVACUUM && !leafCorrection ){
+ /* Fix the pointer map entries associated with the right-child of each
+ ** sibling page. All other pointer map entries have already been taken
+ ** care of. */
+ for(i=0; iaData[8]);
+ ptrmapPut(pBt, key, PTRMAP_BTREE, apNew[i]->pgno, &rc);
}
+ }
- if( !leafCorrection ){
- for(i=0; iaData[8]);
- ptrmapPut(pBt, key, PTRMAP_BTREE, apNew[i]->pgno, &rc);
- }
- }
+ assert( pParent->isInit );
+ TRACE(("BALANCE: finished: old=%d new=%d cells=%d\n",
+ nOld, nNew, nCell));
+
+ /* Free any old pages that were not reused as new pages.
+ */
+ for(i=nNew; iisInit ){
/* The ptrmapCheckPages() contains assert() statements that verify that
** all pointer map pages are set correctly. This is helpful while
** debugging. This is usually disabled because a corrupt database may
** cause an assert() statement to fail. */
ptrmapCheckPages(apNew, nNew);
ptrmapCheckPages(&pParent, 1);
-#endif
}
-
- assert( pParent->isInit );
- TRACE(("BALANCE: finished: old=%d new=%d cells=%d\n",
- nOld, nNew, nCell));
+#endif
/*
** Cleanup before returning.
@@ -59971,6 +60813,13 @@ SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){
** The schema layer numbers meta values differently. At the schema
** layer (and the SetCookie and ReadCookie opcodes) the number of
** free pages is not visible. So Cookie[0] is the same as Meta[1].
+**
+** This routine treats Meta[BTREE_DATA_VERSION] as a special case. Instead
+** of reading the value out of the header, it instead loads the "DataVersion"
+** from the pager. The BTREE_DATA_VERSION value is not actually stored in the
+** database file. It is a number computed by the pager. But its access
+** pattern is the same as header meta values, and so it is convenient to
+** read it from this routine.
*/
SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){
BtShared *pBt = p->pBt;
@@ -59981,7 +60830,11 @@ SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){
assert( pBt->pPage1 );
assert( idx>=0 && idx<=15 );
- *pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]);
+ if( idx==BTREE_DATA_VERSION ){
+ *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iDataVersion;
+ }else{
+ *pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]);
+ }
/* If auto-vacuum is disabled in this build and this is an auto-vacuum
** database, mark the database as read-only. */
@@ -60072,7 +60925,7 @@ SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){
if( pCur->iPage==0 ){
/* All pages of the b-tree have been visited. Return successfully. */
*pnEntry = nEntry;
- return SQLITE_OK;
+ return moveToRoot(pCur);
}
moveToParent(pCur);
}while ( pCur->aiIdx[pCur->iPage]>=pCur->apPage[pCur->iPage]->nCell );
@@ -60464,8 +61317,14 @@ static int checkTreePage(
assert( contentOffset<=usableSize ); /* Enforced by btreeInitPage() */
memset(hit+contentOffset, 0, usableSize-contentOffset);
memset(hit, 1, contentOffset);
+ /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the
+ ** number of cells on the page. */
nCell = get2byte(&data[hdr+3]);
+ /* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page
+ ** immediately follows the b-tree page header. */
cellStart = hdr + 12 - 4*pPage->leaf;
+ /* EVIDENCE-OF: R-02776-14802 The cell pointer array consists of K 2-byte
+ ** integer offsets to the cell contents. */
for(i=0; i=pc; j--) hit[j]++;
}
}
+ /* EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header
+ ** is the offset of the first freeblock, or zero if there are no
+ ** freeblocks on the page. */
i = get2byte(&data[hdr+1]);
while( i>0 ){
int size, j;
@@ -60488,7 +61350,13 @@ static int checkTreePage(
size = get2byte(&data[i+2]);
assert( i+size<=usableSize ); /* Enforced by btreeInitPage() */
for(j=i+size-1; j>=i; j--) hit[j]++;
+ /* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a
+ ** big-endian integer which is the offset in the b-tree page of the next
+ ** freeblock in the chain, or zero if the freeblock is the last on the
+ ** chain. */
j = get2byte(&data[i]);
+ /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of
+ ** increasing offset. */
assert( j==0 || j>i+size ); /* Enforced by btreeInitPage() */
assert( j<=usableSize-4 ); /* Enforced by btreeInitPage() */
i = j;
@@ -60502,6 +61370,11 @@ static int checkTreePage(
break;
}
}
+ /* EVIDENCE-OF: R-43263-13491 The total number of bytes in all fragments
+ ** is stored in the fifth field of the b-tree page header.
+ ** EVIDENCE-OF: R-07161-27322 The one-byte integer at offset 7 gives the
+ ** number of fragmented free bytes within the cell content area.
+ */
if( cnt!=data[hdr+7] ){
checkAppendMsg(pCheck,
"Fragmentation of %d bytes reported as %d on page %d",
@@ -60905,6 +61778,11 @@ SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *p){
return (p->pBt->btsFlags & BTS_READ_ONLY)!=0;
}
+/*
+** Return the size of the header added to each page by this module.
+*/
+SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }
+
/************** End of btree.c ***********************************************/
/************** Begin file backup.c ******************************************/
/*
@@ -61029,6 +61907,20 @@ static int setDestPgsz(sqlite3_backup *p){
return rc;
}
+/*
+** Check that there is no open read-transaction on the b-tree passed as the
+** second argument. If there is not, return SQLITE_OK. Otherwise, if there
+** is an open read-transaction, return SQLITE_ERROR and leave an error
+** message in database handle db.
+*/
+static int checkReadTransaction(sqlite3 *db, Btree *p){
+ if( sqlite3BtreeIsInReadTrans(p) ){
+ sqlite3ErrorWithMsg(db, SQLITE_ERROR, "destination database is in use");
+ return SQLITE_ERROR;
+ }
+ return SQLITE_OK;
+}
+
/*
** Create an sqlite3_backup process to copy the contents of zSrcDb from
** connection handle pSrcDb to zDestDb in pDestDb. If successful, return
@@ -61045,6 +61937,13 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init(
){
sqlite3_backup *p; /* Value to return */
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(pSrcDb)||!sqlite3SafetyCheckOk(pDestDb) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
+
/* Lock the source database handle. The destination database
** handle is not locked in this routine, but it is locked in
** sqlite3_backup_step(). The user is required to ensure that no
@@ -61081,12 +61980,15 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init(
p->iNext = 1;
p->isAttached = 0;
- if( 0==p->pSrc || 0==p->pDest || setDestPgsz(p)==SQLITE_NOMEM ){
+ if( 0==p->pSrc || 0==p->pDest
+ || setDestPgsz(p)==SQLITE_NOMEM
+ || checkReadTransaction(pDestDb, p->pDest)!=SQLITE_OK
+ ){
/* One (or both) of the named databases did not exist or an OOM
- ** error was hit. The error has already been written into the
- ** pDestDb handle. All that is left to do here is free the
- ** sqlite3_backup structure.
- */
+ ** error was hit. Or there is a transaction open on the destination
+ ** database. The error has already been written into the pDestDb
+ ** handle. All that is left to do here is free the sqlite3_backup
+ ** structure. */
sqlite3_free(p);
p = 0;
}
@@ -61241,6 +62143,9 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
int pgszSrc = 0; /* Source page size */
int pgszDest = 0; /* Destination page size */
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( p==0 ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(p->pSrcDb->mutex);
sqlite3BtreeEnter(p->pSrc);
if( p->pDestDb ){
@@ -61530,6 +62435,12 @@ SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){
** call to sqlite3_backup_step().
*/
SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( p==0 ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
return p->nRemaining;
}
@@ -61538,6 +62449,12 @@ SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p){
** recent call to sqlite3_backup_step().
*/
SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( p==0 ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
return p->nPagecount;
}
@@ -63628,6 +64545,7 @@ static Op *opIterNext(VdbeOpIter *p){
*/
SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
int hasAbort = 0;
+ int hasFkCounter = 0;
Op *pOp;
VdbeOpIter sIter;
memset(&sIter, 0, sizeof(sIter));
@@ -63636,15 +64554,17 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
while( (pOp = opIterNext(&sIter))!=0 ){
int opcode = pOp->opcode;
if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename
-#ifndef SQLITE_OMIT_FOREIGN_KEY
- || (opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1)
-#endif
|| ((opcode==OP_Halt || opcode==OP_HaltIfNull)
&& ((pOp->p1&0xff)==SQLITE_CONSTRAINT && pOp->p2==OE_Abort))
){
hasAbort = 1;
break;
}
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+ if( opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1 ){
+ hasFkCounter = 1;
+ }
+#endif
}
sqlite3DbFree(v->db, sIter.apSub);
@@ -63653,7 +64573,7 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
** through all opcodes and hasAbort may be set incorrectly. Return
** true for this case to prevent the assert() in the callers frame
** from failing. */
- return ( v->db->mallocFailed || hasAbort==mayAbort );
+ return ( v->db->mallocFailed || hasAbort==mayAbort || hasFkCounter );
}
#endif /* SQLITE_DEBUG - the sqlite3AssertMayAbort() function */
@@ -63829,6 +64749,34 @@ SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp,
return addr;
}
+#if defined(SQLITE_ENABLE_STMT_SCANSTATUS)
+/*
+** Add an entry to the array of counters managed by sqlite3_stmt_scanstatus().
+*/
+SQLITE_PRIVATE void sqlite3VdbeScanStatus(
+ Vdbe *p, /* VM to add scanstatus() to */
+ int addrExplain, /* Address of OP_Explain (or 0) */
+ int addrLoop, /* Address of loop counter */
+ int addrVisit, /* Address of rows visited counter */
+ LogEst nEst, /* Estimated number of output rows */
+ const char *zName /* Name of table or index being scanned */
+){
+ int nByte = (p->nScan+1) * sizeof(ScanStatus);
+ ScanStatus *aNew;
+ aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte);
+ if( aNew ){
+ ScanStatus *pNew = &aNew[p->nScan++];
+ pNew->addrExplain = addrExplain;
+ pNew->addrLoop = addrLoop;
+ pNew->addrVisit = addrVisit;
+ pNew->nEst = nEst;
+ pNew->zName = sqlite3DbStrDup(p->db, zName);
+ p->aScan = aNew;
+ }
+}
+#endif
+
+
/*
** Change the value of the P1 operand for a specific instruction.
** This routine is useful when a large program is loaded from a
@@ -64927,6 +65875,9 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
p->apCsr = allocSpace(p->apCsr, nCursor*sizeof(VdbeCursor*),
&zCsr, zEnd, &nByte);
p->aOnceFlag = allocSpace(p->aOnceFlag, nOnce, &zCsr, zEnd, &nByte);
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ p->anExec = allocSpace(p->anExec, p->nOp*sizeof(i64), &zCsr, zEnd, &nByte);
+#endif
if( nByte ){
p->pFree = sqlite3DbMallocZero(db, nByte);
}
@@ -64943,7 +65894,7 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
p->aVar[n].db = db;
}
}
- if( p->azVar ){
+ if( p->azVar && pParse->nzVar>0 ){
p->nzVar = pParse->nzVar;
memcpy(p->azVar, pParse->azVar, p->nzVar*sizeof(p->azVar[0]));
memset(pParse->azVar, 0, pParse->nzVar*sizeof(pParse->azVar[0]));
@@ -64994,6 +65945,9 @@ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){
*/
SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){
Vdbe *v = pFrame->v;
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ v->anExec = pFrame->anExec;
+#endif
v->aOnceFlag = pFrame->aOnceFlag;
v->nOnceFlag = pFrame->nOnceFlag;
v->aOp = pFrame->aOp;
@@ -65004,6 +65958,7 @@ SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){
v->nCursor = pFrame->nCursor;
v->db->lastRowid = pFrame->lastRowid;
v->nChange = pFrame->nChange;
+ v->db->nChange = pFrame->nDbChange;
return pFrame->pc;
}
@@ -65571,6 +66526,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
sqlite3CloseSavepoints(db);
db->autoCommit = 1;
+ p->nChange = 0;
}
}
}
@@ -65611,6 +66567,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
}else if( rc!=SQLITE_OK ){
p->rc = rc;
sqlite3RollbackAll(db, SQLITE_OK);
+ p->nChange = 0;
}else{
db->nDeferredCons = 0;
db->nDeferredImmCons = 0;
@@ -65619,6 +66576,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
}
}else{
sqlite3RollbackAll(db, SQLITE_OK);
+ p->nChange = 0;
}
db->nStatement = 0;
}else if( eStatementOp==0 ){
@@ -65630,6 +66588,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
sqlite3CloseSavepoints(db);
db->autoCommit = 1;
+ p->nChange = 0;
}
}
@@ -65650,6 +66609,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
sqlite3CloseSavepoints(db);
db->autoCommit = 1;
+ p->nChange = 0;
}
}
@@ -65911,6 +66871,12 @@ SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){
sqlite3DbFree(db, p->aColName);
sqlite3DbFree(db, p->zSql);
sqlite3DbFree(db, p->pFree);
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ for(i=0; inScan; i++){
+ sqlite3DbFree(db, p->aScan[i].zName);
+ }
+ sqlite3DbFree(db, p->aScan);
+#endif
}
/*
@@ -66069,9 +67035,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format){
i64 i = pMem->u.i;
u64 u;
if( i<0 ){
- if( i<(-MAX_6BYTE) ) return 6;
- /* Previous test prevents: u = -(-9223372036854775808) */
- u = -i;
+ u = ~i;
}else{
u = i;
}
@@ -66237,10 +67201,14 @@ static u32 SQLITE_NOINLINE serialGet(
u32 y = FOUR_BYTE_UINT(buf+4);
x = (x<<32) + y;
if( serial_type==6 ){
+ /* EVIDENCE-OF: R-29851-52272 Value is a big-endian 64-bit
+ ** twos-complement integer. */
pMem->u.i = *(i64*)&x;
pMem->flags = MEM_Int;
testcase( pMem->u.i<0 );
}else{
+ /* EVIDENCE-OF: R-57343-49114 Value is a big-endian IEEE 754-2008 64-bit
+ ** floating point number. */
#if !defined(NDEBUG) && !defined(SQLITE_OMIT_FLOATING_POINT)
/* Verify that integers and floating point values use the same
** byte order. Or, that if SQLITE_MIXED_ENDIAN_64BIT_FLOAT is
@@ -66268,35 +67236,46 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet(
switch( serial_type ){
case 10: /* Reserved for future use */
case 11: /* Reserved for future use */
- case 0: { /* NULL */
+ case 0: { /* Null */
+ /* EVIDENCE-OF: R-24078-09375 Value is a NULL. */
pMem->flags = MEM_Null;
break;
}
- case 1: { /* 1-byte signed integer */
+ case 1: {
+ /* EVIDENCE-OF: R-44885-25196 Value is an 8-bit twos-complement
+ ** integer. */
pMem->u.i = ONE_BYTE_INT(buf);
pMem->flags = MEM_Int;
testcase( pMem->u.i<0 );
return 1;
}
case 2: { /* 2-byte signed integer */
+ /* EVIDENCE-OF: R-49794-35026 Value is a big-endian 16-bit
+ ** twos-complement integer. */
pMem->u.i = TWO_BYTE_INT(buf);
pMem->flags = MEM_Int;
testcase( pMem->u.i<0 );
return 2;
}
case 3: { /* 3-byte signed integer */
+ /* EVIDENCE-OF: R-37839-54301 Value is a big-endian 24-bit
+ ** twos-complement integer. */
pMem->u.i = THREE_BYTE_INT(buf);
pMem->flags = MEM_Int;
testcase( pMem->u.i<0 );
return 3;
}
case 4: { /* 4-byte signed integer */
+ /* EVIDENCE-OF: R-01849-26079 Value is a big-endian 32-bit
+ ** twos-complement integer. */
pMem->u.i = FOUR_BYTE_INT(buf);
pMem->flags = MEM_Int;
testcase( pMem->u.i<0 );
return 4;
}
case 5: { /* 6-byte signed integer */
+ /* EVIDENCE-OF: R-50385-09674 Value is a big-endian 48-bit
+ ** twos-complement integer. */
pMem->u.i = FOUR_BYTE_UINT(buf+2) + (((i64)1)<<32)*TWO_BYTE_INT(buf);
pMem->flags = MEM_Int;
testcase( pMem->u.i<0 );
@@ -66310,11 +67289,17 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet(
}
case 8: /* Integer 0 */
case 9: { /* Integer 1 */
+ /* EVIDENCE-OF: R-12976-22893 Value is the integer 0. */
+ /* EVIDENCE-OF: R-18143-12121 Value is the integer 1. */
pMem->u.i = serial_type-8;
pMem->flags = MEM_Int;
return 0;
}
default: {
+ /* EVIDENCE-OF: R-14606-31564 Value is a BLOB that is (N-12)/2 bytes in
+ ** length.
+ ** EVIDENCE-OF: R-28401-00140 Value is a string in the text encoding and
+ ** (N-13)/2 bytes in length. */
static const u16 aFlag[] = { MEM_Blob|MEM_Ephem, MEM_Str|MEM_Ephem };
pMem->z = (char *)buf;
pMem->n = (serial_type-12)/2;
@@ -66513,6 +67498,41 @@ debugCompareEnd:
}
#endif
+#if SQLITE_DEBUG
+/*
+** Count the number of fields (a.k.a. columns) in the record given by
+** pKey,nKey. The verify that this count is less than or equal to the
+** limit given by pKeyInfo->nField + pKeyInfo->nXField.
+**
+** If this constraint is not satisfied, it means that the high-speed
+** vdbeRecordCompareInt() and vdbeRecordCompareString() routines will
+** not work correctly. If this assert() ever fires, it probably means
+** that the KeyInfo.nField or KeyInfo.nXField values were computed
+** incorrectly.
+*/
+static void vdbeAssertFieldCountWithinLimits(
+ int nKey, const void *pKey, /* The record to verify */
+ const KeyInfo *pKeyInfo /* Compare size with this KeyInfo */
+){
+ int nField = 0;
+ u32 szHdr;
+ u32 idx;
+ u32 notUsed;
+ const unsigned char *aKey = (const unsigned char*)pKey;
+
+ if( CORRUPT_DB ) return;
+ idx = getVarint32(aKey, szHdr);
+ assert( szHdr<=nKey );
+ while( idxnField+pKeyInfo->nXField );
+}
+#else
+# define vdbeAssertFieldCountWithinLimits(A,B,C)
+#endif
+
/*
** Both *pMem1 and *pMem2 contain string values. Compare the two values
** using the collation sequence pColl. As usual, return a negative , zero
@@ -66924,6 +67944,7 @@ static int vdbeRecordCompareInt(
i64 v = pPKey2->aMem[0].u.i;
i64 lhs;
+ vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo);
assert( (*(u8*)pKey1)<=0x3F || CORRUPT_DB );
switch( serial_type ){
case 1: { /* 1-byte signed integer */
@@ -67011,6 +68032,7 @@ static int vdbeRecordCompareString(
int serial_type;
int res;
+ vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo);
getVarint32(&aKey1[1], serial_type);
if( serial_type<12 ){
res = pPKey2->r1; /* (pKey1/nKey1) is a number or a null */
@@ -67712,7 +68734,10 @@ static int doWalCallbacks(sqlite3 *db){
for(i=0; inDb; i++){
Btree *pBt = db->aDb[i].pBt;
if( pBt ){
- int nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt));
+ int nEntry;
+ sqlite3BtreeEnter(pBt);
+ nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt));
+ sqlite3BtreeLeave(pBt);
if( db->xWalCallback && nEntry>0 && rc==SQLITE_OK ){
rc = db->xWalCallback(db->pWalArg, db, db->aDb[i].zName, nEntry);
}
@@ -67892,7 +68917,6 @@ SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){
** sqlite3_errmsg() and sqlite3_errcode().
*/
const char *zErr = (const char *)sqlite3_value_text(db->pErr);
- assert( zErr!=0 || db->mallocFailed );
sqlite3DbFree(db, v->zErrMsg);
if( !db->mallocFailed ){
v->zErrMsg = sqlite3DbStrDup(db, zErr);
@@ -68278,11 +69302,19 @@ static const void *columnName(
const void *(*xFunc)(Mem*),
int useType
){
- const void *ret = 0;
- Vdbe *p = (Vdbe *)pStmt;
+ const void *ret;
+ Vdbe *p;
int n;
- sqlite3 *db = p->db;
-
+ sqlite3 *db;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( pStmt==0 ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
+ ret = 0;
+ p = (Vdbe *)pStmt;
+ db = p->db;
assert( db!=0 );
n = sqlite3_column_count(pStmt);
if( N=0 ){
@@ -68747,6 +69779,12 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){
*/
SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){
sqlite3_stmt *pNext;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(pDb) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
sqlite3_mutex_enter(pDb->mutex);
if( pStmt==0 ){
pNext = (sqlite3_stmt*)pDb->pVdbe;
@@ -68762,11 +69800,87 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){
*/
SQLITE_API int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){
Vdbe *pVdbe = (Vdbe*)pStmt;
- u32 v = pVdbe->aCounter[op];
+ u32 v;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !pStmt ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
+ v = pVdbe->aCounter[op];
if( resetFlag ) pVdbe->aCounter[op] = 0;
return (int)v;
}
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+/*
+** Return status data for a single loop within query pStmt.
+*/
+SQLITE_API int sqlite3_stmt_scanstatus(
+ sqlite3_stmt *pStmt, /* Prepared statement being queried */
+ int idx, /* Index of loop to report on */
+ int iScanStatusOp, /* Which metric to return */
+ void *pOut /* OUT: Write the answer here */
+){
+ Vdbe *p = (Vdbe*)pStmt;
+ ScanStatus *pScan;
+ if( idx<0 || idx>=p->nScan ) return 1;
+ pScan = &p->aScan[idx];
+ switch( iScanStatusOp ){
+ case SQLITE_SCANSTAT_NLOOP: {
+ *(sqlite3_int64*)pOut = p->anExec[pScan->addrLoop];
+ break;
+ }
+ case SQLITE_SCANSTAT_NVISIT: {
+ *(sqlite3_int64*)pOut = p->anExec[pScan->addrVisit];
+ break;
+ }
+ case SQLITE_SCANSTAT_EST: {
+ double r = 1.0;
+ LogEst x = pScan->nEst;
+ while( x<100 ){
+ x += 10;
+ r *= 0.5;
+ }
+ *(double*)pOut = r*sqlite3LogEstToInt(x);
+ break;
+ }
+ case SQLITE_SCANSTAT_NAME: {
+ *(const char**)pOut = pScan->zName;
+ break;
+ }
+ case SQLITE_SCANSTAT_EXPLAIN: {
+ if( pScan->addrExplain ){
+ *(const char**)pOut = p->aOp[ pScan->addrExplain ].p4.z;
+ }else{
+ *(const char**)pOut = 0;
+ }
+ break;
+ }
+ case SQLITE_SCANSTAT_SELECTID: {
+ if( pScan->addrExplain ){
+ *(int*)pOut = p->aOp[ pScan->addrExplain ].p1;
+ }else{
+ *(int*)pOut = -1;
+ }
+ break;
+ }
+ default: {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** Zero all counters associated with the sqlite3_stmt_scanstatus() data.
+*/
+SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){
+ Vdbe *p = (Vdbe*)pStmt;
+ memset(p->anExec, 0, p->nOp * sizeof(i64));
+}
+#endif /* SQLITE_ENABLE_STMT_SCANSTATUS */
+
/************** End of vdbeapi.c *********************************************/
/************** Begin file vdbetrace.c ***************************************/
/*
@@ -69652,6 +70766,9 @@ SQLITE_PRIVATE int sqlite3VdbeExec(
#endif
nVmStep++;
pOp = &aOp[pc];
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ if( p->anExec ) p->anExec[pc]++;
+#endif
/* Only allow tracing if SQLITE_DEBUG is defined.
*/
@@ -71677,7 +72794,10 @@ case OP_MakeRecord: {
nHdr += serial_type<=127 ? 1 : sqlite3VarintLen(serial_type);
}while( (--pRec)>=pData0 );
- /* Add the initial header varint and total the size */
+ /* EVIDENCE-OF: R-22564-11647 The header begins with a single varint
+ ** which determines the total number of bytes in the header. The varint
+ ** value is the size of the header in bytes including the size varint
+ ** itself. */
testcase( nHdr==126 );
testcase( nHdr==127 );
if( nHdr<=126 ){
@@ -71711,7 +72831,11 @@ case OP_MakeRecord: {
pRec = pData0;
do{
serial_type = pRec->uTemp;
+ /* EVIDENCE-OF: R-06529-47362 Following the size varint are one or more
+ ** additional varints, one per column. */
i += putVarint32(&zNewRecord[i], serial_type); /* serial type */
+ /* EVIDENCE-OF: R-64536-51728 The values for each column in the record
+ ** immediately follow the header. */
j += sqlite3VdbeSerialPut(&zNewRecord[j], pRec, serial_type); /* content */
}while( (++pRec)<=pLast );
assert( i==nHdr );
@@ -72846,10 +73970,10 @@ case OP_Found: { /* jump, in3 */
}else{
pIdxKey = sqlite3VdbeAllocUnpackedRecord(
pC->pKeyInfo, aTempRec, sizeof(aTempRec), &pFree
- );
+ );
if( pIdxKey==0 ) goto no_mem;
assert( pIn3->flags & MEM_Blob );
- assert( (pIn3->flags & MEM_Zero)==0 ); /* zeroblobs already expanded */
+ ExpandBlob(pIn3);
sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey);
}
pIdxKey->default_rc = 0;
@@ -72857,8 +73981,8 @@ case OP_Found: { /* jump, in3 */
/* For the OP_NoConflict opcode, take the jump if any of the
** input fields are NULL, since any key with a NULL will not
** conflict */
- for(ii=0; iinField; ii++){
+ if( pIdxKey->aMem[ii].flags & MEM_Null ){
pc = pOp->p2 - 1; VdbeBranchTaken(1,2);
break;
}
@@ -73543,9 +74667,9 @@ case OP_Sort: { /* jump */
**
** The next use of the Rowid or Column or Next instruction for P1
** will refer to the first entry in the database table or index.
-** If the table or index is empty and P2>0, then jump immediately to P2.
-** If P2 is 0 or if the table or index is not empty, fall through
-** to the following instruction.
+** If the table or index is empty, jump immediately to P2.
+** If the table or index is not empty, fall through to the following
+** instruction.
**
** This opcode leaves the cursor configured to move in forward order,
** from the beginning toward the end. In other words, the cursor is
@@ -74461,6 +75585,9 @@ case OP_Program: { /* jump */
pFrame->token = pProgram->token;
pFrame->aOnceFlag = p->aOnceFlag;
pFrame->nOnceFlag = p->nOnceFlag;
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ pFrame->anExec = p->anExec;
+#endif
pEnd = &VdbeFrameMem(pFrame)[pFrame->nChildMem];
for(pMem=VdbeFrameMem(pFrame); pMem!=pEnd; pMem++){
@@ -74478,6 +75605,7 @@ case OP_Program: { /* jump */
pFrame->pParent = p->pFrame;
pFrame->lastRowid = lastRowid;
pFrame->nChange = p->nChange;
+ pFrame->nDbChange = p->db->nChange;
p->nChange = 0;
p->pFrame = pFrame;
p->aMem = aMem = &VdbeFrameMem(pFrame)[-1];
@@ -74488,6 +75616,9 @@ case OP_Program: { /* jump */
p->nOp = pProgram->nOp;
p->aOnceFlag = (u8 *)&p->apCsr[p->nCursor];
p->nOnceFlag = pProgram->nOnce;
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ p->anExec = 0;
+#endif
pc = -1;
memset(p->aOnceFlag, 0, p->nOnceFlag);
@@ -74732,8 +75863,8 @@ case OP_AggFinal: {
/* Opcode: Checkpoint P1 P2 P3 * *
**
** Checkpoint database P1. This is a no-op if P1 is not currently in
-** WAL mode. Parameter P2 is one of SQLITE_CHECKPOINT_PASSIVE, FULL
-** or RESTART. Write 1 or 0 into mem[P3] if the checkpoint returns
+** WAL mode. Parameter P2 is one of SQLITE_CHECKPOINT_PASSIVE, FULL,
+** RESTART, or TRUNCATE. Write 1 or 0 into mem[P3] if the checkpoint returns
** SQLITE_BUSY or not, respectively. Write the number of pages in the
** WAL after the checkpoint into mem[P3+1] and the number of pages
** in the WAL that have been checkpointed after the checkpoint
@@ -74751,6 +75882,7 @@ case OP_Checkpoint: {
assert( pOp->p2==SQLITE_CHECKPOINT_PASSIVE
|| pOp->p2==SQLITE_CHECKPOINT_FULL
|| pOp->p2==SQLITE_CHECKPOINT_RESTART
+ || pOp->p2==SQLITE_CHECKPOINT_TRUNCATE
);
rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]);
if( rc==SQLITE_BUSY ){
@@ -75676,6 +76808,11 @@ SQLITE_API int sqlite3_blob_open(
Parse *pParse = 0;
Incrblob *pBlob = 0;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || ppBlob==0 || zTable==0 ){
+ return SQLITE_MISUSE_BKPT;
+ }
+#endif
flags = !!flags; /* flags = (flags ? 1 : 0); */
*ppBlob = 0;
@@ -75894,7 +77031,6 @@ static int blobReadWrite(
if( n<0 || iOffset<0 || (iOffset+n)>p->nByte ){
/* Request is out of range. Return a transient error. */
rc = SQLITE_ERROR;
- sqlite3Error(db, SQLITE_ERROR);
}else if( v==0 ){
/* If there is no statement handle, then the blob-handle has
** already been invalidated. Return SQLITE_ABORT in this case.
@@ -75912,10 +77048,10 @@ static int blobReadWrite(
sqlite3VdbeFinalize(v);
p->pStmt = 0;
}else{
- db->errCode = rc;
v->rc = rc;
}
}
+ sqlite3Error(db, rc);
rc = sqlite3ApiExit(db, rc);
sqlite3_mutex_leave(db->mutex);
return rc;
@@ -76092,7 +77228,7 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){
** The sorter is running in multi-threaded mode if (a) the library was built
** with pre-processor symbol SQLITE_MAX_WORKER_THREADS set to a value greater
** than zero, and (b) worker threads have been enabled at runtime by calling
-** sqlite3_config(SQLITE_CONFIG_WORKER_THREADS, ...).
+** "PRAGMA threads=N" with some value of N greater than 0.
**
** When Rewind() is called, any data remaining in memory is flushed to a
** final PMA. So at this point the data is stored in some number of sorted
@@ -76137,6 +77273,13 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){
# define SQLITE_DEBUG_SORTER_THREADS 1
#endif
+/*
+** Hard-coded maximum amount of data to accumulate in memory before flushing
+** to a level 0 PMA. The purpose of this limit is to prevent various integer
+** overflows. 512MiB.
+*/
+#define SQLITE_MAX_PMASZ (1<<29)
+
/*
** Private objects used by the sorter
*/
@@ -76431,9 +77574,6 @@ struct SorterRecord {
*/
#define SRVAL(p) ((void*)((SorterRecord*)(p) + 1))
-/* The minimum PMA size is set to this value multiplied by the database
-** page size in bytes. */
-#define SORTER_MIN_WORKING 10
/* Maximum number of PMAs that a single MergeEngine can merge */
#define SORTER_MAX_MERGE_COUNT 16
@@ -76832,16 +77972,15 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
}
if( !sqlite3TempInMemory(db) ){
- pSorter->mnPmaSize = SORTER_MIN_WORKING * pgsz;
+ u32 szPma = sqlite3GlobalConfig.szPma;
+ pSorter->mnPmaSize = szPma * pgsz;
mxCache = db->aDb[0].pSchema->cache_size;
- if( mxCachemxPmaSize = mxCache * pgsz;
+ if( mxCache<(int)szPma ) mxCache = (int)szPma;
+ pSorter->mxPmaSize = MIN((i64)mxCache*pgsz, SQLITE_MAX_PMASZ);
- /* If the application has not configure scratch memory using
- ** SQLITE_CONFIG_SCRATCH then we assume it is OK to do large memory
- ** allocations. If scratch memory has been configured, then assume
- ** large memory allocations should be avoided to prevent heap
- ** fragmentation.
+ /* EVIDENCE-OF: R-26747-61719 When the application provides any amount of
+ ** scratch memory using SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary
+ ** large heap allocations.
*/
if( sqlite3GlobalConfig.pScratch==0 ){
assert( pSorter->iMemory==0 );
@@ -77115,12 +78254,12 @@ SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){
*/
static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFd, i64 nByte){
if( nByte<=(i64)(db->nMaxSorterMmap) && pFd->pMethods->iVersion>=3 ){
- int rc = sqlite3OsTruncate(pFd, nByte);
- if( rc==SQLITE_OK ){
- void *p = 0;
- sqlite3OsFetch(pFd, 0, (int)nByte, &p);
- sqlite3OsUnfetch(pFd, 0, p);
- }
+ void *p = 0;
+ int chunksize = 4*1024;
+ sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_CHUNK_SIZE, &chunksize);
+ sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_SIZE_HINT, &nByte);
+ sqlite3OsFetch(pFd, 0, (int)nByte, &p);
+ sqlite3OsUnfetch(pFd, 0, p);
}
}
#else
@@ -78401,6 +79540,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr, in
}else
#endif
/*if( !pSorter->bUseThreads )*/ {
+ assert( pSorter->pMerger!=0 );
assert( pSorter->pMerger->pTask==(&pSorter->aTask[0]) );
rc = vdbeMergeEngineStep(pSorter->pMerger, pbEof);
}
@@ -79213,7 +80353,7 @@ SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){
** is a helper function - a callback for the tree walker.
*/
static int incrAggDepth(Walker *pWalker, Expr *pExpr){
- if( pExpr->op==TK_AGG_FUNCTION ) pExpr->op2 += pWalker->u.i;
+ if( pExpr->op==TK_AGG_FUNCTION ) pExpr->op2 += pWalker->u.n;
return WRC_Continue;
}
static void incrAggFunctionDepth(Expr *pExpr, int N){
@@ -79221,7 +80361,7 @@ static void incrAggFunctionDepth(Expr *pExpr, int N){
Walker w;
memset(&w, 0, sizeof(w));
w.xExprCallback = incrAggDepth;
- w.u.i = N;
+ w.u.n = N;
sqlite3WalkExpr(&w, pExpr);
}
}
@@ -79773,7 +80913,7 @@ static int exprProbability(Expr *p){
sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8);
assert( r>=0.0 );
if( r>1.0 ) return -1;
- return (int)(r*1000.0);
+ return (int)(r*134217728.0);
}
/*
@@ -79905,7 +81045,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
** EVIDENCE-OF: R-53436-40973 The likely(X) function is equivalent to
** likelihood(X,0.9375). */
/* TUNING: unlikely() probability is 0.0625. likely() is 0.9375 */
- pExpr->iTable = pDef->zName[0]=='u' ? 62 : 938;
+ pExpr->iTable = pDef->zName[0]=='u' ? 8388608 : 125829120;
}
}
#ifndef SQLITE_OMIT_AUTHORIZATION
@@ -81167,7 +82307,7 @@ SQLITE_PRIVATE Expr *sqlite3PExpr(
const Token *pToken /* Argument token */
){
Expr *p;
- if( op==TK_AND && pLeft && pRight ){
+ if( op==TK_AND && pLeft && pRight && pParse->nErr==0 ){
/* Take advantage of short-circuit false optimization for AND */
p = sqlite3ExprAnd(pParse->db, pLeft, pRight);
}else{
@@ -81862,20 +83002,24 @@ SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){
}
/*
-** These routines are Walker callbacks. Walker.u.pi is a pointer
-** to an integer. These routines are checking an expression to see
-** if it is a constant. Set *Walker.u.i to 0 if the expression is
-** not constant.
+** These routines are Walker callbacks used to check expressions to
+** see if they are "constant" for some definition of constant. The
+** Walker.eCode value determines the type of "constant" we are looking
+** for.
**
** These callback routines are used to implement the following:
**
-** sqlite3ExprIsConstant() pWalker->u.i==1
-** sqlite3ExprIsConstantNotJoin() pWalker->u.i==2
-** sqlite3ExprIsConstantOrFunction() pWalker->u.i==3 or 4
+** sqlite3ExprIsConstant() pWalker->eCode==1
+** sqlite3ExprIsConstantNotJoin() pWalker->eCode==2
+** sqlite3ExprRefOneTableOnly() pWalker->eCode==3
+** sqlite3ExprIsConstantOrFunction() pWalker->eCode==4 or 5
+**
+** In all cases, the callbacks set Walker.eCode=0 and abort if the expression
+** is found to not be a constant.
**
** The sqlite3ExprIsConstantOrFunction() is used for evaluating expressions
-** in a CREATE TABLE statement. The Walker.u.i value is 4 when parsing
-** an existing schema and 3 when processing a new statement. A bound
+** in a CREATE TABLE statement. The Walker.eCode value is 5 when parsing
+** an existing schema and 4 when processing a new statement. A bound
** parameter raises an error for new statements, but is silently converted
** to NULL for existing schemas. This allows sqlite_master tables that
** contain a bound parameter because they were generated by older versions
@@ -81884,23 +83028,25 @@ SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){
*/
static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){
- /* If pWalker->u.i is 2 then any term of the expression that comes from
- ** the ON or USING clauses of a join disqualifies the expression
+ /* If pWalker->eCode is 2 then any term of the expression that comes from
+ ** the ON or USING clauses of a left join disqualifies the expression
** from being considered constant. */
- if( pWalker->u.i==2 && ExprHasProperty(pExpr, EP_FromJoin) ){
- pWalker->u.i = 0;
+ if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_FromJoin) ){
+ pWalker->eCode = 0;
return WRC_Abort;
}
switch( pExpr->op ){
/* Consider functions to be constant if all their arguments are constant
- ** and either pWalker->u.i==3 or 4 or the function as the SQLITE_FUNC_CONST
- ** flag. */
+ ** and either pWalker->eCode==4 or 5 or the function has the
+ ** SQLITE_FUNC_CONST flag. */
case TK_FUNCTION:
- if( pWalker->u.i>=3 || ExprHasProperty(pExpr,EP_Constant) ){
+ if( pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_Constant) ){
return WRC_Continue;
+ }else{
+ pWalker->eCode = 0;
+ return WRC_Abort;
}
- /* Fall through */
case TK_ID:
case TK_COLUMN:
case TK_AGG_FUNCTION:
@@ -81909,18 +83055,22 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){
testcase( pExpr->op==TK_COLUMN );
testcase( pExpr->op==TK_AGG_FUNCTION );
testcase( pExpr->op==TK_AGG_COLUMN );
- pWalker->u.i = 0;
- return WRC_Abort;
+ if( pWalker->eCode==3 && pExpr->iTable==pWalker->u.iCur ){
+ return WRC_Continue;
+ }else{
+ pWalker->eCode = 0;
+ return WRC_Abort;
+ }
case TK_VARIABLE:
- if( pWalker->u.i==4 ){
+ if( pWalker->eCode==5 ){
/* Silently convert bound parameters that appear inside of CREATE
** statements into a NULL when parsing the CREATE statement text out
** of the sqlite_master table */
pExpr->op = TK_NULL;
- }else if( pWalker->u.i==3 ){
+ }else if( pWalker->eCode==4 ){
/* A bound parameter in a CREATE statement that originates from
** sqlite3_prepare() causes an error */
- pWalker->u.i = 0;
+ pWalker->eCode = 0;
return WRC_Abort;
}
/* Fall through */
@@ -81932,21 +83082,22 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){
}
static int selectNodeIsConstant(Walker *pWalker, Select *NotUsed){
UNUSED_PARAMETER(NotUsed);
- pWalker->u.i = 0;
+ pWalker->eCode = 0;
return WRC_Abort;
}
-static int exprIsConst(Expr *p, int initFlag){
+static int exprIsConst(Expr *p, int initFlag, int iCur){
Walker w;
memset(&w, 0, sizeof(w));
- w.u.i = initFlag;
+ w.eCode = initFlag;
w.xExprCallback = exprNodeIsConstant;
w.xSelectCallback = selectNodeIsConstant;
+ w.u.iCur = iCur;
sqlite3WalkExpr(&w, p);
- return w.u.i;
+ return w.eCode;
}
/*
-** Walk an expression tree. Return 1 if the expression is constant
+** Walk an expression tree. Return non-zero if the expression is constant
** and 0 if it involves variables or function calls.
**
** For the purposes of this function, a double-quoted string (ex: "abc")
@@ -81954,21 +83105,31 @@ static int exprIsConst(Expr *p, int initFlag){
** a constant.
*/
SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr *p){
- return exprIsConst(p, 1);
+ return exprIsConst(p, 1, 0);
}
/*
-** Walk an expression tree. Return 1 if the expression is constant
+** Walk an expression tree. Return non-zero if the expression is constant
** that does no originate from the ON or USING clauses of a join.
** Return 0 if it involves variables or function calls or terms from
** an ON or USING clause.
*/
SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){
- return exprIsConst(p, 2);
+ return exprIsConst(p, 2, 0);
}
/*
-** Walk an expression tree. Return 1 if the expression is constant
+** Walk an expression tree. Return non-zero if the expression constant
+** for any single row of the table with cursor iCur. In other words, the
+** expression must not refer to any non-deterministic function nor any
+** table other than iCur.
+*/
+SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){
+ return exprIsConst(p, 3, iCur);
+}
+
+/*
+** Walk an expression tree. Return non-zero if the expression is constant
** or a function call with constant arguments. Return and 0 if there
** are any variables.
**
@@ -81978,7 +83139,7 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){
*/
SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){
assert( isInit==0 || isInit==1 );
- return exprIsConst(p, 3+isInit);
+ return exprIsConst(p, 4+isInit, 0);
}
/*
@@ -83635,7 +84796,10 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
#ifndef SQLITE_OMIT_FLOATING_POINT
/* If the column has REAL affinity, it may currently be stored as an
- ** integer. Use OP_RealAffinity to make sure it is really real. */
+ ** integer. Use OP_RealAffinity to make sure it is really real.
+ **
+ ** EVIDENCE-OF: R-60985-57662 SQLite will convert the value back to
+ ** floating point when extracting it from the record. */
if( pExpr->iColumn>=0
&& pTab->aCol[pExpr->iColumn].affinity==SQLITE_AFF_REAL
){
@@ -84697,10 +85861,11 @@ static int exprSrcCount(Walker *pWalker, Expr *pExpr){
int i;
struct SrcCount *p = pWalker->u.pSrcCount;
SrcList *pSrc = p->pSrc;
- for(i=0; inSrc; i++){
+ int nSrc = pSrc ? pSrc->nSrc : 0;
+ for(i=0; iiTable==pSrc->a[i].iCursor ) break;
}
- if( inSrc ){
+ if( inThis++;
}else{
p->nOther++;
@@ -86278,7 +87443,7 @@ static void statInit(
p->mxSample = mxSample;
p->nPSample = (tRowcnt)(sqlite3_value_int64(argv[2])/(mxSample/3+1) + 1);
p->current.anLt = &p->current.anEq[nColUp];
- p->iPrn = nCol*0x689e962d ^ sqlite3_value_int(argv[2])*0xd0944565;
+ p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)sqlite3_value_int(argv[2]);
/* Set up the Stat4Accum.a[] and aBest[] arrays */
p->a = (struct Stat4Sample*)&p->current.anLt[nColUp];
@@ -87287,23 +88452,28 @@ static void decodeIntArray(
if( *z==' ' ) z++;
}
#ifndef SQLITE_ENABLE_STAT3_OR_STAT4
- assert( pIndex!=0 );
+ assert( pIndex!=0 ); {
#else
- if( pIndex )
+ if( pIndex ){
#endif
- while( z[0] ){
- if( sqlite3_strglob("unordered*", z)==0 ){
- pIndex->bUnordered = 1;
- }else if( sqlite3_strglob("sz=[0-9]*", z)==0 ){
- pIndex->szIdxRow = sqlite3LogEst(sqlite3Atoi(z+3));
- }
+ pIndex->bUnordered = 0;
+ pIndex->noSkipScan = 0;
+ while( z[0] ){
+ if( sqlite3_strglob("unordered*", z)==0 ){
+ pIndex->bUnordered = 1;
+ }else if( sqlite3_strglob("sz=[0-9]*", z)==0 ){
+ pIndex->szIdxRow = sqlite3LogEst(sqlite3Atoi(z+3));
+ }else if( sqlite3_strglob("noskipscan*", z)==0 ){
+ pIndex->noSkipScan = 1;
+ }
#ifdef SQLITE_ENABLE_COSTMULT
- else if( sqlite3_strglob("costmult=[0-9]*",z)==0 ){
- pIndex->pTable->costMult = sqlite3LogEst(sqlite3Atoi(z+9));
- }
+ else if( sqlite3_strglob("costmult=[0-9]*",z)==0 ){
+ pIndex->pTable->costMult = sqlite3LogEst(sqlite3Atoi(z+9));
+ }
#endif
- while( z[0]!=0 && z[0]!=' ' ) z++;
- while( z[0]==' ' ) z++;
+ while( z[0]!=0 && z[0]!=' ' ) z++;
+ while( z[0]==' ' ) z++;
+ }
}
}
@@ -87429,6 +88599,7 @@ static void initAvgEq(Index *pIdx){
nRow = pIdx->aiRowEst[0];
nDist100 = ((i64)100 * pIdx->aiRowEst[0]) / pIdx->aiRowEst[iCol+1];
}
+ pIdx->nRowEst0 = nRow;
/* Set nSum to the number of distinct (iCol+1) field prefixes that
** occur in the stat4 table for this index. Set sumEq to the sum of
@@ -87690,7 +88861,7 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){
/* Load the statistics from the sqlite_stat4 table. */
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- if( rc==SQLITE_OK ){
+ if( rc==SQLITE_OK && OptimizationEnabled(db, SQLITE_Stat34) ){
int lookasideEnabled = db->lookaside.bEnabled;
db->lookaside.bEnabled = 0;
rc = loadStat4(db, sInfo.zDatabase);
@@ -87865,6 +89036,7 @@ static void attachFunc(
"attached databases must use the same text encoding as main database");
rc = SQLITE_ERROR;
}
+ sqlite3BtreeEnter(aNew->pBt);
pPager = sqlite3BtreePager(aNew->pBt);
sqlite3PagerLockingMode(pPager, db->dfltLockMode);
sqlite3BtreeSecureDelete(aNew->pBt,
@@ -87872,6 +89044,7 @@ static void attachFunc(
#ifndef SQLITE_OMIT_PAGER_PRAGMAS
sqlite3BtreeSetPagerFlags(aNew->pBt, 3 | (db->flags & PAGER_FLAGS_MASK));
#endif
+ sqlite3BtreeLeave(aNew->pBt);
}
aNew->safety_level = 3;
aNew->zName = sqlite3DbStrDup(db, zName);
@@ -88372,6 +89545,9 @@ SQLITE_API int sqlite3_set_authorizer(
int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
void *pArg
){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
db->xAuth = (sqlite3_xauth)xAuth;
db->pAuthArg = pArg;
@@ -88866,7 +90042,11 @@ SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){
SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){
Table *p = 0;
int i;
- assert( zName!=0 );
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || zName==0 ) return 0;
+#endif
+
/* All mutexes are required for schema access. Make sure we hold them. */
assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) );
#if SQLITE_USER_AUTHENTICATION
@@ -88990,7 +90170,6 @@ static void freeIndex(sqlite3 *db, Index *p){
#ifndef SQLITE_OMIT_ANALYZE
sqlite3DeleteIndexSamples(db, p);
#endif
- if( db==0 || db->pnBytesFreed==0 ) sqlite3KeyInfoUnref(p->pKeyInfo);
sqlite3ExprDelete(db, p->pPartIdxWhere);
sqlite3DbFree(db, p->zColAff);
if( p->isResized ) sqlite3DbFree(db, p->azColl);
@@ -90269,6 +91448,19 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
pTab->iPKey = -1;
}else{
pPk = sqlite3PrimaryKeyIndex(pTab);
+ /*
+ ** Remove all redundant columns from the PRIMARY KEY. For example, change
+ ** "PRIMARY KEY(a,b,a,b,c,b,c,d)" into just "PRIMARY KEY(a,b,c,d)". Later
+ ** code assumes the PRIMARY KEY contains no repeated columns.
+ */
+ for(i=j=1; inKeyCol; i++){
+ if( hasColumn(pPk->aiColumn, j, pPk->aiColumn[i]) ){
+ pPk->nColumn--;
+ }else{
+ pPk->aiColumn[j++] = pPk->aiColumn[i];
+ }
+ }
+ pPk->nKeyCol = j;
}
pPk->isCovering = 1;
assert( pPk!=0 );
@@ -92745,40 +93937,31 @@ SQLITE_PRIVATE void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){
** when it has finished using it.
*/
SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){
+ int i;
+ int nCol = pIdx->nColumn;
+ int nKey = pIdx->nKeyCol;
+ KeyInfo *pKey;
if( pParse->nErr ) return 0;
-#ifndef SQLITE_OMIT_SHARED_CACHE
- if( pIdx->pKeyInfo && pIdx->pKeyInfo->db!=pParse->db ){
- sqlite3KeyInfoUnref(pIdx->pKeyInfo);
- pIdx->pKeyInfo = 0;
+ if( pIdx->uniqNotNull ){
+ pKey = sqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey);
+ }else{
+ pKey = sqlite3KeyInfoAlloc(pParse->db, nCol, 0);
}
-#endif
- if( pIdx->pKeyInfo==0 ){
- int i;
- int nCol = pIdx->nColumn;
- int nKey = pIdx->nKeyCol;
- KeyInfo *pKey;
- if( pIdx->uniqNotNull ){
- pKey = sqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey);
- }else{
- pKey = sqlite3KeyInfoAlloc(pParse->db, nCol, 0);
+ if( pKey ){
+ assert( sqlite3KeyInfoIsWriteable(pKey) );
+ for(i=0; iazColl[i];
+ assert( zColl!=0 );
+ pKey->aColl[i] = strcmp(zColl,"BINARY")==0 ? 0 :
+ sqlite3LocateCollSeq(pParse, zColl);
+ pKey->aSortOrder[i] = pIdx->aSortOrder[i];
}
- if( pKey ){
- assert( sqlite3KeyInfoIsWriteable(pKey) );
- for(i=0; iazColl[i];
- assert( zColl!=0 );
- pKey->aColl[i] = strcmp(zColl,"BINARY")==0 ? 0 :
- sqlite3LocateCollSeq(pParse, zColl);
- pKey->aSortOrder[i] = pIdx->aSortOrder[i];
- }
- if( pParse->nErr ){
- sqlite3KeyInfoUnref(pKey);
- }else{
- pIdx->pKeyInfo = pKey;
- }
+ if( pParse->nErr ){
+ sqlite3KeyInfoUnref(pKey);
+ pKey = 0;
}
}
- return sqlite3KeyInfoRef(pIdx->pKeyInfo);
+ return pKey;
}
#ifndef SQLITE_OMIT_CTE
@@ -93559,8 +94742,8 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
WhereInfo *pWInfo; /* Information about the WHERE clause */
Index *pIdx; /* For looping over indices of the table */
int iTabCur; /* Cursor number for the table */
- int iDataCur; /* VDBE cursor for the canonical data source */
- int iIdxCur; /* Cursor number of the first index */
+ int iDataCur = 0; /* VDBE cursor for the canonical data source */
+ int iIdxCur = 0; /* Cursor number of the first index */
int nIdx; /* Number of indices */
sqlite3 *db; /* Main database structure */
AuthContext sContext; /* Authorization context */
@@ -94330,8 +95513,8 @@ static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
default: {
/* Because sqlite3_value_double() returns 0.0 if the argument is not
** something that can be converted into a number, we have:
- ** IMP: R-57326-31541 Abs(X) return 0.0 if X is a string or blob that
- ** cannot be converted to a numeric value.
+ ** IMP: R-01992-00519 Abs(X) returns 0.0 if X is a string or blob
+ ** that cannot be converted to a numeric value.
*/
double rVal = sqlite3_value_double(argv[0]);
if( rVal<0 ) rVal = -rVal;
@@ -96399,7 +97582,7 @@ static void fkLookupParent(
OE_Abort, 0, P4_STATIC, P5_ConstraintFK);
}else{
if( nIncr>0 && pFKey->isDeferred==0 ){
- sqlite3ParseToplevel(pParse)->mayAbort = 1;
+ sqlite3MayAbort(pParse);
}
sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
}
@@ -96471,6 +97654,10 @@ static Expr *exprTableColumn(
** code for an SQL UPDATE operation, this function may be called twice -
** once to "delete" the old row and once to "insert" the new row.
**
+** Parameter nIncr is passed -1 when inserting a row (as this may decrease
+** the number of FK violations in the db) or +1 when deleting one (as this
+** may increase the number of FK constraint problems).
+**
** The code generated by this function scans through the rows in the child
** table that correspond to the parent table row being deleted or inserted.
** For each child row found, one of the following actions is taken:
@@ -96587,13 +97774,9 @@ static void fkScanChildren(
sqlite3ResolveExprNames(&sNameContext, pWhere);
/* Create VDBE to loop through the entries in pSrc that match the WHERE
- ** clause. If the constraint is not deferred, throw an exception for
- ** each row found. Otherwise, for deferred constraints, increment the
- ** deferred constraint counter by nIncr for each row selected. */
+ ** clause. For each row found, increment either the deferred or immediate
+ ** foreign key constraint counter. */
pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0);
- if( nIncr>0 && pFKey->isDeferred==0 ){
- sqlite3ParseToplevel(pParse)->mayAbort = 1;
- }
sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
if( pWInfo ){
sqlite3WhereEnd(pWInfo);
@@ -96772,6 +97955,24 @@ static int fkParentIsModified(
return 0;
}
+/*
+** Return true if the parser passed as the first argument is being
+** used to code a trigger that is really a "SET NULL" action belonging
+** to trigger pFKey.
+*/
+static int isSetNullAction(Parse *pParse, FKey *pFKey){
+ Parse *pTop = sqlite3ParseToplevel(pParse);
+ if( pTop->pTriggerPrg ){
+ Trigger *p = pTop->pTriggerPrg->pTrigger;
+ if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull)
+ || (p==pFKey->apTrigger[1] && pFKey->aAction[1]==OE_SetNull)
+ ){
+ return 1;
+ }
+ }
+ return 0;
+}
+
/*
** This function is called when inserting, deleting or updating a row of
** table pTab to generate VDBE code to perform foreign key constraint
@@ -96824,7 +98025,7 @@ SQLITE_PRIVATE void sqlite3FkCheck(
int *aiCol;
int iCol;
int i;
- int isIgnore = 0;
+ int bIgnore = 0;
if( aChange
&& sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0
@@ -96883,7 +98084,7 @@ SQLITE_PRIVATE void sqlite3FkCheck(
int rcauth;
char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName;
rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb);
- isIgnore = (rcauth==SQLITE_IGNORE);
+ bIgnore = (rcauth==SQLITE_IGNORE);
}
#endif
}
@@ -96898,12 +98099,18 @@ SQLITE_PRIVATE void sqlite3FkCheck(
/* A row is being removed from the child table. Search for the parent.
** If the parent does not exist, removing the child row resolves an
** outstanding foreign key constraint violation. */
- fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1,isIgnore);
+ fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1, bIgnore);
}
- if( regNew!=0 ){
+ if( regNew!=0 && !isSetNullAction(pParse, pFKey) ){
/* A row is being added to the child table. If a parent row cannot
- ** be found, adding the child row has violated the FK constraint. */
- fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1,isIgnore);
+ ** be found, adding the child row has violated the FK constraint.
+ **
+ ** If this operation is being performed as part of a trigger program
+ ** that is actually a "SET NULL" action belonging to this very
+ ** foreign key, then omit this scan altogether. As all child key
+ ** values are guaranteed to be NULL, it is not possible for adding
+ ** this row to cause an FK violation. */
+ fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1, bIgnore);
}
sqlite3DbFree(db, aiFree);
@@ -96924,8 +98131,8 @@ SQLITE_PRIVATE void sqlite3FkCheck(
&& !pParse->pToplevel && !pParse->isMultiWrite
){
assert( regOld==0 && regNew!=0 );
- /* Inserting a single row into a parent table cannot cause an immediate
- ** foreign key violation. So do nothing in this case. */
+ /* Inserting a single row into a parent table cannot cause (or fix)
+ ** an immediate foreign key violation. So do nothing in this case. */
continue;
}
@@ -96949,13 +98156,28 @@ SQLITE_PRIVATE void sqlite3FkCheck(
fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regNew, -1);
}
if( regOld!=0 ){
- /* If there is a RESTRICT action configured for the current operation
- ** on the parent table of this FK, then throw an exception
- ** immediately if the FK constraint is violated, even if this is a
- ** deferred trigger. That's what RESTRICT means. To defer checking
- ** the constraint, the FK should specify NO ACTION (represented
- ** using OE_None). NO ACTION is the default. */
+ int eAction = pFKey->aAction[aChange!=0];
fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1);
+ /* If this is a deferred FK constraint, or a CASCADE or SET NULL
+ ** action applies, then any foreign key violations caused by
+ ** removing the parent key will be rectified by the action trigger.
+ ** So do not set the "may-abort" flag in this case.
+ **
+ ** Note 1: If the FK is declared "ON UPDATE CASCADE", then the
+ ** may-abort flag will eventually be set on this statement anyway
+ ** (when this function is called as part of processing the UPDATE
+ ** within the action trigger).
+ **
+ ** Note 2: At first glance it may seem like SQLite could simply omit
+ ** all OP_FkCounter related scans when either CASCADE or SET NULL
+ ** applies. The trouble starts if the CASCADE or SET NULL action
+ ** trigger causes other triggers or action rules attached to the
+ ** child table to fire. In these cases the fk constraint counters
+ ** might be set incorrectly if any OP_FkCounter related scans are
+ ** omitted. */
+ if( !pFKey->isDeferred && eAction!=OE_Cascade && eAction!=OE_SetNull ){
+ sqlite3MayAbort(pParse);
+ }
}
pItem->zName = 0;
sqlite3SrcListDelete(db, pSrc);
@@ -100049,7 +101271,6 @@ struct sqlite3_api_routines {
# define sqlite3_column_table_name16 0
# define sqlite3_column_origin_name 0
# define sqlite3_column_origin_name16 0
-# define sqlite3_table_column_metadata 0
#endif
#ifdef SQLITE_OMIT_AUTHORIZATION
@@ -100859,6 +102080,7 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){
#define PragTyp_LOCK_STATUS 40
#define PragTyp_PARSER_TRACE 41
#define PragFlag_NeedSchema 0x01
+#define PragFlag_ReadOnly 0x02
static const struct sPragmaNames {
const char *const zName; /* Name of pragma */
u8 ePragTyp; /* PragTyp_XXX value */
@@ -100875,7 +102097,7 @@ static const struct sPragmaNames {
{ /* zName: */ "application_id",
/* ePragTyp: */ PragTyp_HEADER_VALUE,
/* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ /* iArg: */ BTREE_APPLICATION_ID },
#endif
#if !defined(SQLITE_OMIT_AUTOVACUUM)
{ /* zName: */ "auto_vacuum",
@@ -100941,6 +102163,12 @@ static const struct sPragmaNames {
/* ePragFlag: */ 0,
/* iArg: */ 0 },
#endif
+#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
+ { /* zName: */ "data_version",
+ /* ePragTyp: */ PragTyp_HEADER_VALUE,
+ /* ePragFlag: */ PragFlag_ReadOnly,
+ /* iArg: */ BTREE_DATA_VERSION },
+#endif
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
{ /* zName: */ "database_list",
/* ePragTyp: */ PragTyp_DATABASE_LIST,
@@ -100996,8 +102224,8 @@ static const struct sPragmaNames {
#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
{ /* zName: */ "freelist_count",
/* ePragTyp: */ PragTyp_HEADER_VALUE,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ /* ePragFlag: */ PragFlag_ReadOnly,
+ /* iArg: */ BTREE_FREE_PAGE_COUNT },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
{ /* zName: */ "full_column_names",
@@ -101149,7 +102377,7 @@ static const struct sPragmaNames {
{ /* zName: */ "schema_version",
/* ePragTyp: */ PragTyp_HEADER_VALUE,
/* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ /* iArg: */ BTREE_SCHEMA_VERSION },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
{ /* zName: */ "secure_delete",
@@ -101215,7 +102443,7 @@ static const struct sPragmaNames {
{ /* zName: */ "user_version",
/* ePragTyp: */ PragTyp_HEADER_VALUE,
/* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ /* iArg: */ BTREE_USER_VERSION },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
#if defined(SQLITE_DEBUG)
@@ -101258,7 +102486,7 @@ static const struct sPragmaNames {
/* iArg: */ SQLITE_WriteSchema|SQLITE_RecoveryMode },
#endif
};
-/* Number of pragmas: 57 on by default, 70 total. */
+/* Number of pragmas: 58 on by default, 71 total. */
/* End of the automatically generated pragma table.
***************************************************************************/
@@ -101508,7 +102736,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
Token *pId; /* Pointer to token */
char *aFcntl[4]; /* Argument to SQLITE_FCNTL_PRAGMA */
int iDb; /* Database index for */
- int lwr, upr, mid; /* Binary search bounds */
+ int lwr, upr, mid = 0; /* Binary search bounds */
int rc; /* return value form SQLITE_FCNTL_PRAGMA */
sqlite3 *db = pParse->db; /* The database connection */
Db *pDb; /* The specific database being pragmaed */
@@ -102868,7 +104096,8 @@ SQLITE_PRIVATE void sqlite3Pragma(
){
for(pEnc=&encnames[0]; pEnc->zName; pEnc++){
if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){
- ENC(pParse->db) = pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE;
+ SCHEMA_ENC(db) = ENC(db) =
+ pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE;
break;
}
}
@@ -102913,24 +104142,9 @@ SQLITE_PRIVATE void sqlite3Pragma(
** applications for any purpose.
*/
case PragTyp_HEADER_VALUE: {
- int iCookie; /* Cookie index. 1 for schema-cookie, 6 for user-cookie. */
+ int iCookie = aPragmaNames[mid].iArg; /* Which cookie to read or write */
sqlite3VdbeUsesBtree(v, iDb);
- switch( zLeft[0] ){
- case 'a': case 'A':
- iCookie = BTREE_APPLICATION_ID;
- break;
- case 'f': case 'F':
- iCookie = BTREE_FREE_PAGE_COUNT;
- break;
- case 's': case 'S':
- iCookie = BTREE_SCHEMA_VERSION;
- break;
- default:
- iCookie = BTREE_USER_VERSION;
- break;
- }
-
- if( zRight && iCookie!=BTREE_FREE_PAGE_COUNT ){
+ if( zRight && (aPragmaNames[mid].mPragFlag & PragFlag_ReadOnly)==0 ){
/* Write the specified cookie value */
static const VdbeOpList setCookie[] = {
{ OP_Transaction, 0, 1, 0}, /* 0 */
@@ -102983,7 +104197,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
#ifndef SQLITE_OMIT_WAL
/*
- ** PRAGMA [database.]wal_checkpoint = passive|full|restart
+ ** PRAGMA [database.]wal_checkpoint = passive|full|restart|truncate
**
** Checkpoint the database.
*/
@@ -102995,6 +104209,8 @@ SQLITE_PRIVATE void sqlite3Pragma(
eMode = SQLITE_CHECKPOINT_FULL;
}else if( sqlite3StrICmp(zRight, "restart")==0 ){
eMode = SQLITE_CHECKPOINT_RESTART;
+ }else if( sqlite3StrICmp(zRight, "truncate")==0 ){
+ eMode = SQLITE_CHECKPOINT_TRUNCATE;
}
}
sqlite3VdbeSetNumCols(v, 3);
@@ -103574,9 +104790,11 @@ SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){
int commit_internal = !(db->flags&SQLITE_InternChanges);
assert( sqlite3_mutex_held(db->mutex) );
+ assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) );
assert( db->init.busy==0 );
rc = SQLITE_OK;
db->init.busy = 1;
+ ENC(db) = SCHEMA_ENC(db);
for(i=0; rc==SQLITE_OK && inDb; i++){
if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue;
rc = sqlite3InitOne(db, i, pzErrMsg);
@@ -103889,9 +105107,12 @@ static int sqlite3LockAndPrepare(
const char **pzTail /* OUT: End of parsed string */
){
int rc;
- assert( ppStmt!=0 );
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( ppStmt==0 ) return SQLITE_MISUSE_BKPT;
+#endif
*ppStmt = 0;
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !sqlite3SafetyCheckOk(db)||zSql==0 ){
return SQLITE_MISUSE_BKPT;
}
sqlite3_mutex_enter(db->mutex);
@@ -103998,9 +105219,11 @@ static int sqlite3Prepare16(
const char *zTail8 = 0;
int rc = SQLITE_OK;
- assert( ppStmt );
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( ppStmt==0 ) return SQLITE_MISUSE_BKPT;
+#endif
*ppStmt = 0;
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !sqlite3SafetyCheckOk(db)||zSql==0 ){
return SQLITE_MISUSE_BKPT;
}
if( nBytes>=0 ){
@@ -104126,20 +105349,25 @@ struct SortCtx {
#define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */
/*
-** Delete all the content of a Select structure but do not deallocate
-** the select structure itself.
+** Delete all the content of a Select structure. Deallocate the structure
+** itself only if bFree is true.
*/
-static void clearSelect(sqlite3 *db, Select *p){
- sqlite3ExprListDelete(db, p->pEList);
- sqlite3SrcListDelete(db, p->pSrc);
- sqlite3ExprDelete(db, p->pWhere);
- sqlite3ExprListDelete(db, p->pGroupBy);
- sqlite3ExprDelete(db, p->pHaving);
- sqlite3ExprListDelete(db, p->pOrderBy);
- sqlite3SelectDelete(db, p->pPrior);
- sqlite3ExprDelete(db, p->pLimit);
- sqlite3ExprDelete(db, p->pOffset);
- sqlite3WithDelete(db, p->pWith);
+static void clearSelect(sqlite3 *db, Select *p, int bFree){
+ while( p ){
+ Select *pPrior = p->pPrior;
+ sqlite3ExprListDelete(db, p->pEList);
+ sqlite3SrcListDelete(db, p->pSrc);
+ sqlite3ExprDelete(db, p->pWhere);
+ sqlite3ExprListDelete(db, p->pGroupBy);
+ sqlite3ExprDelete(db, p->pHaving);
+ sqlite3ExprListDelete(db, p->pOrderBy);
+ sqlite3ExprDelete(db, p->pLimit);
+ sqlite3ExprDelete(db, p->pOffset);
+ sqlite3WithDelete(db, p->pWith);
+ if( bFree ) sqlite3DbFree(db, p);
+ p = pPrior;
+ bFree = 1;
+ }
}
/*
@@ -104198,8 +105426,7 @@ SQLITE_PRIVATE Select *sqlite3SelectNew(
pNew->addrOpenEphm[0] = -1;
pNew->addrOpenEphm[1] = -1;
if( db->mallocFailed ) {
- clearSelect(db, pNew);
- if( pNew!=&standin ) sqlite3DbFree(db, pNew);
+ clearSelect(db, pNew, pNew!=&standin);
pNew = 0;
}else{
assert( pNew->pSrc!=0 || pParse->nErr>0 );
@@ -104224,10 +105451,7 @@ SQLITE_PRIVATE void sqlite3SelectSetName(Select *p, const char *zName){
** Delete the given Select structure and all of its substructures.
*/
SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3 *db, Select *p){
- if( p ){
- clearSelect(db, p);
- sqlite3DbFree(db, p);
- }
+ clearSelect(db, p, 1);
}
/*
@@ -104610,7 +105834,9 @@ static void pushOntoSorter(
pKI = pOp->p4.pKeyInfo;
memset(pKI->aSortOrder, 0, pKI->nField); /* Makes OP_Jump below testable */
sqlite3VdbeChangeP4(v, -1, (char*)pKI, P4_KEYINFO);
- pOp->p4.pKeyInfo = keyInfoFromExprList(pParse, pSort->pOrderBy, nOBSat, 1);
+ testcase( pKI->nXField>2 );
+ pOp->p4.pKeyInfo = keyInfoFromExprList(pParse, pSort->pOrderBy, nOBSat,
+ pKI->nXField-1);
addrJmp = sqlite3VdbeCurrentAddr(v);
sqlite3VdbeAddOp3(v, OP_Jump, addrJmp+1, 0, addrJmp+1); VdbeCoverage(v);
pSort->labelBkOut = sqlite3VdbeMakeLabel(v);
@@ -105121,7 +106347,7 @@ static KeyInfo *keyInfoFromExprList(
int i;
nExpr = pList->nExpr;
- pInfo = sqlite3KeyInfoAlloc(db, nExpr+nExtra-iStart, 1);
+ pInfo = sqlite3KeyInfoAlloc(db, nExpr-iStart, nExtra+1);
if( pInfo ){
assert( sqlite3KeyInfoIsWriteable(pInfo) );
for(i=iStart, pItem=pList->a+iStart; iselFlags & SF_Values ){
+ sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms");
+ }else{
+ sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s"
+ " do not have the same number of result columns", selectOpName(p->op));
+ }
+}
+
+/*
+** Handle the special case of a compound-select that originates from a
+** VALUES clause. By handling this as a special case, we avoid deep
+** recursion, and thus do not need to enforce the SQLITE_LIMIT_COMPOUND_SELECT
+** on a VALUES clause.
+**
+** Because the Select object originates from a VALUES clause:
+** (1) It has no LIMIT or OFFSET
+** (2) All terms are UNION ALL
+** (3) There is no ORDER BY clause
+*/
+static int multiSelectValues(
+ Parse *pParse, /* Parsing context */
+ Select *p, /* The right-most of SELECTs to be coded */
+ SelectDest *pDest /* What to do with query results */
+){
+ Select *pPrior;
+ int nExpr = p->pEList->nExpr;
+ int nRow = 1;
+ int rc = 0;
+ assert( p->pNext==0 );
+ assert( p->selFlags & SF_AllValues );
+ do{
+ assert( p->selFlags & SF_Values );
+ assert( p->op==TK_ALL || (p->op==TK_SELECT && p->pPrior==0) );
+ assert( p->pLimit==0 );
+ assert( p->pOffset==0 );
+ if( p->pEList->nExpr!=nExpr ){
+ selectWrongNumTermsError(pParse, p);
+ return 1;
+ }
+ if( p->pPrior==0 ) break;
+ assert( p->pPrior->pNext==p );
+ p = p->pPrior;
+ nRow++;
+ }while(1);
+ while( p ){
+ pPrior = p->pPrior;
+ p->pPrior = 0;
+ rc = sqlite3Select(pParse, p, pDest);
+ p->pPrior = pPrior;
+ if( rc ) break;
+ p->nSelectRow = nRow;
+ p = p->pNext;
+ }
+ return rc;
+}
/*
** This routine is called to process a compound query form from
@@ -106224,17 +107510,19 @@ static int multiSelect(
dest.eDest = SRT_Table;
}
+ /* Special handling for a compound-select that originates as a VALUES clause.
+ */
+ if( p->selFlags & SF_AllValues ){
+ rc = multiSelectValues(pParse, p, &dest);
+ goto multi_select_end;
+ }
+
/* Make sure all SELECTs in the statement have the same number of elements
** in their result sets.
*/
assert( p->pEList && pPrior->pEList );
if( p->pEList->nExpr!=pPrior->pEList->nExpr ){
- if( p->selFlags & SF_Values ){
- sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms");
- }else{
- sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s"
- " do not have the same number of result columns", selectOpName(p->op));
- }
+ selectWrongNumTermsError(pParse, p);
rc = 1;
goto multi_select_end;
}
@@ -108120,7 +109408,9 @@ static int selectExpander(Walker *pWalker, Select *p){
}
pTabList = p->pSrc;
pEList = p->pEList;
- sqlite3WithPush(pParse, findRightmost(p)->pWith, 0);
+ if( pWalker->xSelectCallback2==selectPopWith ){
+ sqlite3WithPush(pParse, findRightmost(p)->pWith, 0);
+ }
/* Make sure cursor numbers have been assigned to all entries in
** the FROM clause of the SELECT statement.
@@ -108411,7 +109701,9 @@ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){
sqlite3WalkSelect(&w, pSelect);
}
w.xSelectCallback = selectExpander;
- w.xSelectCallback2 = selectPopWith;
+ if( (pSelect->selFlags & SF_AllValues)==0 ){
+ w.xSelectCallback2 = selectPopWith;
+ }
sqlite3WalkSelect(&w, pSelect);
}
@@ -108897,7 +110189,7 @@ SQLITE_PRIVATE int sqlite3Select(
**
** is transformed to:
**
- ** SELECT xyz FROM ... GROUP BY xyz
+ ** SELECT xyz FROM ... GROUP BY xyz ORDER BY xyz
**
** The second form is preferred as a single index (or temp-table) may be
** used for both the ORDER BY and DISTINCT processing. As originally
@@ -108910,7 +110202,6 @@ SQLITE_PRIVATE int sqlite3Select(
p->selFlags &= ~SF_Distinct;
p->pGroupBy = sqlite3ExprListDup(db, p->pEList, 0);
pGroupBy = p->pGroupBy;
- sSort.pOrderBy = 0;
/* Notice that even thought SF_Distinct has been cleared from p->selFlags,
** the sDistinct.isTnct is still set. Hence, isTnct represents the
** original setting of the SF_Distinct flag, not the current setting */
@@ -108926,7 +110217,7 @@ SQLITE_PRIVATE int sqlite3Select(
*/
if( sSort.pOrderBy ){
KeyInfo *pKeyInfo;
- pKeyInfo = keyInfoFromExprList(pParse, sSort.pOrderBy, 0, 0);
+ pKeyInfo = keyInfoFromExprList(pParse, sSort.pOrderBy, 0, pEList->nExpr);
sSort.iECursor = pParse->nTab++;
sSort.addrSortIndex =
sqlite3VdbeAddOp4(v, OP_OpenEphemeral,
@@ -109100,7 +110391,7 @@ SQLITE_PRIVATE int sqlite3Select(
** will be converted into a Noop.
*/
sAggInfo.sortingIdx = pParse->nTab++;
- pKeyInfo = keyInfoFromExprList(pParse, pGroupBy, 0, 0);
+ pKeyInfo = keyInfoFromExprList(pParse, pGroupBy, 0, sAggInfo.nColumn);
addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen,
sAggInfo.sortingIdx, sAggInfo.nSortingColumn,
0, (char*)pKeyInfo, P4_KEYINFO);
@@ -109713,6 +111004,9 @@ SQLITE_API int sqlite3_get_table(
int rc;
TabResult res;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || pazResult==0 ) return SQLITE_MISUSE_BKPT;
+#endif
*pazResult = 0;
if( pnColumn ) *pnColumn = 0;
if( pnRow ) *pnRow = 0;
@@ -111776,7 +113070,7 @@ static int execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql){
** overwriting the database with the vacuumed content.
**
** Only 1x temporary space and only 1x writes would be required if
-** the copy of step (3) were replace by deleting the original database
+** the copy of step (3) were replaced by deleting the original database
** and renaming the transient database as the original. But that will
** not work if other processes are attached to the original database.
** And a power loss in between deleting the original and renaming the
@@ -112134,6 +113428,9 @@ SQLITE_API int sqlite3_create_module(
const sqlite3_module *pModule, /* The definition of the module */
void *pAux /* Context pointer for xCreate/xConnect */
){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
+#endif
return createModule(db, zName, pModule, pAux, 0);
}
@@ -112147,6 +113444,9 @@ SQLITE_API int sqlite3_create_module_v2(
void *pAux, /* Context pointer for xCreate/xConnect */
void (*xDestroy)(void *) /* Module destructor function */
){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
+#endif
return createModule(db, zName, pModule, pAux, xDestroy);
}
@@ -112379,7 +113679,12 @@ SQLITE_PRIVATE void sqlite3VtabBeginParse(
addModuleArgument(db, pTable, sqlite3NameFromToken(db, pModuleName));
addModuleArgument(db, pTable, 0);
addModuleArgument(db, pTable, sqlite3DbStrDup(db, pTable->zName));
- pParse->sNameToken.n = (int)(&pModuleName->z[pModuleName->n] - pName1->z);
+ assert( (pParse->sNameToken.z==pName2->z && pName2->z!=0)
+ || (pParse->sNameToken.z==pName1->z && pName2->z==0)
+ );
+ pParse->sNameToken.n = (int)(
+ &pModuleName->z[pModuleName->n] - pParse->sNameToken.z
+ );
#ifndef SQLITE_OMIT_AUTHORIZATION
/* Creating a virtual table invokes the authorization callback twice.
@@ -112751,6 +114056,9 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
Table *pTab;
char *zErr = 0;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
if( !db->pVtabCtx || !(pTab = db->pVtabCtx->pTab) ){
sqlite3Error(db, SQLITE_MISUSE);
@@ -113107,6 +114415,9 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *db){
static const unsigned char aMap[] = {
SQLITE_ROLLBACK, SQLITE_ABORT, SQLITE_FAIL, SQLITE_IGNORE, SQLITE_REPLACE
};
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
assert( OE_Rollback==1 && OE_Abort==2 && OE_Fail==3 );
assert( OE_Ignore==4 && OE_Replace==5 );
assert( db->vtabOnConflict>=1 && db->vtabOnConflict<=5 );
@@ -113122,8 +114433,10 @@ SQLITE_API int sqlite3_vtab_config(sqlite3 *db, int op, ...){
va_list ap;
int rc = SQLITE_OK;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
-
va_start(ap, op);
switch( op ){
case SQLITE_VTAB_CONSTRAINT_SUPPORT: {
@@ -113258,6 +114571,9 @@ struct WhereLevel {
} u;
struct WhereLoop *pWLoop; /* The selected WhereLoop object */
Bitmask notReady; /* FROM entries not usable at this level */
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ int addrVisit; /* Address at which row is visited */
+#endif
};
/*
@@ -113288,7 +114604,6 @@ struct WhereLoop {
union {
struct { /* Information for internal btree tables */
u16 nEq; /* Number of equality constraints */
- u16 nSkip; /* Number of initial index columns to skip */
Index *pIndex; /* Index used, or NULL */
} btree;
struct { /* Information for virtual tables */
@@ -113301,12 +114616,13 @@ struct WhereLoop {
} u;
u32 wsFlags; /* WHERE_* flags describing the plan */
u16 nLTerm; /* Number of entries in aLTerm[] */
+ u16 nSkip; /* Number of NULL aLTerm[] entries */
/**** whereLoopXfer() copies fields above ***********************/
# define WHERE_LOOP_XFER_SZ offsetof(WhereLoop,nLSlot)
u16 nLSlot; /* Number of slots allocated for aLTerm[] */
WhereTerm **aLTerm; /* WhereTerms used */
WhereLoop *pNextLoop; /* Next WhereLoop object in the WhereClause */
- WhereTerm *aLTermSpace[4]; /* Initial aLTerm[] space */
+ WhereTerm *aLTermSpace[3]; /* Initial aLTerm[] space */
};
/* This object holds the prerequisites and the cost of running a
@@ -113632,6 +114948,7 @@ struct WhereInfo {
#define WHERE_AUTO_INDEX 0x00004000 /* Uses an ephemeral index */
#define WHERE_SKIPSCAN 0x00008000 /* Uses the skip-scan algorithm */
#define WHERE_UNQ_WANTED 0x00010000 /* WHERE_ONEROW would have been helpful*/
+#define WHERE_PARTIALIDX 0x00020000 /* The automatic index is partial */
/************** End of whereInt.h ********************************************/
/************** Continuing where we left off in where.c **********************/
@@ -113839,10 +115156,11 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u8 wtFlags){
sqlite3DbFree(db, pOld);
}
pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]);
+ memset(&pWC->a[pWC->nTerm], 0, sizeof(pWC->a[0])*(pWC->nSlot-pWC->nTerm));
}
pTerm = &pWC->a[idx = pWC->nTerm++];
if( p && ExprHasProperty(p, EP_Unlikely) ){
- pTerm->truthProb = sqlite3LogEst(p->iTable) - 99;
+ pTerm->truthProb = sqlite3LogEst(p->iTable) - 270;
}else{
pTerm->truthProb = 1;
}
@@ -114373,6 +115691,15 @@ static void transferJoinMarkings(Expr *pDerived, Expr *pBase){
}
}
+/*
+** Mark term iChild as being a child of term iParent
+*/
+static void markTermAsChild(WhereClause *pWC, int iChild, int iParent){
+ pWC->a[iChild].iParent = iParent;
+ pWC->a[iChild].truthProb = pWC->a[iParent].truthProb;
+ pWC->a[iParent].nChild++;
+}
+
#if !defined(SQLITE_OMIT_OR_OPTIMIZATION) && !defined(SQLITE_OMIT_SUBQUERY)
/*
** Analyze a term that consists of two or more OR-connected
@@ -114670,8 +115997,7 @@ static void exprAnalyzeOrTerm(
testcase( idxNew==0 );
exprAnalyze(pSrc, pWC, idxNew);
pTerm = &pWC->a[idxTerm];
- pWC->a[idxNew].iParent = idxTerm;
- pTerm->nChild = 1;
+ markTermAsChild(pWC, idxNew, idxTerm);
}else{
sqlite3ExprListDelete(db, pList);
}
@@ -114773,9 +116099,8 @@ static void exprAnalyze(
idxNew = whereClauseInsert(pWC, pDup, TERM_VIRTUAL|TERM_DYNAMIC);
if( idxNew==0 ) return;
pNew = &pWC->a[idxNew];
- pNew->iParent = idxTerm;
+ markTermAsChild(pWC, idxNew, idxTerm);
pTerm = &pWC->a[idxTerm];
- pTerm->nChild = 1;
pTerm->wtFlags |= TERM_COPIED;
if( pExpr->op==TK_EQ
&& !ExprHasProperty(pExpr, EP_FromJoin)
@@ -114832,9 +116157,8 @@ static void exprAnalyze(
testcase( idxNew==0 );
exprAnalyze(pSrc, pWC, idxNew);
pTerm = &pWC->a[idxTerm];
- pWC->a[idxNew].iParent = idxTerm;
+ markTermAsChild(pWC, idxNew, idxTerm);
}
- pTerm->nChild = 2;
}
#endif /* SQLITE_OMIT_BETWEEN_OPTIMIZATION */
@@ -114909,9 +116233,8 @@ static void exprAnalyze(
exprAnalyze(pSrc, pWC, idxNew2);
pTerm = &pWC->a[idxTerm];
if( isComplete ){
- pWC->a[idxNew1].iParent = idxTerm;
- pWC->a[idxNew2].iParent = idxTerm;
- pTerm->nChild = 2;
+ markTermAsChild(pWC, idxNew1, idxTerm);
+ markTermAsChild(pWC, idxNew2, idxTerm);
}
}
#endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */
@@ -114944,9 +116267,8 @@ static void exprAnalyze(
pNewTerm->leftCursor = pLeft->iTable;
pNewTerm->u.leftColumn = pLeft->iColumn;
pNewTerm->eOperator = WO_MATCH;
- pNewTerm->iParent = idxTerm;
+ markTermAsChild(pWC, idxNew, idxTerm);
pTerm = &pWC->a[idxTerm];
- pTerm->nChild = 1;
pTerm->wtFlags |= TERM_COPIED;
pNewTerm->prereqAll = pTerm->prereqAll;
}
@@ -114967,7 +116289,7 @@ static void exprAnalyze(
if( pExpr->op==TK_NOTNULL
&& pExpr->pLeft->op==TK_COLUMN
&& pExpr->pLeft->iColumn>=0
- && OptimizationEnabled(db, SQLITE_Stat3)
+ && OptimizationEnabled(db, SQLITE_Stat34)
){
Expr *pNewExpr;
Expr *pLeft = pExpr->pLeft;
@@ -114986,9 +116308,8 @@ static void exprAnalyze(
pNewTerm->leftCursor = pLeft->iTable;
pNewTerm->u.leftColumn = pLeft->iColumn;
pNewTerm->eOperator = WO_GT;
- pNewTerm->iParent = idxTerm;
+ markTermAsChild(pWC, idxNew, idxTerm);
pTerm = &pWC->a[idxTerm];
- pTerm->nChild = 1;
pTerm->wtFlags |= TERM_COPIED;
pNewTerm->prereqAll = pTerm->prereqAll;
}
@@ -115208,6 +116529,8 @@ static void constructAutomaticIndex(
Bitmask idxCols; /* Bitmap of columns used for indexing */
Bitmask extraCols; /* Bitmap of additional columns */
u8 sentWarning = 0; /* True if a warnning has been issued */
+ Expr *pPartial = 0; /* Partial Index Expression */
+ int iContinue = 0; /* Jump here to skip excluded rows */
/* Generate code to skip over the creation and initialization of the
** transient index on 2nd and subsequent iterations of the loop. */
@@ -115223,6 +116546,12 @@ static void constructAutomaticIndex(
pLoop = pLevel->pWLoop;
idxCols = 0;
for(pTerm=pWC->a; pTermprereq==0
+ && (pTerm->wtFlags & TERM_VIRTUAL)==0
+ && sqlite3ExprIsTableConstant(pTerm->pExpr, pSrc->iCursor) ){
+ pPartial = sqlite3ExprAnd(pParse->db, pPartial,
+ sqlite3ExprDup(pParse->db, pTerm->pExpr, 0));
+ }
if( termCanDriveIndex(pTerm, pSrc, notReady) ){
int iCol = pTerm->u.leftColumn;
Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol);
@@ -115235,7 +116564,9 @@ static void constructAutomaticIndex(
sentWarning = 1;
}
if( (idxCols & cMask)==0 ){
- if( whereLoopResize(pParse->db, pLoop, nKeyCol+1) ) return;
+ if( whereLoopResize(pParse->db, pLoop, nKeyCol+1) ){
+ goto end_auto_index_create;
+ }
pLoop->aLTerm[nKeyCol++] = pTerm;
idxCols |= cMask;
}
@@ -115255,7 +116586,7 @@ static void constructAutomaticIndex(
** if they go out of sync.
*/
extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1));
- mxBitCol = (pTable->nCol >= BMS-1) ? BMS-1 : pTable->nCol;
+ mxBitCol = MIN(BMS-1,pTable->nCol);
testcase( pTable->nCol==BMS-1 );
testcase( pTable->nCol==BMS-2 );
for(i=0; icolUsed & MASKBIT(BMS-1) ){
nKeyCol += pTable->nCol - BMS + 1;
}
- pLoop->wsFlags |= WHERE_COLUMN_EQ | WHERE_IDX_ONLY;
/* Construct the Index object to describe this index */
pIdx = sqlite3AllocateIndexObject(pParse->db, nKeyCol+1, 0, &zNotUsed);
- if( pIdx==0 ) return;
+ if( pIdx==0 ) goto end_auto_index_create;
pLoop->u.btree.pIndex = pIdx;
pIdx->zName = "auto-index";
pIdx->pTable = pTable;
@@ -115320,18 +116650,29 @@ static void constructAutomaticIndex(
VdbeComment((v, "for %s", pTable->zName));
/* Fill the automatic index with content */
+ sqlite3ExprCachePush(pParse);
addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v);
+ if( pPartial ){
+ iContinue = sqlite3VdbeMakeLabel(v);
+ sqlite3ExprIfFalse(pParse, pPartial, iContinue, SQLITE_JUMPIFNULL);
+ pLoop->wsFlags |= WHERE_PARTIALIDX;
+ }
regRecord = sqlite3GetTempReg(pParse);
sqlite3GenerateIndexKey(pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0, 0, 0);
sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord);
sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
+ if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue);
sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v);
sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX);
sqlite3VdbeJumpHere(v, addrTop);
sqlite3ReleaseTempReg(pParse, regRecord);
+ sqlite3ExprCachePop(pParse);
/* Jump here when skipping the initialization */
sqlite3VdbeJumpHere(v, addrInit);
+
+end_auto_index_create:
+ sqlite3ExprDelete(pParse->db, pPartial);
}
#endif /* SQLITE_OMIT_AUTOMATIC_INDEX */
@@ -115491,7 +116832,6 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){
}
#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */
-
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
/*
** Estimate the location of a particular key among all keys in an
@@ -115500,9 +116840,10 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){
** aStat[0] Est. number of rows less than pVal
** aStat[1] Est. number of rows equal to pVal
**
-** Return SQLITE_OK on success.
+** Return the index of the sample that is the smallest sample that
+** is greater than or equal to pRec.
*/
-static void whereKeyStats(
+static int whereKeyStats(
Parse *pParse, /* Database connection */
Index *pIdx, /* Index to consider domain of */
UnpackedRecord *pRec, /* Vector of values to consider */
@@ -115584,6 +116925,7 @@ static void whereKeyStats(
}
aStat[0] = iLower + iGap;
}
+ return i;
}
#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
@@ -115734,7 +117076,7 @@ static int whereRangeSkipScanEst(
** If either of the upper or lower bound is not present, then NULL is passed in
** place of the corresponding WhereTerm.
**
-** The value in (pBuilder->pNew->u.btree.nEq) is the index of the index
+** The value in (pBuilder->pNew->u.btree.nEq) is the number of the index
** column subject to the range constraint. Or, equivalently, the number of
** equality constraints optimized by the proposed index scan. For example,
** assuming index p is on t1(a, b), and the SQL query is:
@@ -115750,7 +117092,7 @@ static int whereRangeSkipScanEst(
**
** When this function is called, *pnOut is set to the sqlite3LogEst() of the
** number of rows that the index scan is expected to visit without
-** considering the range constraints. If nEq is 0, this is the number of
+** considering the range constraints. If nEq is 0, then *pnOut is the number of
** rows in the index. Assuming no error occurs, *pnOut is adjusted (reduced)
** to account for the range constraints pLower and pUpper.
**
@@ -115774,10 +117116,7 @@ static int whereRangeScanEst(
Index *p = pLoop->u.btree.pIndex;
int nEq = pLoop->u.btree.nEq;
- if( p->nSample>0
- && nEqnSampleCol
- && OptimizationEnabled(pParse->db, SQLITE_Stat3)
- ){
+ if( p->nSample>0 && nEqnSampleCol ){
if( nEq==pBuilder->nRecValid ){
UnpackedRecord *pRec = pBuilder->pRec;
tRowcnt a[2];
@@ -115793,15 +117132,19 @@ static int whereRangeScanEst(
** is not a simple variable or literal value), the lower bound of the
** range is $P. Due to a quirk in the way whereKeyStats() works, even
** if $L is available, whereKeyStats() is called for both ($P) and
- ** ($P:$L) and the larger of the two returned values used.
+ ** ($P:$L) and the larger of the two returned values is used.
**
** Similarly, iUpper is to be set to the estimate of the number of rows
** less than the upper bound of the range query. Where the upper bound
** is either ($P) or ($P:$U). Again, even if $U is available, both values
** of iUpper are requested of whereKeyStats() and the smaller used.
+ **
+ ** The number of rows between the two bounds is then just iUpper-iLower.
*/
- tRowcnt iLower;
- tRowcnt iUpper;
+ tRowcnt iLower; /* Rows less than the lower bound */
+ tRowcnt iUpper; /* Rows less than the upper bound */
+ int iLwrIdx = -2; /* aSample[] for the lower bound */
+ int iUprIdx = -1; /* aSample[] for the upper bound */
if( pRec ){
testcase( pRec->nField!=pBuilder->nRecValid );
@@ -115815,7 +117158,7 @@ static int whereRangeScanEst(
/* Determine iLower and iUpper using ($P) only. */
if( nEq==0 ){
iLower = 0;
- iUpper = sqlite3LogEstToInt(p->aiRowLogEst[0]);
+ iUpper = p->nRowEst0;
}else{
/* Note: this call could be optimized away - since the same values must
** have been requested when testing key $P in whereEqualScanEst(). */
@@ -115839,7 +117182,7 @@ static int whereRangeScanEst(
rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk);
if( rc==SQLITE_OK && bOk ){
tRowcnt iNew;
- whereKeyStats(pParse, p, pRec, 0, a);
+ iLwrIdx = whereKeyStats(pParse, p, pRec, 0, a);
iNew = a[0] + ((pLower->eOperator & (WO_GT|WO_LE)) ? a[1] : 0);
if( iNew>iLower ) iLower = iNew;
nOut--;
@@ -115854,7 +117197,7 @@ static int whereRangeScanEst(
rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk);
if( rc==SQLITE_OK && bOk ){
tRowcnt iNew;
- whereKeyStats(pParse, p, pRec, 1, a);
+ iUprIdx = whereKeyStats(pParse, p, pRec, 1, a);
iNew = a[0] + ((pUpper->eOperator & (WO_GT|WO_LE)) ? a[1] : 0);
if( iNewiLower ){
nNew = sqlite3LogEst(iUpper - iLower);
+ /* TUNING: If both iUpper and iLower are derived from the same
+ ** sample, then assume they are 4x more selective. This brings
+ ** the estimated selectivity more in line with what it would be
+ ** if estimated without the use of STAT3/4 tables. */
+ if( iLwrIdx==iUprIdx ) nNew -= 20; assert( 20==sqlite3LogEst(4) );
}else{
nNew = 10; assert( 10==sqlite3LogEst(2) );
}
@@ -115890,12 +117238,15 @@ static int whereRangeScanEst(
nNew = whereRangeAdjust(pLower, nOut);
nNew = whereRangeAdjust(pUpper, nNew);
- /* TUNING: If there is both an upper and lower limit, assume the range is
+ /* TUNING: If there is both an upper and lower limit and neither limit
+ ** has an application-defined likelihood(), assume the range is
** reduced by an additional 75%. This means that, by default, an open-ended
** range query (e.g. col > ?) is assumed to match 1/4 of the rows in the
** index. While a closed range (e.g. col BETWEEN ? AND ?) is estimated to
** match 1/64 of the index. */
- if( pLower && pUpper ) nNew -= 20;
+ if( pLower && pLower->truthProb>0 && pUpper && pUpper->truthProb>0 ){
+ nNew -= 20;
+ }
nOut -= (pLower!=0) + (pUpper!=0);
if( nNew<10 ) nNew = 10;
@@ -116255,7 +117606,7 @@ static int codeAllEqualityTerms(
pLoop = pLevel->pWLoop;
assert( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 );
nEq = pLoop->u.btree.nEq;
- nSkip = pLoop->u.btree.nSkip;
+ nSkip = pLoop->nSkip;
pIdx = pLoop->u.btree.pIndex;
assert( pIdx!=0 );
@@ -116369,7 +117720,7 @@ static void explainAppendTerm(
static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop, Table *pTab){
Index *pIndex = pLoop->u.btree.pIndex;
u16 nEq = pLoop->u.btree.nEq;
- u16 nSkip = pLoop->u.btree.nSkip;
+ u16 nSkip = pLoop->nSkip;
int i, j;
Column *aCol = pTab->aCol;
i16 *aiColumn = pIndex->aiColumn;
@@ -116400,11 +117751,14 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop, Table *pTab){
/*
** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN
-** command. If the query being compiled is an EXPLAIN QUERY PLAN, a single
-** record is added to the output to describe the table scan strategy in
-** pLevel.
+** command, or if either SQLITE_DEBUG or SQLITE_ENABLE_STMT_SCANSTATUS was
+** defined at compile-time. If it is not a no-op, a single OP_Explain opcode
+** is added to the output to describe the table scan strategy in pLevel.
+**
+** If an OP_Explain opcode is added to the VM, its address is returned.
+** Otherwise, if no OP_Explain is coded, zero is returned.
*/
-static void explainOneScan(
+static int explainOneScan(
Parse *pParse, /* Parse context */
SrcList *pTabList, /* Table list this loop refers to */
WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
@@ -116412,7 +117766,8 @@ static void explainOneScan(
int iFrom, /* Value for "from" column of output */
u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
){
-#ifndef SQLITE_DEBUG
+ int ret = 0;
+#if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS)
if( pParse->explain==2 )
#endif
{
@@ -116429,7 +117784,7 @@ static void explainOneScan(
pLoop = pLevel->pWLoop;
flags = pLoop->wsFlags;
- if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_ONETABLE_ONLY) ) return;
+ if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_ONETABLE_ONLY) ) return 0;
isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0
|| ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0))
@@ -116458,6 +117813,8 @@ static void explainOneScan(
if( isSearch ){
zFmt = "PRIMARY KEY";
}
+ }else if( flags & WHERE_PARTIALIDX ){
+ zFmt = "AUTOMATIC PARTIAL COVERING INDEX";
}else if( flags & WHERE_AUTO_INDEX ){
zFmt = "AUTOMATIC COVERING INDEX";
}else if( flags & WHERE_IDX_ONLY ){
@@ -116499,13 +117856,46 @@ static void explainOneScan(
}
#endif
zMsg = sqlite3StrAccumFinish(&str);
- sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg, P4_DYNAMIC);
+ ret = sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg,P4_DYNAMIC);
}
+ return ret;
}
#else
-# define explainOneScan(u,v,w,x,y,z)
+# define explainOneScan(u,v,w,x,y,z) 0
#endif /* SQLITE_OMIT_EXPLAIN */
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+/*
+** Configure the VM passed as the first argument with an
+** sqlite3_stmt_scanstatus() entry corresponding to the scan used to
+** implement level pLvl. Argument pSrclist is a pointer to the FROM
+** clause that the scan reads data from.
+**
+** If argument addrExplain is not 0, it must be the address of an
+** OP_Explain instruction that describes the same loop.
+*/
+static void addScanStatus(
+ Vdbe *v, /* Vdbe to add scanstatus entry to */
+ SrcList *pSrclist, /* FROM clause pLvl reads data from */
+ WhereLevel *pLvl, /* Level to add scanstatus() entry for */
+ int addrExplain /* Address of OP_Explain (or 0) */
+){
+ const char *zObj = 0;
+ WhereLoop *pLoop = pLvl->pWLoop;
+ if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){
+ zObj = pLoop->u.btree.pIndex->zName;
+ }else{
+ zObj = pSrclist->a[pLvl->iFrom].zName;
+ }
+ sqlite3VdbeScanStatus(
+ v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj
+ );
+}
+#else
+# define addScanStatus(a, b, c, d) ((void)d)
+#endif
+
+
/*
** Generate code for the start of the iLevel-th loop in the WHERE clause
@@ -116806,7 +118196,7 @@ static Bitmask codeOneLoopStart(
pIdx = pLoop->u.btree.pIndex;
iIdxCur = pLevel->iIdxCur;
- assert( nEq>=pLoop->u.btree.nSkip );
+ assert( nEq>=pLoop->nSkip );
/* If this loop satisfies a sort order (pOrderBy) request that
** was passed to this function to implement a "SELECT min(x) ..."
@@ -116823,7 +118213,7 @@ static Bitmask codeOneLoopStart(
&& pWInfo->nOBSat>0
&& (pIdx->nKeyCol>nEq)
){
- assert( pLoop->u.btree.nSkip==0 );
+ assert( pLoop->nSkip==0 );
bSeekPastNull = 1;
nExtraReg = 1;
}
@@ -117136,10 +118526,9 @@ static Bitmask codeOneLoopStart(
Expr *pExpr = pWC->a[iTerm].pExpr;
if( &pWC->a[iTerm] == pTerm ) continue;
if( ExprHasProperty(pExpr, EP_FromJoin) ) continue;
- testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO );
- testcase( pWC->a[iTerm].wtFlags & TERM_VIRTUAL );
- if( pWC->a[iTerm].wtFlags & (TERM_ORINFO|TERM_VIRTUAL) ) continue;
+ if( (pWC->a[iTerm].wtFlags & TERM_VIRTUAL)!=0 ) continue;
if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue;
+ testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO );
pExpr = sqlite3ExprDup(db, pExpr, 0);
pAndExpr = sqlite3ExprAnd(db, pAndExpr, pExpr);
}
@@ -117172,9 +118561,11 @@ static Bitmask codeOneLoopStart(
assert( pSubWInfo || pParse->nErr || db->mallocFailed );
if( pSubWInfo ){
WhereLoop *pSubLoop;
- explainOneScan(
+ int addrExplain = explainOneScan(
pParse, pOrTab, &pSubWInfo->a[0], iLevel, pLevel->iFrom, 0
);
+ addScanStatus(v, pOrTab, &pSubWInfo->a[0], addrExplain);
+
/* This is the sub-WHERE clause body. First skip over
** duplicate rows from prior sub-WHERE clauses, and record the
** rowid (or PRIMARY KEY) for the current row so that the same
@@ -117305,6 +118696,10 @@ static Bitmask codeOneLoopStart(
}
}
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ pLevel->addrVisit = sqlite3VdbeCurrentAddr(v);
+#endif
+
/* Insert code to test every subexpression that can be completely
** computed using the current set of tables.
*/
@@ -117444,7 +118839,7 @@ static void whereLoopPrint(WhereLoop *p, WhereClause *pWC){
sqlite3_free(z);
}
if( p->wsFlags & WHERE_SKIPSCAN ){
- sqlite3DebugPrintf(" f %05x %d-%d", p->wsFlags, p->nLTerm,p->u.btree.nSkip);
+ sqlite3DebugPrintf(" f %05x %d-%d", p->wsFlags, p->nLTerm,p->nSkip);
}else{
sqlite3DebugPrintf(" f %05x N %d", p->wsFlags, p->nLTerm);
}
@@ -117480,7 +118875,6 @@ static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){
p->u.vtab.idxStr = 0;
}else if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 && p->u.btree.pIndex!=0 ){
sqlite3DbFree(db, p->u.btree.pIndex->zColAff);
- sqlite3KeyInfoUnref(p->u.btree.pIndex->pKeyInfo);
sqlite3DbFree(db, p->u.btree.pIndex);
p->u.btree.pIndex = 0;
}
@@ -117555,10 +118949,11 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){
}
/*
-** Return TRUE if both of the following are true:
+** Return TRUE if all of the following are true:
**
** (1) X has the same or lower cost that Y
** (2) X is a proper subset of Y
+** (3) X skips at least as many columns as Y
**
** By "proper subset" we mean that X uses fewer WHERE clause terms
** than Y and that every WHERE clause term used by X is also used
@@ -117566,19 +118961,25 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){
**
** If X is a proper subset of Y then Y is a better choice and ought
** to have a lower cost. This routine returns TRUE when that cost
-** relationship is inverted and needs to be adjusted.
+** relationship is inverted and needs to be adjusted. The third rule
+** was added because if X uses skip-scan less than Y it still might
+** deserve a lower cost even if it is a proper subset of Y.
*/
static int whereLoopCheaperProperSubset(
const WhereLoop *pX, /* First WhereLoop to compare */
const WhereLoop *pY /* Compare against this WhereLoop */
){
int i, j;
- if( pX->nLTerm >= pY->nLTerm ) return 0; /* X is not a subset of Y */
+ if( pX->nLTerm-pX->nSkip >= pY->nLTerm-pY->nSkip ){
+ return 0; /* X is not a subset of Y */
+ }
+ if( pY->nSkip > pX->nSkip ) return 0;
if( pX->rRun >= pY->rRun ){
if( pX->rRun > pY->rRun ) return 0; /* X costs more than Y */
if( pX->nOut > pY->nOut ) return 0; /* X costs more than Y */
}
for(i=pX->nLTerm-1; i>=0; i--){
+ if( pX->aLTerm[i]==0 ) continue;
for(j=pY->nLTerm-1; j>=0; j--){
if( pY->aLTerm[j]==pX->aLTerm[i] ) break;
}
@@ -117600,33 +119001,24 @@ static int whereLoopCheaperProperSubset(
** To say "WhereLoop X is a proper subset of Y" means that X uses fewer
** WHERE clause terms than Y and that every WHERE clause term used by X is
** also used by Y.
-**
-** This adjustment is omitted for SKIPSCAN loops. In a SKIPSCAN loop, the
-** WhereLoop.nLTerm field is not an accurate measure of the number of WHERE
-** clause terms covered, since some of the first nLTerm entries in aLTerm[]
-** will be NULL (because they are skipped). That makes it more difficult
-** to compare the loops. We could add extra code to do the comparison, and
-** perhaps we will someday. But SKIPSCAN is sufficiently uncommon, and this
-** adjustment is sufficient minor, that it is very difficult to construct
-** a test case where the extra code would improve the query plan. Better
-** to avoid the added complexity and just omit cost adjustments to SKIPSCAN
-** loops.
*/
static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){
if( (pTemplate->wsFlags & WHERE_INDEXED)==0 ) return;
- if( (pTemplate->wsFlags & WHERE_SKIPSCAN)!=0 ) return;
for(; p; p=p->pNextLoop){
if( p->iTab!=pTemplate->iTab ) continue;
if( (p->wsFlags & WHERE_INDEXED)==0 ) continue;
- if( (p->wsFlags & WHERE_SKIPSCAN)!=0 ) continue;
if( whereLoopCheaperProperSubset(p, pTemplate) ){
/* Adjust pTemplate cost downward so that it is cheaper than its
- ** subset p */
+ ** subset p. */
+ WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n",
+ pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut-1));
pTemplate->rRun = p->rRun;
pTemplate->nOut = p->nOut - 1;
}else if( whereLoopCheaperProperSubset(pTemplate, p) ){
/* Adjust pTemplate cost upward so that it is costlier than p since
** pTemplate is a proper subset of p */
+ WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n",
+ pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut+1));
pTemplate->rRun = p->rRun;
pTemplate->nOut = p->nOut + 1;
}
@@ -117671,8 +119063,9 @@ static WhereLoop **whereLoopFindLesser(
/* Any loop using an appliation-defined index (or PRIMARY KEY or
** UNIQUE constraint) with one or more == constraints is better
- ** than an automatic index. */
+ ** than an automatic index. Unless it is a skip-scan. */
if( (p->wsFlags & WHERE_AUTO_INDEX)!=0
+ && (pTemplate->nSkip)==0
&& (pTemplate->wsFlags & WHERE_INDEXED)!=0
&& (pTemplate->wsFlags & WHERE_COLUMN_EQ)!=0
&& (p->prereq & pTemplate->prereq)==pTemplate->prereq
@@ -117831,10 +119224,30 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){
** Adjust the WhereLoop.nOut value downward to account for terms of the
** WHERE clause that reference the loop but which are not used by an
** index.
+*
+** For every WHERE clause term that is not used by the index
+** and which has a truth probability assigned by one of the likelihood(),
+** likely(), or unlikely() SQL functions, reduce the estimated number
+** of output rows by the probability specified.
**
-** In the current implementation, the first extra WHERE clause term reduces
-** the number of output rows by a factor of 10 and each additional term
-** reduces the number of output rows by sqrt(2).
+** TUNING: For every WHERE clause term that is not used by the index
+** and which does not have an assigned truth probability, heuristics
+** described below are used to try to estimate the truth probability.
+** TODO --> Perhaps this is something that could be improved by better
+** table statistics.
+**
+** Heuristic 1: Estimate the truth probability as 93.75%. The 93.75%
+** value corresponds to -1 in LogEst notation, so this means decrement
+** the WhereLoop.nOut field for every such WHERE clause term.
+**
+** Heuristic 2: If there exists one or more WHERE clause terms of the
+** form "x==EXPR" and EXPR is not a constant 0 or 1, then make sure the
+** final output row estimate is no greater than 1/4 of the total number
+** of rows in the table. In other words, assume that x==EXPR will filter
+** out at least 3 out of 4 rows. If EXPR is -1 or 0 or 1, then maybe the
+** "x" column is boolean or else -1 or 0 or 1 is a common default value
+** on the "x" column and so in that case only cap the output row estimate
+** at 1/2 instead of 1/4.
*/
static void whereLoopOutputAdjust(
WhereClause *pWC, /* The WHERE clause */
@@ -117843,9 +119256,10 @@ static void whereLoopOutputAdjust(
){
WhereTerm *pTerm, *pX;
Bitmask notAllowed = ~(pLoop->prereq|pLoop->maskSelf);
- int i, j;
- int nEq = 0; /* Number of = constraints not within likely()/unlikely() */
+ int i, j, k;
+ LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */
+ assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 );
for(i=pWC->nTerm, pTerm=pWC->a; i>0; i--, pTerm++){
if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) break;
if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue;
@@ -117858,20 +119272,26 @@ static void whereLoopOutputAdjust(
}
if( j<0 ){
if( pTerm->truthProb<=0 ){
+ /* If a truth probability is specified using the likelihood() hints,
+ ** then use the probability provided by the application. */
pLoop->nOut += pTerm->truthProb;
}else{
+ /* In the absence of explicit truth probabilities, use heuristics to
+ ** guess a reasonable truth probability. */
pLoop->nOut--;
- if( pTerm->eOperator&WO_EQ ) nEq++;
+ if( pTerm->eOperator&WO_EQ ){
+ Expr *pRight = pTerm->pExpr->pRight;
+ if( sqlite3ExprIsInteger(pRight, &k) && k>=(-1) && k<=1 ){
+ k = 10;
+ }else{
+ k = 20;
+ }
+ if( iReducenOut>nRow-10 ){
- pLoop->nOut = nRow - 10;
- }
+ if( pLoop->nOut > nRow-iReduce ) pLoop->nOut = nRow - iReduce;
}
/*
@@ -117912,7 +119332,7 @@ static int whereLoopAddBtreeIndex(
Bitmask saved_prereq; /* Original value of pNew->prereq */
u16 saved_nLTerm; /* Original value of pNew->nLTerm */
u16 saved_nEq; /* Original value of pNew->u.btree.nEq */
- u16 saved_nSkip; /* Original value of pNew->u.btree.nSkip */
+ u16 saved_nSkip; /* Original value of pNew->nSkip */
u32 saved_wsFlags; /* Original value of pNew->wsFlags */
LogEst saved_nOut; /* Original value of pNew->nOut */
int iCol; /* Index of the column in the table */
@@ -117941,7 +119361,7 @@ static int whereLoopAddBtreeIndex(
pTerm = whereScanInit(&scan, pBuilder->pWC, pSrc->iCursor, iCol,
opMask, pProbe);
saved_nEq = pNew->u.btree.nEq;
- saved_nSkip = pNew->u.btree.nSkip;
+ saved_nSkip = pNew->nSkip;
saved_nLTerm = pNew->nLTerm;
saved_wsFlags = pNew->wsFlags;
saved_prereq = pNew->prereq;
@@ -117949,44 +119369,6 @@ static int whereLoopAddBtreeIndex(
pNew->rSetup = 0;
rSize = pProbe->aiRowLogEst[0];
rLogSize = estLog(rSize);
-
- /* Consider using a skip-scan if there are no WHERE clause constraints
- ** available for the left-most terms of the index, and if the average
- ** number of repeats in the left-most terms is at least 18.
- **
- ** The magic number 18 is selected on the basis that scanning 17 rows
- ** is almost always quicker than an index seek (even though if the index
- ** contains fewer than 2^17 rows we assume otherwise in other parts of
- ** the code). And, even if it is not, it should not be too much slower.
- ** On the other hand, the extra seeks could end up being significantly
- ** more expensive. */
- assert( 42==sqlite3LogEst(18) );
- if( saved_nEq==saved_nSkip
- && saved_nEq+1nKeyCol
- && pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */
- && (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK
- ){
- LogEst nIter;
- pNew->u.btree.nEq++;
- pNew->u.btree.nSkip++;
- pNew->aLTerm[pNew->nLTerm++] = 0;
- pNew->wsFlags |= WHERE_SKIPSCAN;
- nIter = pProbe->aiRowLogEst[saved_nEq] - pProbe->aiRowLogEst[saved_nEq+1];
- if( pTerm ){
- /* TUNING: When estimating skip-scan for a term that is also indexable,
- ** multiply the cost of the skip-scan by 2.0, to make it a little less
- ** desirable than the regular index lookup. */
- nIter += 10; assert( 10==sqlite3LogEst(2) );
- }
- pNew->nOut -= nIter;
- /* TUNING: Because uncertainties in the estimates for skip-scan queries,
- ** add a 1.375 fudge factor to make skip-scan slightly less likely. */
- nIter += 5;
- whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nIter + nInMul);
- pNew->nOut = saved_nOut;
- pNew->u.btree.nEq = saved_nEq;
- pNew->u.btree.nSkip = saved_nSkip;
- }
for(; rc==SQLITE_OK && pTerm!=0; pTerm = whereScanNext(&scan)){
u16 eOp = pTerm->eOperator; /* Shorthand for pTerm->eOperator */
LogEst rCostIdx;
@@ -118081,7 +119463,6 @@ static int whereLoopAddBtreeIndex(
if( nInMul==0
&& pProbe->nSample
&& pNew->u.btree.nEq<=pProbe->nSampleCol
- && OptimizationEnabled(db, SQLITE_Stat3)
&& ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect))
){
Expr *pExpr = pTerm->pExpr;
@@ -118149,10 +119530,45 @@ static int whereLoopAddBtreeIndex(
}
pNew->prereq = saved_prereq;
pNew->u.btree.nEq = saved_nEq;
- pNew->u.btree.nSkip = saved_nSkip;
+ pNew->nSkip = saved_nSkip;
pNew->wsFlags = saved_wsFlags;
pNew->nOut = saved_nOut;
pNew->nLTerm = saved_nLTerm;
+
+ /* Consider using a skip-scan if there are no WHERE clause constraints
+ ** available for the left-most terms of the index, and if the average
+ ** number of repeats in the left-most terms is at least 18.
+ **
+ ** The magic number 18 is selected on the basis that scanning 17 rows
+ ** is almost always quicker than an index seek (even though if the index
+ ** contains fewer than 2^17 rows we assume otherwise in other parts of
+ ** the code). And, even if it is not, it should not be too much slower.
+ ** On the other hand, the extra seeks could end up being significantly
+ ** more expensive. */
+ assert( 42==sqlite3LogEst(18) );
+ if( saved_nEq==saved_nSkip
+ && saved_nEq+1nKeyCol
+ && pProbe->noSkipScan==0
+ && pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */
+ && (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK
+ ){
+ LogEst nIter;
+ pNew->u.btree.nEq++;
+ pNew->nSkip++;
+ pNew->aLTerm[pNew->nLTerm++] = 0;
+ pNew->wsFlags |= WHERE_SKIPSCAN;
+ nIter = pProbe->aiRowLogEst[saved_nEq] - pProbe->aiRowLogEst[saved_nEq+1];
+ pNew->nOut -= nIter;
+ /* TUNING: Because uncertainties in the estimates for skip-scan queries,
+ ** add a 1.375 fudge factor to make skip-scan slightly less likely. */
+ nIter += 5;
+ whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nIter + nInMul);
+ pNew->nOut = saved_nOut;
+ pNew->u.btree.nEq = saved_nEq;
+ pNew->nSkip = saved_nSkip;
+ pNew->wsFlags = saved_wsFlags;
+ }
+
return rc;
}
@@ -118331,7 +119747,7 @@ static int whereLoopAddBtree(
if( pTerm->prereqRight & pNew->maskSelf ) continue;
if( termCanDriveIndex(pTerm, pSrc, 0) ){
pNew->u.btree.nEq = 1;
- pNew->u.btree.nSkip = 0;
+ pNew->nSkip = 0;
pNew->u.btree.pIndex = 0;
pNew->nLTerm = 1;
pNew->aLTerm[0] = pTerm;
@@ -118372,7 +119788,7 @@ static int whereLoopAddBtree(
}
rSize = pProbe->aiRowLogEst[0];
pNew->u.btree.nEq = 0;
- pNew->u.btree.nSkip = 0;
+ pNew->nSkip = 0;
pNew->nLTerm = 0;
pNew->iSortIdx = 0;
pNew->rSetup = 0;
@@ -118922,7 +120338,7 @@ static i8 wherePathSatisfiesOrderBy(
/* Skip over == and IS NULL terms */
if( ju.btree.nEq
- && pLoop->u.btree.nSkip==0
+ && pLoop->nSkip==0
&& ((i = pLoop->aLTerm[j]->eOperator) & (WO_EQ|WO_ISNULL))!=0
){
if( i & WO_ISNULL ){
@@ -119376,7 +120792,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
}
#ifdef WHERETRACE_ENABLED /* >=2 */
- if( sqlite3WhereTrace>=2 ){
+ if( sqlite3WhereTrace & 0x02 ){
sqlite3DebugPrintf("---- after round %d ----\n", iLoop);
for(ii=0, pTo=aTo; iisWC;
pLoop = pBuilder->pNew;
pLoop->wsFlags = 0;
- pLoop->u.btree.nSkip = 0;
+ pLoop->nSkip = 0;
pTerm = findTerm(pWC, iCur, -1, 0, WO_EQ, 0);
if( pTerm ){
pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW;
@@ -119507,7 +120923,6 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
}else{
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
assert( pLoop->aLTermSpace==pLoop->aLTerm );
- assert( ArraySize(pLoop->aLTermSpace)==4 );
if( !IsUniqueIndex(pIdx)
|| pIdx->pPartIdxWhere!=0
|| pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace)
@@ -120016,7 +121431,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
*/
notReady = ~(Bitmask)0;
for(ii=0; iia[ii];
+ wsFlags = pLevel->pWLoop->wsFlags;
#ifndef SQLITE_OMIT_AUTOMATIC_INDEX
if( (pLevel->pWLoop->wsFlags & WHERE_AUTO_INDEX)!=0 ){
constructAutomaticIndex(pParse, &pWInfo->sWC,
@@ -120024,10 +121442,15 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
if( db->mallocFailed ) goto whereBeginError;
}
#endif
- explainOneScan(pParse, pTabList, pLevel, ii, pLevel->iFrom, wctrlFlags);
+ addrExplain = explainOneScan(
+ pParse, pTabList, pLevel, ii, pLevel->iFrom, wctrlFlags
+ );
pLevel->addrBody = sqlite3VdbeCurrentAddr(v);
notReady = codeOneLoopStart(pWInfo, ii, notReady);
pWInfo->iContinue = pLevel->addrCont;
+ if( (wsFlags&WHERE_MULTI_OR)==0 && (wctrlFlags&WHERE_ONETABLE_ONLY)==0 ){
+ addScanStatus(v, pTabList, pLevel, addrExplain);
+ }
}
/* Done. */
@@ -122608,13 +124031,19 @@ static void yy_reduce(
int cnt = 0, mxSelect;
p->pWith = yymsp[-1].minor.yy59;
if( p->pPrior ){
+ u16 allValues = SF_Values;
pNext = 0;
for(pLoop=p; pLoop; pNext=pLoop, pLoop=pLoop->pPrior, cnt++){
pLoop->pNext = pNext;
pLoop->selFlags |= SF_Compound;
+ allValues &= pLoop->selFlags;
}
- mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT];
- if( mxSelect && cnt>mxSelect ){
+ if( allValues ){
+ p->selFlags |= SF_AllValues;
+ }else if(
+ (mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0
+ && cnt>mxSelect
+ ){
sqlite3ErrorMsg(pParse, "too many terms in compound SELECT");
}
}
@@ -124458,6 +125887,9 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr
int mxSqlLen; /* Max length of an SQL string */
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( zSql==0 || pzErrMsg==0 ) return SQLITE_MISUSE_BKPT;
+#endif
mxSqlLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH];
if( db->nVdbeActive==0 ){
db->u1.isInterrupted = 0;
@@ -124725,6 +126157,13 @@ SQLITE_API int sqlite3_complete(const char *zSql){
};
#endif /* SQLITE_OMIT_TRIGGER */
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( zSql==0 ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
+
while( *zSql ){
switch( *zSql ){
case ';': { /* A semicolon */
@@ -125026,7 +126465,7 @@ SQLITE_API int sqlite3_threadsafe(void){ return SQLITE_THREADSAFE; }
** I/O active are written using this function. These messages
** are intended for debugging activity only.
*/
-SQLITE_PRIVATE void (*sqlite3IoTrace)(const char*, ...) = 0;
+/* not-private */ void (*sqlite3IoTrace)(const char*, ...) = 0;
#endif
/*
@@ -125235,6 +126674,13 @@ SQLITE_API int sqlite3_initialize(void){
** when this routine is invoked, then this routine is a harmless no-op.
*/
SQLITE_API int sqlite3_shutdown(void){
+#ifdef SQLITE_OMIT_WSD
+ int rc = sqlite3_wsd_init(4096, 24);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+#endif
+
if( sqlite3GlobalConfig.isInit ){
#ifdef SQLITE_EXTRA_SHUTDOWN
void SQLITE_EXTRA_SHUTDOWN(void);
@@ -125293,15 +126739,17 @@ SQLITE_API int sqlite3_config(int op, ...){
switch( op ){
/* Mutex configuration options are only available in a threadsafe
- ** compile.
+ ** compile.
*/
-#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0
+#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-54466-46756 */
case SQLITE_CONFIG_SINGLETHREAD: {
/* Disable all mutexing */
sqlite3GlobalConfig.bCoreMutex = 0;
sqlite3GlobalConfig.bFullMutex = 0;
break;
}
+#endif
+#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-20520-54086 */
case SQLITE_CONFIG_MULTITHREAD: {
/* Disable mutexing of database connections */
/* Enable mutexing of core data structures */
@@ -125309,17 +126757,23 @@ SQLITE_API int sqlite3_config(int op, ...){
sqlite3GlobalConfig.bFullMutex = 0;
break;
}
+#endif
+#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-59593-21810 */
case SQLITE_CONFIG_SERIALIZED: {
/* Enable all mutexing */
sqlite3GlobalConfig.bCoreMutex = 1;
sqlite3GlobalConfig.bFullMutex = 1;
break;
}
+#endif
+#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-63666-48755 */
case SQLITE_CONFIG_MUTEX: {
/* Specify an alternative mutex implementation */
sqlite3GlobalConfig.mutex = *va_arg(ap, sqlite3_mutex_methods*);
break;
}
+#endif
+#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-14450-37597 */
case SQLITE_CONFIG_GETMUTEX: {
/* Retrieve the current mutex implementation */
*va_arg(ap, sqlite3_mutex_methods*) = sqlite3GlobalConfig.mutex;
@@ -125327,37 +126781,61 @@ SQLITE_API int sqlite3_config(int op, ...){
}
#endif
-
case SQLITE_CONFIG_MALLOC: {
- /* Specify an alternative malloc implementation */
+ /* EVIDENCE-OF: R-55594-21030 The SQLITE_CONFIG_MALLOC option takes a
+ ** single argument which is a pointer to an instance of the
+ ** sqlite3_mem_methods structure. The argument specifies alternative
+ ** low-level memory allocation routines to be used in place of the memory
+ ** allocation routines built into SQLite. */
sqlite3GlobalConfig.m = *va_arg(ap, sqlite3_mem_methods*);
break;
}
case SQLITE_CONFIG_GETMALLOC: {
- /* Retrieve the current malloc() implementation */
+ /* EVIDENCE-OF: R-51213-46414 The SQLITE_CONFIG_GETMALLOC option takes a
+ ** single argument which is a pointer to an instance of the
+ ** sqlite3_mem_methods structure. The sqlite3_mem_methods structure is
+ ** filled with the currently defined memory allocation routines. */
if( sqlite3GlobalConfig.m.xMalloc==0 ) sqlite3MemSetDefault();
*va_arg(ap, sqlite3_mem_methods*) = sqlite3GlobalConfig.m;
break;
}
case SQLITE_CONFIG_MEMSTATUS: {
- /* Enable or disable the malloc status collection */
+ /* EVIDENCE-OF: R-61275-35157 The SQLITE_CONFIG_MEMSTATUS option takes
+ ** single argument of type int, interpreted as a boolean, which enables
+ ** or disables the collection of memory allocation statistics. */
sqlite3GlobalConfig.bMemstat = va_arg(ap, int);
break;
}
case SQLITE_CONFIG_SCRATCH: {
- /* Designate a buffer for scratch memory space */
+ /* EVIDENCE-OF: R-08404-60887 There are three arguments to
+ ** SQLITE_CONFIG_SCRATCH: A pointer an 8-byte aligned memory buffer from
+ ** which the scratch allocations will be drawn, the size of each scratch
+ ** allocation (sz), and the maximum number of scratch allocations (N). */
sqlite3GlobalConfig.pScratch = va_arg(ap, void*);
sqlite3GlobalConfig.szScratch = va_arg(ap, int);
sqlite3GlobalConfig.nScratch = va_arg(ap, int);
break;
}
case SQLITE_CONFIG_PAGECACHE: {
- /* Designate a buffer for page cache memory space */
+ /* EVIDENCE-OF: R-31408-40510 There are three arguments to
+ ** SQLITE_CONFIG_PAGECACHE: A pointer to 8-byte aligned memory, the size
+ ** of each page buffer (sz), and the number of pages (N). */
sqlite3GlobalConfig.pPage = va_arg(ap, void*);
sqlite3GlobalConfig.szPage = va_arg(ap, int);
sqlite3GlobalConfig.nPage = va_arg(ap, int);
break;
}
+ case SQLITE_CONFIG_PCACHE_HDRSZ: {
+ /* EVIDENCE-OF: R-39100-27317 The SQLITE_CONFIG_PCACHE_HDRSZ option takes
+ ** a single parameter which is a pointer to an integer and writes into
+ ** that integer the number of extra bytes per page required for each page
+ ** in SQLITE_CONFIG_PAGECACHE. */
+ *va_arg(ap, int*) =
+ sqlite3HeaderSizeBtree() +
+ sqlite3HeaderSizePcache() +
+ sqlite3HeaderSizePcache1();
+ break;
+ }
case SQLITE_CONFIG_PCACHE: {
/* no-op */
@@ -125370,11 +126848,18 @@ SQLITE_API int sqlite3_config(int op, ...){
}
case SQLITE_CONFIG_PCACHE2: {
- /* Specify an alternative page cache implementation */
+ /* EVIDENCE-OF: R-63325-48378 The SQLITE_CONFIG_PCACHE2 option takes a
+ ** single argument which is a pointer to an sqlite3_pcache_methods2
+ ** object. This object specifies the interface to a custom page cache
+ ** implementation. */
sqlite3GlobalConfig.pcache2 = *va_arg(ap, sqlite3_pcache_methods2*);
break;
}
case SQLITE_CONFIG_GETPCACHE2: {
+ /* EVIDENCE-OF: R-22035-46182 The SQLITE_CONFIG_GETPCACHE2 option takes a
+ ** single argument which is a pointer to an sqlite3_pcache_methods2
+ ** object. SQLite copies of the current page cache implementation into
+ ** that object. */
if( sqlite3GlobalConfig.pcache2.xInit==0 ){
sqlite3PCacheSetDefault();
}
@@ -125382,9 +126867,14 @@ SQLITE_API int sqlite3_config(int op, ...){
break;
}
+/* EVIDENCE-OF: R-06626-12911 The SQLITE_CONFIG_HEAP option is only
+** available if SQLite is compiled with either SQLITE_ENABLE_MEMSYS3 or
+** SQLITE_ENABLE_MEMSYS5 and returns SQLITE_ERROR if invoked otherwise. */
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
case SQLITE_CONFIG_HEAP: {
- /* Designate a buffer for heap memory space */
+ /* EVIDENCE-OF: R-19854-42126 There are three arguments to
+ ** SQLITE_CONFIG_HEAP: An 8-byte aligned pointer to the memory, the
+ ** number of bytes in the memory buffer, and the minimum allocation size. */
sqlite3GlobalConfig.pHeap = va_arg(ap, void*);
sqlite3GlobalConfig.nHeap = va_arg(ap, int);
sqlite3GlobalConfig.mnReq = va_arg(ap, int);
@@ -125397,17 +126887,19 @@ SQLITE_API int sqlite3_config(int op, ...){
}
if( sqlite3GlobalConfig.pHeap==0 ){
- /* If the heap pointer is NULL, then restore the malloc implementation
- ** back to NULL pointers too. This will cause the malloc to go
- ** back to its default implementation when sqlite3_initialize() is
- ** run.
+ /* EVIDENCE-OF: R-49920-60189 If the first pointer (the memory pointer)
+ ** is NULL, then SQLite reverts to using its default memory allocator
+ ** (the system malloc() implementation), undoing any prior invocation of
+ ** SQLITE_CONFIG_MALLOC.
+ **
+ ** Setting sqlite3GlobalConfig.m to all zeros will cause malloc to
+ ** revert to its default implementation when sqlite3_initialize() is run
*/
memset(&sqlite3GlobalConfig.m, 0, sizeof(sqlite3GlobalConfig.m));
}else{
- /* The heap pointer is not NULL, then install one of the
- ** mem5.c/mem3.c methods. The enclosing #if guarantees at
- ** least one of these methods is currently enabled.
- */
+ /* EVIDENCE-OF: R-61006-08918 If the memory pointer is not NULL then the
+ ** alternative memory allocator is engaged to handle all of SQLites
+ ** memory allocation needs. */
#ifdef SQLITE_ENABLE_MEMSYS3
sqlite3GlobalConfig.m = *sqlite3MemGetMemsys3();
#endif
@@ -125446,11 +126938,19 @@ SQLITE_API int sqlite3_config(int op, ...){
** sqlite3_config(SQLITE_CONFIG_URI,0) configuration calls.
*/
case SQLITE_CONFIG_URI: {
+ /* EVIDENCE-OF: R-25451-61125 The SQLITE_CONFIG_URI option takes a single
+ ** argument of type int. If non-zero, then URI handling is globally
+ ** enabled. If the parameter is zero, then URI handling is globally
+ ** disabled. */
sqlite3GlobalConfig.bOpenUri = va_arg(ap, int);
break;
}
case SQLITE_CONFIG_COVERING_INDEX_SCAN: {
+ /* EVIDENCE-OF: R-36592-02772 The SQLITE_CONFIG_COVERING_INDEX_SCAN
+ ** option takes a single integer argument which is interpreted as a
+ ** boolean in order to enable or disable the use of covering indices for
+ ** full table scans in the query optimizer. */
sqlite3GlobalConfig.bUseCis = va_arg(ap, int);
break;
}
@@ -125465,25 +126965,43 @@ SQLITE_API int sqlite3_config(int op, ...){
#endif
case SQLITE_CONFIG_MMAP_SIZE: {
+ /* EVIDENCE-OF: R-58063-38258 SQLITE_CONFIG_MMAP_SIZE takes two 64-bit
+ ** integer (sqlite3_int64) values that are the default mmap size limit
+ ** (the default setting for PRAGMA mmap_size) and the maximum allowed
+ ** mmap size limit. */
sqlite3_int64 szMmap = va_arg(ap, sqlite3_int64);
sqlite3_int64 mxMmap = va_arg(ap, sqlite3_int64);
- if( mxMmap<0 || mxMmap>SQLITE_MAX_MMAP_SIZE ){
- mxMmap = SQLITE_MAX_MMAP_SIZE;
- }
- sqlite3GlobalConfig.mxMmap = mxMmap;
+ /* EVIDENCE-OF: R-53367-43190 If either argument to this option is
+ ** negative, then that argument is changed to its compile-time default.
+ **
+ ** EVIDENCE-OF: R-34993-45031 The maximum allowed mmap size will be
+ ** silently truncated if necessary so that it does not exceed the
+ ** compile-time maximum mmap size set by the SQLITE_MAX_MMAP_SIZE
+ ** compile-time option.
+ */
+ if( mxMmap<0 || mxMmap>SQLITE_MAX_MMAP_SIZE ) mxMmap = SQLITE_MAX_MMAP_SIZE;
if( szMmap<0 ) szMmap = SQLITE_DEFAULT_MMAP_SIZE;
if( szMmap>mxMmap) szMmap = mxMmap;
+ sqlite3GlobalConfig.mxMmap = mxMmap;
sqlite3GlobalConfig.szMmap = szMmap;
break;
}
-#if SQLITE_OS_WIN && defined(SQLITE_WIN32_MALLOC)
+#if SQLITE_OS_WIN && defined(SQLITE_WIN32_MALLOC) /* IMP: R-04780-55815 */
case SQLITE_CONFIG_WIN32_HEAPSIZE: {
+ /* EVIDENCE-OF: R-34926-03360 SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit
+ ** unsigned integer value that specifies the maximum size of the created
+ ** heap. */
sqlite3GlobalConfig.nHeap = va_arg(ap, int);
break;
}
#endif
+ case SQLITE_CONFIG_PMASZ: {
+ sqlite3GlobalConfig.szPma = va_arg(ap, unsigned int);
+ break;
+ }
+
default: {
rc = SQLITE_ERROR;
break;
@@ -125562,6 +127080,12 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
** Return the mutex associated with a database connection.
*/
SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3 *db){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
return db->mutex;
}
@@ -125571,6 +127095,10 @@ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3 *db){
*/
SQLITE_API int sqlite3_db_release_memory(sqlite3 *db){
int i;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
sqlite3BtreeEnterAll(db);
for(i=0; inDb; i++){
@@ -125660,13 +127188,20 @@ static int binCollFunc(
){
int rc, n;
n = nKey1lastRowid;
}
@@ -125708,6 +127249,12 @@ SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){
** Return the number of changes in the most recent call to sqlite3_exec().
*/
SQLITE_API int sqlite3_changes(sqlite3 *db){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
return db->nChange;
}
@@ -125715,6 +127262,12 @@ SQLITE_API int sqlite3_changes(sqlite3 *db){
** Return the number of changes since the database handle was opened.
*/
SQLITE_API int sqlite3_total_changes(sqlite3 *db){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
return db->nTotalChange;
}
@@ -125894,16 +127447,6 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
for(j=0; jnDb; j++){
struct Db *pDb = &db->aDb[j];
if( pDb->pBt ){
- if( pDb->pSchema ){
- /* Must clear the KeyInfo cache. See ticket [e4a18565a36884b00edf] */
- sqlite3BtreeEnter(pDb->pBt);
- for(i=sqliteHashFirst(&pDb->pSchema->idxHash); i; i=sqliteHashNext(i)){
- Index *pIdx = sqliteHashData(i);
- sqlite3KeyInfoUnref(pIdx->pKeyInfo);
- pIdx->pKeyInfo = 0;
- }
- sqlite3BtreeLeave(pDb->pBt);
- }
sqlite3BtreeClose(pDb->pBt);
pDb->pBt = 0;
if( j!=1 ){
@@ -126210,7 +127753,7 @@ static int sqliteDefaultBusyCallback(
void *ptr, /* Database connection */
int count /* Number of times table has been busy */
){
-#if SQLITE_OS_WIN || (defined(HAVE_USLEEP) && HAVE_USLEEP)
+#if SQLITE_OS_WIN || HAVE_USLEEP
static const u8 delays[] =
{ 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 };
static const u8 totals[] =
@@ -126273,6 +127816,9 @@ SQLITE_API int sqlite3_busy_handler(
int (*xBusy)(void*,int),
void *pArg
){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE;
+#endif
sqlite3_mutex_enter(db->mutex);
db->busyHandler.xFunc = xBusy;
db->busyHandler.pArg = pArg;
@@ -126294,6 +127840,12 @@ SQLITE_API void sqlite3_progress_handler(
int (*xProgress)(void*),
void *pArg
){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return;
+ }
+#endif
sqlite3_mutex_enter(db->mutex);
if( nOps>0 ){
db->xProgress = xProgress;
@@ -126314,6 +127866,9 @@ SQLITE_API void sqlite3_progress_handler(
** specified number of milliseconds before returning 0.
*/
SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
if( ms>0 ){
sqlite3_busy_handler(db, sqliteDefaultBusyCallback, (void*)db);
db->busyTimeout = ms;
@@ -126327,6 +127882,12 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){
** Cause any pending operation to stop at its earliest opportunity.
*/
SQLITE_API void sqlite3_interrupt(sqlite3 *db){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return;
+ }
+#endif
db->u1.isInterrupted = 1;
}
@@ -126464,6 +128025,12 @@ SQLITE_API int sqlite3_create_function_v2(
){
int rc = SQLITE_ERROR;
FuncDestructor *pArg = 0;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ return SQLITE_MISUSE_BKPT;
+ }
+#endif
sqlite3_mutex_enter(db->mutex);
if( xDestroy ){
pArg = (FuncDestructor *)sqlite3DbMallocZero(db, sizeof(FuncDestructor));
@@ -126500,6 +128067,10 @@ SQLITE_API int sqlite3_create_function16(
){
int rc;
char *zFunc8;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || zFunctionName==0 ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
assert( !db->mallocFailed );
zFunc8 = sqlite3Utf16to8(db, zFunctionName, -1, SQLITE_UTF16NATIVE);
@@ -126531,6 +128102,12 @@ SQLITE_API int sqlite3_overload_function(
){
int nName = sqlite3Strlen30(zName);
int rc = SQLITE_OK;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || zName==0 || nArg<-2 ){
+ return SQLITE_MISUSE_BKPT;
+ }
+#endif
sqlite3_mutex_enter(db->mutex);
if( sqlite3FindFunction(db, zName, nName, nArg, SQLITE_UTF8, 0)==0 ){
rc = sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8,
@@ -126552,6 +128129,13 @@ SQLITE_API int sqlite3_overload_function(
*/
SQLITE_API void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){
void *pOld;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
sqlite3_mutex_enter(db->mutex);
pOld = db->pTraceArg;
db->xTrace = xTrace;
@@ -126573,6 +128157,13 @@ SQLITE_API void *sqlite3_profile(
void *pArg
){
void *pOld;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
sqlite3_mutex_enter(db->mutex);
pOld = db->pProfileArg;
db->xProfile = xProfile;
@@ -126593,6 +128184,13 @@ SQLITE_API void *sqlite3_commit_hook(
void *pArg /* Argument to the function */
){
void *pOld;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
sqlite3_mutex_enter(db->mutex);
pOld = db->pCommitArg;
db->xCommitCallback = xCallback;
@@ -126611,6 +128209,13 @@ SQLITE_API void *sqlite3_update_hook(
void *pArg /* Argument to the function */
){
void *pRet;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
sqlite3_mutex_enter(db->mutex);
pRet = db->pUpdateArg;
db->xUpdateCallback = xCallback;
@@ -126629,6 +128234,13 @@ SQLITE_API void *sqlite3_rollback_hook(
void *pArg /* Argument to the function */
){
void *pRet;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
sqlite3_mutex_enter(db->mutex);
pRet = db->pRollbackArg;
db->xRollbackCallback = xCallback;
@@ -126675,6 +128287,9 @@ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int nFrame){
UNUSED_PARAMETER(db);
UNUSED_PARAMETER(nFrame);
#else
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
if( nFrame>0 ){
sqlite3_wal_hook(db, sqlite3WalDefaultHook, SQLITE_INT_TO_PTR(nFrame));
}else{
@@ -126695,6 +128310,12 @@ SQLITE_API void *sqlite3_wal_hook(
){
#ifndef SQLITE_OMIT_WAL
void *pRet;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
sqlite3_mutex_enter(db->mutex);
pRet = db->pWalArg;
db->xWalCallback = xCallback;
@@ -126722,14 +128343,21 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
int rc; /* Return code */
int iDb = SQLITE_MAX_ATTACHED; /* sqlite3.aDb[] index of db to checkpoint */
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
+
/* Initialize the output variables to -1 in case an error occurs. */
if( pnLog ) *pnLog = -1;
if( pnCkpt ) *pnCkpt = -1;
- assert( SQLITE_CHECKPOINT_FULL>SQLITE_CHECKPOINT_PASSIVE );
- assert( SQLITE_CHECKPOINT_FULLSQLITE_CHECKPOINT_RESTART ){
+ assert( SQLITE_CHECKPOINT_PASSIVE==0 );
+ assert( SQLITE_CHECKPOINT_FULL==1 );
+ assert( SQLITE_CHECKPOINT_RESTART==2 );
+ assert( SQLITE_CHECKPOINT_TRUNCATE==3 );
+ if( eModeSQLITE_CHECKPOINT_TRUNCATE ){
+ /* EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint
+ ** mode: */
return SQLITE_MISUSE;
}
@@ -126757,7 +128385,9 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
** checkpointed.
*/
SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){
- return sqlite3_wal_checkpoint_v2(db, zDb, SQLITE_CHECKPOINT_PASSIVE, 0, 0);
+ /* EVIDENCE-OF: R-41613-20553 The sqlite3_wal_checkpoint(D,X) is equivalent to
+ ** sqlite3_wal_checkpoint_v2(D,X,SQLITE_CHECKPOINT_PASSIVE,0,0). */
+ return sqlite3_wal_checkpoint_v2(db,zDb,SQLITE_CHECKPOINT_PASSIVE,0,0);
}
#ifndef SQLITE_OMIT_WAL
@@ -126944,32 +128574,6 @@ SQLITE_API const char *sqlite3_errstr(int rc){
return sqlite3ErrStr(rc);
}
-/*
-** Invalidate all cached KeyInfo objects for database connection "db"
-*/
-static void invalidateCachedKeyInfo(sqlite3 *db){
- Db *pDb; /* A single database */
- int iDb; /* The database index number */
- HashElem *k; /* For looping over tables in pDb */
- Table *pTab; /* A table in the database */
- Index *pIdx; /* Each index */
-
- for(iDb=0, pDb=db->aDb; iDbnDb; iDb++, pDb++){
- if( pDb->pBt==0 ) continue;
- sqlite3BtreeEnter(pDb->pBt);
- for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){
- pTab = (Table*)sqliteHashData(k);
- for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
- if( pIdx->pKeyInfo && pIdx->pKeyInfo->db==db ){
- sqlite3KeyInfoUnref(pIdx->pKeyInfo);
- pIdx->pKeyInfo = 0;
- }
- }
- }
- sqlite3BtreeLeave(pDb->pBt);
- }
-}
-
/*
** Create a new collating function for database "db". The name is zName
** and the encoding is enc.
@@ -127013,7 +128617,6 @@ static int createCollation(
return SQLITE_BUSY;
}
sqlite3ExpirePreparedStatements(db);
- invalidateCachedKeyInfo(db);
/* If collation sequence pColl was created directly by a call to
** sqlite3_create_collation, and not generated by synthCollSeq(),
@@ -127118,6 +128721,12 @@ static const int aHardLimit[] = {
SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){
int oldLimit;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return -1;
+ }
+#endif
/* EVIDENCE-OF: R-30189-54097 For each limit category SQLITE_LIMIT_NAME
** there is a hard upper bound set at compile-time by a C preprocessor
@@ -127194,7 +128803,8 @@ SQLITE_PRIVATE int sqlite3ParseUri(
assert( *pzErrMsg==0 );
- if( ((flags & SQLITE_OPEN_URI) || sqlite3GlobalConfig.bOpenUri)
+ if( ((flags & SQLITE_OPEN_URI) /* IMP: R-48725-32206 */
+ || sqlite3GlobalConfig.bOpenUri) /* IMP: R-51689-46548 */
&& nUri>=5 && memcmp(zUri, "file:", 5)==0 /* IMP: R-57884-37496 */
){
char *zOpt;
@@ -127403,6 +129013,9 @@ static int openDatabase(
char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */
char *zErrMsg = 0; /* Error message from sqlite3ParseUri() */
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( ppDb==0 ) return SQLITE_MISUSE_BKPT;
+#endif
*ppDb = 0;
#ifndef SQLITE_OMIT_AUTOINIT
rc = sqlite3_initialize();
@@ -127507,6 +129120,9 @@ static int openDatabase(
#endif
#if defined(SQLITE_DEFAULT_FOREIGN_KEYS) && SQLITE_DEFAULT_FOREIGN_KEYS
| SQLITE_ForeignKeys
+#endif
+#if defined(SQLITE_REVERSE_UNORDERED_SELECTS)
+ | SQLITE_ReverseOrder
#endif
;
sqlite3HashInit(&db->aCollSeq);
@@ -127517,20 +129133,24 @@ static int openDatabase(
/* Add the default collation sequence BINARY. BINARY works for both UTF-8
** and UTF-16, so add a version for each to avoid any unnecessary
** conversions. The only error that can occur here is a malloc() failure.
+ **
+ ** EVIDENCE-OF: R-52786-44878 SQLite defines three built-in collating
+ ** functions:
*/
createCollation(db, "BINARY", SQLITE_UTF8, 0, binCollFunc, 0);
createCollation(db, "BINARY", SQLITE_UTF16BE, 0, binCollFunc, 0);
createCollation(db, "BINARY", SQLITE_UTF16LE, 0, binCollFunc, 0);
+ createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0);
createCollation(db, "RTRIM", SQLITE_UTF8, (void*)1, binCollFunc, 0);
if( db->mallocFailed ){
goto opendb_out;
}
+ /* EVIDENCE-OF: R-08308-17224 The default collating function for all
+ ** strings is BINARY.
+ */
db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 0);
assert( db->pDfltColl!=0 );
- /* Also add a UTF-8 case-insensitive collation sequence. */
- createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0);
-
/* Parse the filename/URI argument. */
db->openFlags = flags;
rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg);
@@ -127553,6 +129173,7 @@ static int openDatabase(
}
sqlite3BtreeEnter(db->aDb[0].pBt);
db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt);
+ if( !db->mallocFailed ) ENC(db) = SCHEMA_ENC(db);
sqlite3BtreeLeave(db->aDb[0].pBt);
db->aDb[1].pSchema = sqlite3SchemaGet(db, 0);
@@ -127694,13 +129315,15 @@ SQLITE_API int sqlite3_open16(
sqlite3_value *pVal;
int rc;
- assert( zFilename );
- assert( ppDb );
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( ppDb==0 ) return SQLITE_MISUSE_BKPT;
+#endif
*ppDb = 0;
#ifndef SQLITE_OMIT_AUTOINIT
rc = sqlite3_initialize();
if( rc ) return rc;
#endif
+ if( zFilename==0 ) zFilename = "\000\000";
pVal = sqlite3ValueNew(0);
sqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC);
zFilename8 = sqlite3ValueText(pVal, SQLITE_UTF8);
@@ -127709,7 +129332,7 @@ SQLITE_API int sqlite3_open16(
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0);
assert( *ppDb || rc==SQLITE_NOMEM );
if( rc==SQLITE_OK && !DbHasProperty(*ppDb, 0, DB_SchemaLoaded) ){
- ENC(*ppDb) = SQLITE_UTF16NATIVE;
+ SCHEMA_ENC(*ppDb) = ENC(*ppDb) = SQLITE_UTF16NATIVE;
}
}else{
rc = SQLITE_NOMEM;
@@ -127730,13 +129353,7 @@ SQLITE_API int sqlite3_create_collation(
void* pCtx,
int(*xCompare)(void*,int,const void*,int,const void*)
){
- int rc;
- sqlite3_mutex_enter(db->mutex);
- assert( !db->mallocFailed );
- rc = createCollation(db, zName, (u8)enc, pCtx, xCompare, 0);
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
- return rc;
+ return sqlite3_create_collation_v2(db, zName, enc, pCtx, xCompare, 0);
}
/*
@@ -127751,6 +129368,10 @@ SQLITE_API int sqlite3_create_collation_v2(
void(*xDel)(void*)
){
int rc;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
assert( !db->mallocFailed );
rc = createCollation(db, zName, (u8)enc, pCtx, xCompare, xDel);
@@ -127772,6 +129393,10 @@ SQLITE_API int sqlite3_create_collation16(
){
int rc = SQLITE_OK;
char *zName8;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
assert( !db->mallocFailed );
zName8 = sqlite3Utf16to8(db, zName, -1, SQLITE_UTF16NATIVE);
@@ -127794,6 +129419,9 @@ SQLITE_API int sqlite3_collation_needed(
void *pCollNeededArg,
void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*)
){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
db->xCollNeeded = xCollNeeded;
db->xCollNeeded16 = 0;
@@ -127812,6 +129440,9 @@ SQLITE_API int sqlite3_collation_needed16(
void *pCollNeededArg,
void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*)
){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
db->xCollNeeded = 0;
db->xCollNeeded16 = xCollNeeded16;
@@ -127838,6 +129469,12 @@ SQLITE_API int sqlite3_global_recover(void){
** by the next COMMIT or ROLLBACK.
*/
SQLITE_API int sqlite3_get_autocommit(sqlite3 *db){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
return db->autoCommit;
}
@@ -127891,7 +129528,6 @@ SQLITE_API void sqlite3_thread_cleanup(void){
** Return meta information about a specific column of a database table.
** See comment in sqlite3.h (sqlite.h.in) for details.
*/
-#ifdef SQLITE_ENABLE_COLUMN_METADATA
SQLITE_API int sqlite3_table_column_metadata(
sqlite3 *db, /* Connection handle */
const char *zDbName, /* Database name or NULL */
@@ -127907,7 +129543,7 @@ SQLITE_API int sqlite3_table_column_metadata(
char *zErrMsg = 0;
Table *pTab = 0;
Column *pCol = 0;
- int iCol;
+ int iCol = 0;
char const *zDataType = 0;
char const *zCollSeq = 0;
@@ -127931,11 +129567,8 @@ SQLITE_API int sqlite3_table_column_metadata(
}
/* Find the column for which info is requested */
- if( sqlite3IsRowid(zColumnName) ){
- iCol = pTab->iPKey;
- if( iCol>=0 ){
- pCol = &pTab->aCol[iCol];
- }
+ if( zColumnName==0 ){
+ /* Query for existance of table only */
}else{
for(iCol=0; iColnCol; iCol++){
pCol = &pTab->aCol[iCol];
@@ -127944,8 +129577,13 @@ SQLITE_API int sqlite3_table_column_metadata(
}
}
if( iCol==pTab->nCol ){
- pTab = 0;
- goto error_out;
+ if( HasRowid(pTab) && sqlite3IsRowid(zColumnName) ){
+ iCol = pTab->iPKey;
+ pCol = iCol>=0 ? &pTab->aCol[iCol] : 0;
+ }else{
+ pTab = 0;
+ goto error_out;
+ }
}
}
@@ -127998,7 +129636,6 @@ error_out:
sqlite3_mutex_leave(db->mutex);
return rc;
}
-#endif
/*
** Sleep for a little while. Return the amount of time slept.
@@ -128020,6 +129657,9 @@ SQLITE_API int sqlite3_sleep(int ms){
** Enable or disable the extended result codes.
*/
SQLITE_API int sqlite3_extended_result_codes(sqlite3 *db, int onoff){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
db->errMask = onoff ? 0xffffffff : 0xff;
sqlite3_mutex_leave(db->mutex);
@@ -128033,6 +129673,9 @@ SQLITE_API int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, vo
int rc = SQLITE_ERROR;
Btree *pBtree;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
sqlite3_mutex_enter(db->mutex);
pBtree = sqlite3DbNameToBtree(db, zDbName);
if( pBtree ){
@@ -128375,7 +130018,7 @@ SQLITE_API int sqlite3_test_control(int op, ...){
** returns a NULL pointer.
*/
SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam){
- if( zFilename==0 ) return 0;
+ if( zFilename==0 || zParam==0 ) return 0;
zFilename += sqlite3Strlen30(zFilename) + 1;
while( zFilename[0] ){
int x = strcmp(zFilename, zParam);
@@ -128431,7 +130074,14 @@ SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){
** connection.
*/
SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName){
- Btree *pBt = sqlite3DbNameToBtree(db, zDbName);
+ Btree *pBt;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
+ pBt = sqlite3DbNameToBtree(db, zDbName);
return pBt ? sqlite3BtreeGetFilename(pBt) : 0;
}
@@ -128440,7 +130090,14 @@ SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName){
** no such database exists.
*/
SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){
- Btree *pBt = sqlite3DbNameToBtree(db, zDbName);
+ Btree *pBt;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return -1;
+ }
+#endif
+ pBt = sqlite3DbNameToBtree(db, zDbName);
return pBt ? sqlite3BtreeIsReadonly(pBt) : -1;
}
@@ -131509,7 +133166,7 @@ static int fts3SelectLeaf(
sqlite3_int64 *piLeaf, /* Selected leaf node */
sqlite3_int64 *piLeaf2 /* Selected leaf node */
){
- int rc; /* Return code */
+ int rc = SQLITE_OK; /* Return code */
int iHeight; /* Height of this node in tree */
assert( piLeaf || piLeaf2 );
@@ -131520,7 +133177,7 @@ static int fts3SelectLeaf(
if( rc==SQLITE_OK && iHeight>1 ){
char *zBlob = 0; /* Blob read from %_segments table */
- int nBlob; /* Size of zBlob in bytes */
+ int nBlob = 0; /* Size of zBlob in bytes */
if( piLeaf && piLeaf2 && (*piLeaf!=*piLeaf2) ){
rc = sqlite3Fts3ReadBlock(p, *piLeaf, &zBlob, &nBlob, 0);
@@ -132742,7 +134399,7 @@ static int fts3FilterMethod(
int nVal, /* Number of elements in apVal */
sqlite3_value **apVal /* Arguments for the indexing scheme */
){
- int rc;
+ int rc = SQLITE_OK;
char *zSql; /* SQL statement used to access %_content */
int eSearch;
Fts3Table *p = (Fts3Table *)pCursor->pVtab;
@@ -137861,7 +139518,7 @@ static int isVowel(const char *z){
** by a consonant.
**
** In this routine z[] is in reverse order. So we are really looking
-** for an instance of of a consonant followed by a vowel.
+** for an instance of a consonant followed by a vowel.
*/
static int m_gt_0(const char *z){
while( isVowel(z) ){ z++; }
@@ -139230,7 +140887,7 @@ static int fts3tokConnectMethod(
sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
char **pzErr /* OUT: sqlite3_malloc'd error message */
){
- Fts3tokTable *pTab;
+ Fts3tokTable *pTab = 0;
const sqlite3_tokenizer_module *pMod = 0;
sqlite3_tokenizer *pTok = 0;
int rc;
@@ -142605,8 +144262,8 @@ static int fts3PromoteSegments(
if( bOk ){
int iIdx = 0;
- sqlite3_stmt *pUpdate1;
- sqlite3_stmt *pUpdate2;
+ sqlite3_stmt *pUpdate1 = 0;
+ sqlite3_stmt *pUpdate2 = 0;
if( rc==SQLITE_OK ){
rc = fts3SqlStmt(p, SQL_UPDATE_LEVEL_IDX, &pUpdate1, 0);
@@ -147838,13 +149495,12 @@ static int readInt16(u8 *p){
return (p[0]<<8) + p[1];
}
static void readCoord(u8 *p, RtreeCoord *pCoord){
- u32 i = (
+ pCoord->u = (
(((u32)p[0]) << 24) +
(((u32)p[1]) << 16) +
(((u32)p[2]) << 8) +
(((u32)p[3]) << 0)
);
- *(u32 *)pCoord = i;
}
static i64 readInt64(u8 *p){
return (
@@ -147873,7 +149529,7 @@ static int writeCoord(u8 *p, RtreeCoord *pCoord){
u32 i;
assert( sizeof(RtreeCoord)==4 );
assert( sizeof(u32)==4 );
- i = *(u32 *)pCoord;
+ i = pCoord->u;
p[0] = (i>>24)&0xFF;
p[1] = (i>>16)&0xFF;
p[2] = (i>> 8)&0xFF;
@@ -148204,14 +149860,13 @@ static void nodeGetCell(
RtreeCell *pCell /* OUT: Write the cell contents here */
){
u8 *pData;
- u8 *pEnd;
RtreeCoord *pCoord;
+ int ii;
pCell->iRowid = nodeGetRowid(pRtree, pNode, iCell);
pData = pNode->zData + (12 + pRtree->nBytesPerCell*iCell);
- pEnd = pData + pRtree->nDim*8;
pCoord = pCell->aCoord;
- for(; pDatanDim*2; ii++){
+ readCoord(&pData[ii*4], &pCoord[ii]);
}
}
@@ -148651,7 +150306,7 @@ static RtreeSearchPoint *rtreeEnqueue(
pNew = pCur->aPoint + i;
pNew->rScore = rScore;
pNew->iLevel = iLevel;
- assert( iLevel>=0 && iLevel<=RTREE_MAX_DEPTH );
+ assert( iLevel<=RTREE_MAX_DEPTH );
while( i>0 ){
RtreeSearchPoint *pParent;
j = (i-1)/2;
@@ -150275,6 +151930,8 @@ static int rtreeUpdate(
rtreeReference(pRtree);
assert(nData>=1);
+ cell.iRowid = 0; /* Used only to suppress a compiler warning */
+
/* Constraint handling. A write operation on an r-tree table may return
** SQLITE_CONSTRAINT for two reasons:
**
diff --git a/TMessagesProj/jni/sqlite/sqlite3.h b/TMessagesProj/jni/sqlite/sqlite3.h
index c31f126d..07406477 100644
--- a/TMessagesProj/jni/sqlite/sqlite3.h
+++ b/TMessagesProj/jni/sqlite/sqlite3.h
@@ -57,7 +57,7 @@ extern "C" {
/*
** These no-op macros are used in front of interfaces to mark those
** interfaces as either deprecated or experimental. New applications
-** should not use deprecated interfaces - they are support for backwards
+** should not use deprecated interfaces - they are supported for backwards
** compatibility only. Application writers should be aware that
** experimental interfaces are subject to change in point releases.
**
@@ -107,9 +107,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.8.7.4"
-#define SQLITE_VERSION_NUMBER 3008007
-#define SQLITE_SOURCE_ID "2014-12-09 01:34:36 f66f7a17b78ba617acde90fc810107f34f1a1f2e"
+#define SQLITE_VERSION "3.8.8.1"
+#define SQLITE_VERSION_NUMBER 3008008
+#define SQLITE_SOURCE_ID "2015-01-20 16:51:25 f73337e3e289915a76ca96e7a05a1a8d4e890d55"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -201,7 +201,7 @@ SQLITE_API const char *sqlite3_compileoption_get(int N);
** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but
** can be fully or partially disabled using a call to [sqlite3_config()]
** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD],
-** or [SQLITE_CONFIG_MUTEX]. ^(The return value of the
+** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the
** sqlite3_threadsafe() function shows only the compile-time setting of
** thread safety, not any run-time changes to that setting made by
** sqlite3_config(). In other words, the return value from sqlite3_threadsafe()
@@ -1221,7 +1221,7 @@ struct sqlite3_vfs {
**
**
** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as
-** was given no the corresponding lock.
+** was given on the corresponding lock.
**
** The xShmLock method can transition between unlocked and SHARED or
** between unlocked and EXCLUSIVE. It cannot transition between SHARED
@@ -1504,26 +1504,28 @@ struct sqlite3_mem_methods {
** SQLITE_CONFIG_SERIALIZED configuration option.
**
** [[SQLITE_CONFIG_MALLOC]]
SQLITE_CONFIG_MALLOC
-**
^(This option takes a single argument which is a pointer to an
-** instance of the [sqlite3_mem_methods] structure. The argument specifies
+**
^(The SQLITE_CONFIG_MALLOC option takes a single argument which is
+** a pointer to an instance of the [sqlite3_mem_methods] structure.
+** The argument specifies
** alternative low-level memory allocation routines to be used in place of
** the memory allocation routines built into SQLite.)^ ^SQLite makes
** its own private copy of the content of the [sqlite3_mem_methods] structure
** before the [sqlite3_config()] call returns.
**
** [[SQLITE_CONFIG_GETMALLOC]]
SQLITE_CONFIG_GETMALLOC
-**
^(This option takes a single argument which is a pointer to an
-** instance of the [sqlite3_mem_methods] structure. The [sqlite3_mem_methods]
+**
^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which
+** is a pointer to an instance of the [sqlite3_mem_methods] structure.
+** The [sqlite3_mem_methods]
** structure is filled with the currently defined memory allocation routines.)^
** This option can be used to overload the default memory allocation
** routines with a wrapper that simulations memory allocation failure or
** tracks memory usage, for example.
**
** [[SQLITE_CONFIG_MEMSTATUS]]
SQLITE_CONFIG_MEMSTATUS
-**
^This option takes single argument of type int, interpreted as a
-** boolean, which enables or disables the collection of memory allocation
-** statistics. ^(When memory allocation statistics are disabled, the
-** following SQLite interfaces become non-operational:
+**
^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
+** interpreted as a boolean, which enables or disables the collection of
+** memory allocation statistics. ^(When memory allocation statistics are
+** disabled, the following SQLite interfaces become non-operational:
**
^This option specifies a static memory buffer that SQLite can use for
-** scratch memory. There are three arguments: A pointer an 8-byte
+**
^The SQLITE_CONFIG_SCRATCH option specifies a static memory buffer
+** that SQLite can use for scratch memory. ^(There are three arguments
+** to SQLITE_CONFIG_SCRATCH: A pointer an 8-byte
** aligned memory buffer from which the scratch allocations will be
** drawn, the size of each scratch allocation (sz),
-** and the maximum number of scratch allocations (N). The sz
-** argument must be a multiple of 16.
+** and the maximum number of scratch allocations (N).)^
** The first argument must be a pointer to an 8-byte aligned buffer
** of at least sz*N bytes of memory.
-** ^SQLite will use no more than two scratch buffers per thread. So
-** N should be set to twice the expected maximum number of threads.
-** ^SQLite will never require a scratch buffer that is more than 6
-** times the database page size. ^If SQLite needs needs additional
+** ^SQLite will not use more than one scratch buffers per thread.
+** ^SQLite will never request a scratch buffer that is more than 6
+** times the database page size.
+** ^If SQLite needs needs additional
** scratch memory beyond what is provided by this configuration option, then
-** [sqlite3_malloc()] will be used to obtain the memory needed.
+** [sqlite3_malloc()] will be used to obtain the memory needed.
+** ^When the application provides any amount of scratch memory using
+** SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary large
+** [sqlite3_malloc|heap allocations].
+** This can help [Robson proof|prevent memory allocation failures] due to heap
+** fragmentation in low-memory embedded systems.
+**
**
** [[SQLITE_CONFIG_PAGECACHE]]
SQLITE_CONFIG_PAGECACHE
-**
^This option specifies a static memory buffer that SQLite can use for
-** the database page cache with the default page cache implementation.
+**
^The SQLITE_CONFIG_PAGECACHE option specifies a static memory buffer
+** that SQLite can use for the database page cache with the default page
+** cache implementation.
** This configuration should not be used if an application-define page
-** cache implementation is loaded using the SQLITE_CONFIG_PCACHE2 option.
-** There are three arguments to this option: A pointer to 8-byte aligned
+** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2]
+** configuration option.
+** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to
+** 8-byte aligned
** memory, the size of each page buffer (sz), and the number of pages (N).
** The sz argument should be the size of the largest database page
-** (a power of two between 512 and 32768) plus a little extra for each
-** page header. ^The page header size is 20 to 40 bytes depending on
-** the host architecture. ^It is harmless, apart from the wasted memory,
-** to make sz a little too large. The first
-** argument should point to an allocation of at least sz*N bytes of memory.
+** (a power of two between 512 and 65536) plus some extra bytes for each
+** page header. ^The number of extra bytes needed by the page header
+** can be determined using the [SQLITE_CONFIG_PCACHE_HDRSZ] option
+** to [sqlite3_config()].
+** ^It is harmless, apart from the wasted memory,
+** for the sz parameter to be larger than necessary. The first
+** argument should pointer to an 8-byte aligned block of memory that
+** is at least sz*N bytes of memory, otherwise subsequent behavior is
+** undefined.
** ^SQLite will use the memory provided by the first argument to satisfy its
** memory needs for the first N pages that it adds to cache. ^If additional
** page cache memory is needed beyond what is provided by this option, then
-** SQLite goes to [sqlite3_malloc()] for the additional storage space.
-** The pointer in the first argument must
-** be aligned to an 8-byte boundary or subsequent behavior of SQLite
-** will be undefined.
+** SQLite goes to [sqlite3_malloc()] for the additional storage space.
**
** [[SQLITE_CONFIG_HEAP]]
SQLITE_CONFIG_HEAP
-**
^This option specifies a static memory buffer that SQLite will use
-** for all of its dynamic memory allocation needs beyond those provided
-** for by [SQLITE_CONFIG_SCRATCH] and [SQLITE_CONFIG_PAGECACHE].
-** There are three arguments: An 8-byte aligned pointer to the memory,
+**
^The SQLITE_CONFIG_HEAP option specifies a static memory buffer
+** that SQLite will use for all of its dynamic memory allocation needs
+** beyond those provided for by [SQLITE_CONFIG_SCRATCH] and
+** [SQLITE_CONFIG_PAGECACHE].
+** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled
+** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns
+** [SQLITE_ERROR] if invoked otherwise.
+** ^There are three arguments to SQLITE_CONFIG_HEAP:
+** An 8-byte aligned pointer to the memory,
** the number of bytes in the memory buffer, and the minimum allocation size.
** ^If the first pointer (the memory pointer) is NULL, then SQLite reverts
** to using its default memory allocator (the system malloc() implementation),
** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the
-** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or
-** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory
+** memory pointer is not NULL then the alternative memory
** allocator is engaged to handle all of SQLites memory allocation needs.
** The first pointer (the memory pointer) must be aligned to an 8-byte
** boundary or subsequent behavior of SQLite will be undefined.
@@ -1590,11 +1606,11 @@ struct sqlite3_mem_methods {
** for the minimum allocation size are 2**5 through 2**8.
**
** [[SQLITE_CONFIG_MUTEX]]
SQLITE_CONFIG_MUTEX
-**
^(This option takes a single argument which is a pointer to an
-** instance of the [sqlite3_mutex_methods] structure. The argument specifies
-** alternative low-level mutex routines to be used in place
-** the mutex routines built into SQLite.)^ ^SQLite makes a copy of the
-** content of the [sqlite3_mutex_methods] structure before the call to
+**
^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a
+** pointer to an instance of the [sqlite3_mutex_methods] structure.
+** The argument specifies alternative low-level mutex routines to be used
+** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of
+** the content of the [sqlite3_mutex_methods] structure before the call to
** [sqlite3_config()] returns. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** the entire mutexing subsystem is omitted from the build and hence calls to
@@ -1602,8 +1618,8 @@ struct sqlite3_mem_methods {
** return [SQLITE_ERROR].
**
** [[SQLITE_CONFIG_GETMUTEX]]
SQLITE_CONFIG_GETMUTEX
-**
^(This option takes a single argument which is a pointer to an
-** instance of the [sqlite3_mutex_methods] structure. The
+**
^(The SQLITE_CONFIG_GETMUTEX option takes a single argument which
+** is a pointer to an instance of the [sqlite3_mutex_methods] structure. The
** [sqlite3_mutex_methods]
** structure is filled with the currently defined mutex routines.)^
** This option can be used to overload the default mutex allocation
@@ -1615,25 +1631,25 @@ struct sqlite3_mem_methods {
** return [SQLITE_ERROR].
**
** [[SQLITE_CONFIG_LOOKASIDE]]
SQLITE_CONFIG_LOOKASIDE
-**
^(This option takes two arguments that determine the default
-** memory allocation for the lookaside memory allocator on each
-** [database connection]. The first argument is the
+**
^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine
+** the default size of lookaside memory on each [database connection].
+** The first argument is the
** size of each lookaside buffer slot and the second is the number of
-** slots allocated to each database connection.)^ ^(This option sets the
-** default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
-** verb to [sqlite3_db_config()] can be used to change the lookaside
+** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE
+** sets the default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
+** option to [sqlite3_db_config()] can be used to change the lookaside
** configuration on individual connections.)^
**
** [[SQLITE_CONFIG_PCACHE2]]
SQLITE_CONFIG_PCACHE2
-**
^(This option takes a single argument which is a pointer to
-** an [sqlite3_pcache_methods2] object. This object specifies the interface
-** to a custom page cache implementation.)^ ^SQLite makes a copy of the
-** object and uses it for page cache memory allocations.
+**
^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
+** a pointer to an [sqlite3_pcache_methods2] object. This object specifies
+** the interface to a custom page cache implementation.)^
+** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.
**
** [[SQLITE_CONFIG_GETPCACHE2]]
SQLITE_CONFIG_GETPCACHE2
-**
^(This option takes a single argument which is a pointer to an
-** [sqlite3_pcache_methods2] object. SQLite copies of the current
-** page cache implementation into that object.)^
+**
^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which
+** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of
+** the current page cache implementation into that object.)^
**
** [[SQLITE_CONFIG_LOG]]
SQLITE_CONFIG_LOG
**
The SQLITE_CONFIG_LOG option is used to configure the SQLite
@@ -1656,10 +1672,11 @@ struct sqlite3_mem_methods {
** function must be threadsafe.
**
** [[SQLITE_CONFIG_URI]]
SQLITE_CONFIG_URI
-**
^(This option takes a single argument of type int. If non-zero, then
-** URI handling is globally enabled. If the parameter is zero, then URI handling
-** is globally disabled.)^ ^If URI handling is globally enabled, all filenames
-** passed to [sqlite3_open()], [sqlite3_open_v2()], [sqlite3_open16()] or
+**
^(The SQLITE_CONFIG_URI option takes a single argument of type int.
+** If non-zero, then URI handling is globally enabled. If the parameter is zero,
+** then URI handling is globally disabled.)^ ^If URI handling is globally
+** enabled, all filenames passed to [sqlite3_open()], [sqlite3_open_v2()],
+** [sqlite3_open16()] or
** specified as part of [ATTACH] commands are interpreted as URIs, regardless
** of whether or not the [SQLITE_OPEN_URI] flag is set when the database
** connection is opened. ^If it is globally disabled, filenames are
@@ -1669,9 +1686,10 @@ struct sqlite3_mem_methods {
** [SQLITE_USE_URI] symbol defined.)^
**
** [[SQLITE_CONFIG_COVERING_INDEX_SCAN]]
SQLITE_CONFIG_COVERING_INDEX_SCAN
-**
^This option takes a single integer argument which is interpreted as
-** a boolean in order to enable or disable the use of covering indices for
-** full table scans in the query optimizer. ^The default setting is determined
+**
^The SQLITE_CONFIG_COVERING_INDEX_SCAN option takes a single integer
+** argument which is interpreted as a boolean in order to enable or disable
+** the use of covering indices for full table scans in the query optimizer.
+** ^The default setting is determined
** by the [SQLITE_ALLOW_COVERING_INDEX_SCAN] compile-time option, or is "on"
** if that compile-time option is omitted.
** The ability to disable the use of covering indices for full table scans
@@ -1711,19 +1729,39 @@ struct sqlite3_mem_methods {
** ^The default setting can be overridden by each database connection using
** either the [PRAGMA mmap_size] command, or by using the
** [SQLITE_FCNTL_MMAP_SIZE] file control. ^(The maximum allowed mmap size
-** cannot be changed at run-time. Nor may the maximum allowed mmap size
-** exceed the compile-time maximum mmap size set by the
+** will be silently truncated if necessary so that it does not exceed the
+** compile-time maximum mmap size set by the
** [SQLITE_MAX_MMAP_SIZE] compile-time option.)^
** ^If either argument to this option is negative, then that argument is
** changed to its compile-time default.
**
** [[SQLITE_CONFIG_WIN32_HEAPSIZE]]
**
SQLITE_CONFIG_WIN32_HEAPSIZE
-**
^This option is only available if SQLite is compiled for Windows
-** with the [SQLITE_WIN32_MALLOC] pre-processor macro defined.
-** SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value
+**
^The SQLITE_CONFIG_WIN32_HEAPSIZE option is only available if SQLite is
+** compiled for Windows with the [SQLITE_WIN32_MALLOC] pre-processor macro
+** defined. ^SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value
** that specifies the maximum size of the created heap.
**
+**
+** [[SQLITE_CONFIG_PCACHE_HDRSZ]]
+**
SQLITE_CONFIG_PCACHE_HDRSZ
+**
^The SQLITE_CONFIG_PCACHE_HDRSZ option takes a single parameter which
+** is a pointer to an integer and writes into that integer the number of extra
+** bytes per page required for each page in [SQLITE_CONFIG_PAGECACHE].
+** The amount of extra space required can change depending on the compiler,
+** target platform, and SQLite version.
+**
+** [[SQLITE_CONFIG_PMASZ]]
+**
SQLITE_CONFIG_PMASZ
+**
^The SQLITE_CONFIG_PMASZ option takes a single parameter which
+** is an unsigned integer and sets the "Minimum PMA Size" for the multithreaded
+** sorter to that integer. The default minimum PMA Size is set by the
+** [SQLITE_SORTER_PMASZ] compile-time option. New threads are launched
+** to help with sort operations when multithreaded sorting
+** is enabled (using the [PRAGMA threads] command) and the amount of content
+** to be sorted exceeds the page size times the minimum of the
+** [PRAGMA cache_size] setting and this value.
+**
*/
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
@@ -1748,6 +1786,8 @@ struct sqlite3_mem_methods {
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */
+#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
+#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
/*
** CAPI3REF: Database Connection Configuration Options
@@ -1875,47 +1915,45 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
/*
** CAPI3REF: Count The Number Of Rows Modified
**
-** ^This function returns the number of database rows that were changed
-** or inserted or deleted by the most recently completed SQL statement
-** on the [database connection] specified by the first parameter.
-** ^(Only changes that are directly specified by the [INSERT], [UPDATE],
-** or [DELETE] statement are counted. Auxiliary changes caused by
-** triggers or [foreign key actions] are not counted.)^ Use the
-** [sqlite3_total_changes()] function to find the total number of changes
-** including changes caused by triggers and foreign key actions.
+** ^This function returns the number of rows modified, inserted or
+** deleted by the most recently completed INSERT, UPDATE or DELETE
+** statement on the database connection specified by the only parameter.
+** ^Executing any other type of SQL statement does not modify the value
+** returned by this function.
**
-** ^Changes to a view that are simulated by an [INSTEAD OF trigger]
-** are not counted. Only real table changes are counted.
+** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are
+** considered - auxiliary changes caused by [CREATE TRIGGER | triggers],
+** [foreign key actions] or [REPLACE] constraint resolution are not counted.
+**
+** Changes to a view that are intercepted by
+** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value
+** returned by sqlite3_changes() immediately after an INSERT, UPDATE or
+** DELETE statement run on a view is always zero. Only changes made to real
+** tables are counted.
**
-** ^(A "row change" is a change to a single row of a single table
-** caused by an INSERT, DELETE, or UPDATE statement. Rows that
-** are changed as side effects of [REPLACE] constraint resolution,
-** rollback, ABORT processing, [DROP TABLE], or by any other
-** mechanisms do not count as direct row changes.)^
-**
-** A "trigger context" is a scope of execution that begins and
-** ends with the script of a [CREATE TRIGGER | trigger].
-** Most SQL statements are
-** evaluated outside of any trigger. This is the "top level"
-** trigger context. If a trigger fires from the top level, a
-** new trigger context is entered for the duration of that one
-** trigger. Subtriggers create subcontexts for their duration.
-**
-** ^Calling [sqlite3_exec()] or [sqlite3_step()] recursively does
-** not create a new trigger context.
-**
-** ^This function returns the number of direct row changes in the
-** most recent INSERT, UPDATE, or DELETE statement within the same
-** trigger context.
-**
-** ^Thus, when called from the top level, this function returns the
-** number of changes in the most recent INSERT, UPDATE, or DELETE
-** that also occurred at the top level. ^(Within the body of a trigger,
-** the sqlite3_changes() interface can be called to find the number of
-** changes in the most recently completed INSERT, UPDATE, or DELETE
-** statement within the body of the same trigger.
-** However, the number returned does not include changes
-** caused by subtriggers since those have their own context.)^
+** Things are more complicated if the sqlite3_changes() function is
+** executed while a trigger program is running. This may happen if the
+** program uses the [changes() SQL function], or if some other callback
+** function invokes sqlite3_changes() directly. Essentially:
+**
+**
+**
^(Before entering a trigger program the value returned by
+** sqlite3_changes() function is saved. After the trigger program
+** has finished, the original value is restored.)^
+**
+**
^(Within a trigger program each INSERT, UPDATE and DELETE
+** statement sets the value returned by sqlite3_changes()
+** upon completion as normal. Of course, this value will not include
+** any changes performed by sub-triggers, as the sqlite3_changes()
+** value will be saved and restored after each sub-trigger has run.)^
+**
+**
+** ^This means that if the changes() SQL function (or similar) is used
+** by the first INSERT, UPDATE or DELETE statement within a trigger, it
+** returns the value as set when the calling statement began executing.
+** ^If it is used by the second or subsequent such statement within a trigger
+** program, the value returned reflects the number of rows modified by the
+** previous INSERT, UPDATE or DELETE statement within the same trigger.
**
** See also the [sqlite3_total_changes()] interface, the
** [count_changes pragma], and the [changes() SQL function].
@@ -1929,20 +1967,17 @@ SQLITE_API int sqlite3_changes(sqlite3*);
/*
** CAPI3REF: Total Number Of Rows Modified
**
-** ^This function returns the number of row changes caused by [INSERT],
-** [UPDATE] or [DELETE] statements since the [database connection] was opened.
-** ^(The count returned by sqlite3_total_changes() includes all changes
-** from all [CREATE TRIGGER | trigger] contexts and changes made by
-** [foreign key actions]. However,
-** the count does not include changes used to implement [REPLACE] constraints,
-** do rollbacks or ABORT processing, or [DROP TABLE] processing. The
-** count does not include rows of views that fire an [INSTEAD OF trigger],
-** though if the INSTEAD OF trigger makes changes of its own, those changes
-** are counted.)^
-** ^The sqlite3_total_changes() function counts the changes as soon as
-** the statement that makes them is completed (when the statement handle
-** is passed to [sqlite3_reset()] or [sqlite3_finalize()]).
-**
+** ^This function returns the total number of rows inserted, modified or
+** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed
+** since the database connection was opened, including those executed as
+** part of trigger programs. ^Executing any other type of SQL statement
+** does not affect the value returned by sqlite3_total_changes().
+**
+** ^Changes made as part of [foreign key actions] are included in the
+** count, but those made as part of REPLACE constraint resolution are
+** not. ^Changes to a view that are intercepted by INSTEAD OF triggers
+** are not counted.
+**
** See also the [sqlite3_changes()] interface, the
** [count_changes pragma], and the [total_changes() SQL function].
**
@@ -2029,6 +2064,7 @@ SQLITE_API int sqlite3_complete16(const void *sql);
/*
** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors
+** KEYWORDS: {busy-handler callback} {busy handler}
**
** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X
** that might be invoked with argument P whenever
@@ -2045,7 +2081,7 @@ SQLITE_API int sqlite3_complete16(const void *sql);
** ^The first argument to the busy handler is a copy of the void* pointer which
** is the third argument to sqlite3_busy_handler(). ^The second argument to
** the busy handler callback is the number of times that the busy handler has
-** been invoked for the same locking event. ^If the
+** been invoked previously for the same locking event. ^If the
** busy callback returns 0, then no additional attempts are made to
** access the database and [SQLITE_BUSY] is returned
** to the application.
@@ -2420,13 +2456,14 @@ SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
** applications to access the same PRNG for other purposes.
**
** ^A call to this routine stores N bytes of randomness into buffer P.
-** ^If N is less than one, then P can be a NULL pointer.
+** ^The P parameter can be a NULL pointer.
**
** ^If this routine has not been previously called or if the previous
-** call had N less than one, then the PRNG is seeded using randomness
-** obtained from the xRandomness method of the default [sqlite3_vfs] object.
-** ^If the previous call to this routine had an N of 1 or more then
-** the pseudo-randomness is generated
+** call had N less than one or a NULL pointer for P, then the PRNG is
+** seeded using randomness obtained from the xRandomness method of
+** the default [sqlite3_vfs] object.
+** ^If the previous call to this routine had an N of 1 or more and a
+** non-NULL P then the pseudo-randomness is generated
** internally and without recourse to the [sqlite3_vfs] xRandomness
** method.
*/
@@ -4148,9 +4185,9 @@ SQLITE_API int sqlite3_create_function_v2(
** These constant define integer codes that represent the various
** text encodings supported by SQLite.
*/
-#define SQLITE_UTF8 1
-#define SQLITE_UTF16LE 2
-#define SQLITE_UTF16BE 3
+#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */
+#define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */
+#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */
#define SQLITE_UTF16 4 /* Use native byte order */
#define SQLITE_ANY 5 /* Deprecated */
#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */
@@ -4499,7 +4536,8 @@ typedef void (*sqlite3_destructor_type)(void*);
** the [sqlite3_context] pointer, the results are undefined.
*/
SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*));
-SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*,sqlite3_uint64,void(*)(void*));
+SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*,
+ sqlite3_uint64,void(*)(void*));
SQLITE_API void sqlite3_result_double(sqlite3_context*, double);
SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int);
SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int);
@@ -5131,20 +5169,27 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
/*
** CAPI3REF: Extract Metadata About A Column Of A Table
**
-** ^This routine returns metadata about a specific column of a specific
-** database table accessible using the [database connection] handle
-** passed as the first function argument.
+** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns
+** information about column C of table T in database D
+** on [database connection] X.)^ ^The sqlite3_table_column_metadata()
+** interface returns SQLITE_OK and fills in the non-NULL pointers in
+** the final five arguments with appropriate values if the specified
+** column exists. ^The sqlite3_table_column_metadata() interface returns
+** SQLITE_ERROR and if the specified column does not exist.
+** ^If the column-name parameter to sqlite3_table_column_metadata() is a
+** NULL pointer, then this routine simply checks for the existance of the
+** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it
+** does not.
**
** ^The column is identified by the second, third and fourth parameters to
-** this function. ^The second parameter is either the name of the database
+** this function. ^(The second parameter is either the name of the database
** (i.e. "main", "temp", or an attached database) containing the specified
-** table or NULL. ^If it is NULL, then all attached databases are searched
+** table or NULL.)^ ^If it is NULL, then all attached databases are searched
** for the table using the same algorithm used by the database engine to
** resolve unqualified table references.
**
** ^The third and fourth parameters to this function are the table and column
-** name of the desired column, respectively. Neither of these parameters
-** may be NULL.
+** name of the desired column, respectively.
**
** ^Metadata is returned by writing to the memory locations passed as the 5th
** and subsequent parameters to this function. ^Any of these arguments may be
@@ -5163,16 +5208,17 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
** )^
**
** ^The memory pointed to by the character pointers returned for the
-** declaration type and collation sequence is valid only until the next
+** declaration type and collation sequence is valid until the next
** call to any SQLite API function.
**
** ^If the specified table is actually a view, an [error code] is returned.
**
-** ^If the specified column is "rowid", "oid" or "_rowid_" and an
+** ^If the specified column is "rowid", "oid" or "_rowid_" and the table
+** is not a [WITHOUT ROWID] table and an
** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output
** parameters are set for the explicitly declared column. ^(If there is no
-** explicitly declared [INTEGER PRIMARY KEY] column, then the output
-** parameters are set as follows:
+** [INTEGER PRIMARY KEY] column, then the outputs
+** for the [rowid] are set as follows:
**
**
** data type: "INTEGER"
@@ -5182,13 +5228,9 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
** auto increment: 0
**
)^
**
-** ^(This function may load one or more schemas from database files. If an
-** error occurs during this process, or if the requested table or column
-** cannot be found, an [error code] is returned and an error message left
-** in the [database connection] (to be retrieved using sqlite3_errmsg()).)^
-**
-** ^This API is only available if the library was compiled with the
-** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined.
+** ^This function causes all database schemas to be read from disk and
+** parsed, if that has not already been done, and returns an error if
+** any errors are encountered while loading the schema.
*/
SQLITE_API int sqlite3_table_column_metadata(
sqlite3 *db, /* Connection handle */
@@ -5641,26 +5683,42 @@ typedef struct sqlite3_blob sqlite3_blob;
** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow;
** )^
**
+** ^(Parameter zDb is not the filename that contains the database, but
+** rather the symbolic name of the database. For attached databases, this is
+** the name that appears after the AS keyword in the [ATTACH] statement.
+** For the main database file, the database name is "main". For TEMP
+** tables, the database name is "temp".)^
+**
** ^If the flags parameter is non-zero, then the BLOB is opened for read
-** and write access. ^If it is zero, the BLOB is opened for read access.
-** ^It is not possible to open a column that is part of an index or primary
-** key for writing. ^If [foreign key constraints] are enabled, it is
-** not possible to open a column that is part of a [child key] for writing.
+** and write access. ^If the flags parameter is zero, the BLOB is opened for
+** read-only access.
**
-** ^Note that the database name is not the filename that contains
-** the database but rather the symbolic name of the database that
-** appears after the AS keyword when the database is connected using [ATTACH].
-** ^For the main database file, the database name is "main".
-** ^For TEMP tables, the database name is "temp".
+** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored
+** in *ppBlob. Otherwise an [error code] is returned and, unless the error
+** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
+** the API is not misused, it is always safe to call [sqlite3_blob_close()]
+** on *ppBlob after this function it returns.
+**
+** This function fails with SQLITE_ERROR if any of the following are true:
+**
+**
^(Database zDb does not exist)^,
+**
^(Table zTable does not exist within database zDb)^,
+**
^(Table zTable is a WITHOUT ROWID table)^,
+**
^(Column zColumn does not exist)^,
+**
^(Row iRow is not present in the table)^,
+**
^(The specified column of row iRow contains a value that is not
+** a TEXT or BLOB value)^,
+**
^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE
+** constraint and the blob is being opened for read/write access)^,
+**
^([foreign key constraints | Foreign key constraints] are enabled,
+** column zColumn is part of a [child key] definition and the blob is
+** being opened for read/write access)^.
+**
+**
+** ^Unless it returns SQLITE_MISUSE, this function sets the
+** [database connection] error code and message accessible via
+** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions.
**
-** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is written
-** to *ppBlob. Otherwise an [error code] is returned and *ppBlob is set
-** to be a null pointer.)^
-** ^This function sets the [database connection] error code and message
-** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()] and related
-** functions. ^Note that the *ppBlob variable is always initialized in a
-** way that makes it safe to invoke [sqlite3_blob_close()] on *ppBlob
-** regardless of the success or failure of this routine.
**
** ^(If the row that a BLOB handle points to is modified by an
** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects
@@ -5678,13 +5736,9 @@ typedef struct sqlite3_blob sqlite3_blob;
** interface. Use the [UPDATE] SQL command to change the size of a
** blob.
**
-** ^The [sqlite3_blob_open()] interface will fail for a [WITHOUT ROWID]
-** table. Incremental BLOB I/O is not possible on [WITHOUT ROWID] tables.
-**
** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces
-** and the built-in [zeroblob] SQL function can be used, if desired,
-** to create an empty, zero-filled blob in which to read or write using
-** this interface.
+** and the built-in [zeroblob] SQL function may be used to create a
+** zero-filled blob to read or write using the incremental-blob interface.
**
** To avoid a resource leak, every open [BLOB handle] should eventually
** be released by a call to [sqlite3_blob_close()].
@@ -5726,24 +5780,22 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_i
/*
** CAPI3REF: Close A BLOB Handle
**
-** ^Closes an open [BLOB handle].
+** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed
+** unconditionally. Even if this routine returns an error code, the
+** handle is still closed.)^
**
-** ^Closing a BLOB shall cause the current transaction to commit
-** if there are no other BLOBs, no pending prepared statements, and the
-** database connection is in [autocommit mode].
-** ^If any writes were made to the BLOB, they might be held in cache
-** until the close operation if they will fit.
+** ^If the blob handle being closed was opened for read-write access, and if
+** the database is in auto-commit mode and there are no other open read-write
+** blob handles or active write statements, the current transaction is
+** committed. ^If an error occurs while committing the transaction, an error
+** code is returned and the transaction rolled back.
**
-** ^(Closing the BLOB often forces the changes
-** out to disk and so if any I/O errors occur, they will likely occur
-** at the time when the BLOB is closed. Any errors that occur during
-** closing are reported as a non-zero return value.)^
-**
-** ^(The BLOB is closed unconditionally. Even if this routine returns
-** an error code, the BLOB is still closed.)^
-**
-** ^Calling this routine with a null pointer (such as would be returned
-** by a failed call to [sqlite3_blob_open()]) is a harmless no-op.
+** Calling this function with an argument that is not a NULL pointer or an
+** open blob handle results in undefined behaviour. ^Calling this routine
+** with a null pointer (such as would be returned by a failed call to
+** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function
+** is passed a valid open blob handle, the values returned by the
+** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning.
*/
SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
@@ -5793,21 +5845,27 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
/*
** CAPI3REF: Write Data Into A BLOB Incrementally
**
-** ^This function is used to write data into an open [BLOB handle] from a
-** caller-supplied buffer. ^N bytes of data are copied from the buffer Z
-** into the open BLOB, starting at offset iOffset.
+** ^(This function is used to write data into an open [BLOB handle] from a
+** caller-supplied buffer. N bytes of data are copied from the buffer Z
+** into the open BLOB, starting at offset iOffset.)^
+**
+** ^(On success, sqlite3_blob_write() returns SQLITE_OK.
+** Otherwise, an [error code] or an [extended error code] is returned.)^
+** ^Unless SQLITE_MISUSE is returned, this function sets the
+** [database connection] error code and message accessible via
+** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions.
**
** ^If the [BLOB handle] passed as the first argument was not opened for
** writing (the flags parameter to [sqlite3_blob_open()] was zero),
** this function returns [SQLITE_READONLY].
**
-** ^This function may only modify the contents of the BLOB; it is
+** This function may only modify the contents of the BLOB; it is
** not possible to increase the size of a BLOB using this API.
** ^If offset iOffset is less than N bytes from the end of the BLOB,
-** [SQLITE_ERROR] is returned and no data is written. ^If N is
-** less than zero [SQLITE_ERROR] is returned and no data is written.
-** The size of the BLOB (and hence the maximum value of N+iOffset)
-** can be determined using the [sqlite3_blob_bytes()] interface.
+** [SQLITE_ERROR] is returned and no data is written. The size of the
+** BLOB (and hence the maximum value of N+iOffset) can be determined
+** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less
+** than zero [SQLITE_ERROR] is returned and no data is written.
**
** ^An attempt to write to an expired [BLOB handle] fails with an
** error code of [SQLITE_ABORT]. ^Writes to the BLOB that occurred
@@ -5816,9 +5874,6 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
** have been overwritten by the statement that expired the BLOB handle
** or by other independent statements.
**
-** ^(On success, sqlite3_blob_write() returns SQLITE_OK.
-** Otherwise, an [error code] or an [extended error code] is returned.)^
-**
** This routine only works on a [BLOB handle] which has been created
** by a prior successful call to [sqlite3_blob_open()] and which has not
** been closed by [sqlite3_blob_close()]. Passing any other pointer in
@@ -5871,34 +5926,34 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
**
** The SQLite source code contains multiple implementations
** of these mutex routines. An appropriate implementation
-** is selected automatically at compile-time. ^(The following
+** is selected automatically at compile-time. The following
** implementations are available in the SQLite core:
**
**
**
SQLITE_MUTEX_PTHREADS
**
SQLITE_MUTEX_W32
**
SQLITE_MUTEX_NOOP
-**
)^
+**
**
-** ^The SQLITE_MUTEX_NOOP implementation is a set of routines
+** The SQLITE_MUTEX_NOOP implementation is a set of routines
** that does no real locking and is appropriate for use in
-** a single-threaded application. ^The SQLITE_MUTEX_PTHREADS and
+** a single-threaded application. The SQLITE_MUTEX_PTHREADS and
** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix
** and Windows.
**
-** ^(If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor
+** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor
** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex
** implementation is included with the library. In this case the
** application must supply a custom mutex implementation using the
** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function
** before calling sqlite3_initialize() or any other public sqlite3_
-** function that calls sqlite3_initialize().)^
+** function that calls sqlite3_initialize().
**
** ^The sqlite3_mutex_alloc() routine allocates a new
-** mutex and returns a pointer to it. ^If it returns NULL
-** that means that a mutex could not be allocated. ^SQLite
-** will unwind its stack and return an error. ^(The argument
-** to sqlite3_mutex_alloc() is one of these integer constants:
+** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
+** routine returns NULL if it is unable to allocate the requested
+** mutex. The argument to sqlite3_mutex_alloc() must one of these
+** integer constants:
**
**
**
SQLITE_MUTEX_FAST
@@ -5911,7 +5966,8 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
**
SQLITE_MUTEX_STATIC_PMEM
**
SQLITE_MUTEX_STATIC_APP1
**
SQLITE_MUTEX_STATIC_APP2
-**
)^
+**
SQLITE_MUTEX_STATIC_APP3
+**
**
** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE)
** cause sqlite3_mutex_alloc() to create
@@ -5919,14 +5975,14 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** is used but not necessarily so when SQLITE_MUTEX_FAST is used.
** The mutex implementation does not need to make a distinction
** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does
-** not want to. ^SQLite will only request a recursive mutex in
-** cases where it really needs one. ^If a faster non-recursive mutex
+** not want to. SQLite will only request a recursive mutex in
+** cases where it really needs one. If a faster non-recursive mutex
** implementation is available on the host platform, the mutex subsystem
** might return such a mutex in response to SQLITE_MUTEX_FAST.
**
** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other
** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return
-** a pointer to a static preexisting mutex. ^Six static mutexes are
+** a pointer to a static preexisting mutex. ^Nine static mutexes are
** used by the current version of SQLite. Future versions of SQLite
** may add additional static mutexes. Static mutexes are for internal
** use by SQLite only. Applications that use SQLite mutexes should
@@ -5935,16 +5991,13 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
**
** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
-** returns a different mutex on every call. ^But for the static
+** returns a different mutex on every call. ^For the static
** mutex types, the same mutex is returned on every call that has
** the same type number.
**
** ^The sqlite3_mutex_free() routine deallocates a previously
-** allocated dynamic mutex. ^SQLite is careful to deallocate every
-** dynamic mutex that it allocates. The dynamic mutexes must not be in
-** use when they are deallocated. Attempting to deallocate a static
-** mutex results in undefined behavior. ^SQLite never deallocates
-** a static mutex.
+** allocated dynamic mutex. Attempting to deallocate a static
+** mutex results in undefined behavior.
**
** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
** to enter a mutex. ^If another thread is already within the mutex,
@@ -5952,23 +6005,21 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK]
** upon successful entry. ^(Mutexes created using
** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread.
-** In such cases the,
+** In such cases, the
** mutex must be exited an equal number of times before another thread
-** can enter.)^ ^(If the same thread tries to enter any other
-** kind of mutex more than once, the behavior is undefined.
-** SQLite will never exhibit
-** such behavior in its own use of mutexes.)^
+** can enter.)^ If the same thread tries to enter any mutex other
+** than an SQLITE_MUTEX_RECURSIVE more than once, the behavior is undefined.
**
** ^(Some systems (for example, Windows 95) do not support the operation
** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try()
-** will always return SQLITE_BUSY. The SQLite core only ever uses
-** sqlite3_mutex_try() as an optimization so this is acceptable behavior.)^
+** will always return SQLITE_BUSY. The SQLite core only ever uses
+** sqlite3_mutex_try() as an optimization so this is acceptable
+** behavior.)^
**
** ^The sqlite3_mutex_leave() routine exits a mutex that was
-** previously entered by the same thread. ^(The behavior
+** previously entered by the same thread. The behavior
** is undefined if the mutex is not currently entered by the
-** calling thread or is not currently allocated. SQLite will
-** never do either.)^
+** calling thread or is not currently allocated.
**
** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or
** sqlite3_mutex_leave() is a NULL pointer, then all three routines
@@ -5989,9 +6040,9 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
** used to allocate and use mutexes.
**
** Usually, the default mutex implementations provided by SQLite are
-** sufficient, however the user has the option of substituting a custom
+** sufficient, however the application has the option of substituting a custom
** implementation for specialized deployments or systems for which SQLite
-** does not provide a suitable implementation. In this case, the user
+** does not provide a suitable implementation. In this case, the application
** creates and populates an instance of this structure to pass
** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option.
** Additionally, an instance of this structure can be used as an
@@ -6032,13 +6083,13 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
** (i.e. it is acceptable to provide an implementation that segfaults if
** it is passed a NULL pointer).
**
-** The xMutexInit() method must be threadsafe. ^It must be harmless to
+** The xMutexInit() method must be threadsafe. It must be harmless to
** invoke xMutexInit() multiple times within the same process and without
** intervening calls to xMutexEnd(). Second and subsequent calls to
** xMutexInit() must be no-ops.
**
-** ^xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()]
-** and its associates). ^Similarly, xMutexAlloc() must not use SQLite memory
+** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()]
+** and its associates). Similarly, xMutexAlloc() must not use SQLite memory
** allocation for a static mutex. ^However xMutexAlloc() may use SQLite
** memory allocation for a fast or recursive mutex.
**
@@ -6064,29 +6115,29 @@ struct sqlite3_mutex_methods {
** CAPI3REF: Mutex Verification Routines
**
** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines
-** are intended for use inside assert() statements. ^The SQLite core
+** are intended for use inside assert() statements. The SQLite core
** never uses these routines except inside an assert() and applications
-** are advised to follow the lead of the core. ^The SQLite core only
+** are advised to follow the lead of the core. The SQLite core only
** provides implementations for these routines when it is compiled
-** with the SQLITE_DEBUG flag. ^External mutex implementations
+** with the SQLITE_DEBUG flag. External mutex implementations
** are only required to provide these routines if SQLITE_DEBUG is
** defined and if NDEBUG is not defined.
**
-** ^These routines should return true if the mutex in their argument
+** These routines should return true if the mutex in their argument
** is held or not held, respectively, by the calling thread.
**
-** ^The implementation is not required to provide versions of these
+** The implementation is not required to provide versions of these
** routines that actually work. If the implementation does not provide working
** versions of these routines, it should at least provide stubs that always
** return true so that one does not get spurious assertion failures.
**
-** ^If the argument to sqlite3_mutex_held() is a NULL pointer then
+** If the argument to sqlite3_mutex_held() is a NULL pointer then
** the routine should return 1. This seems counter-intuitive since
** clearly the mutex cannot be held if it does not exist. But
** the reason the mutex does not exist is because the build is not
** using mutexes. And we do not want the assert() containing the
** call to sqlite3_mutex_held() to fail, so a non-zero return is
-** the appropriate thing to do. ^The sqlite3_mutex_notheld()
+** the appropriate thing to do. The sqlite3_mutex_notheld()
** interface should also return 1 when given a NULL pointer.
*/
#ifndef NDEBUG
@@ -6819,6 +6870,10 @@ typedef struct sqlite3_backup sqlite3_backup;
** must be different or else sqlite3_backup_init(D,N,S,M) will fail with
** an error.
**
+** ^A call to sqlite3_backup_init() will fail, returning SQLITE_ERROR, if
+** there is already a read or read-write transaction open on the
+** destination database.
+**
** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is
** returned and an error code and error message are stored in the
** destination [database connection] D.
@@ -7142,12 +7197,10 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** CAPI3REF: Write-Ahead Log Commit Hook
**
** ^The [sqlite3_wal_hook()] function is used to register a callback that
-** will be invoked each time a database connection commits data to a
-** [write-ahead log] (i.e. whenever a transaction is committed in
-** [journal_mode | journal_mode=WAL mode]).
+** is invoked each time data is committed to a database in wal mode.
**
-** ^The callback is invoked by SQLite after the commit has taken place and
-** the associated write-lock on the database released, so the implementation
+** ^(The callback is invoked by SQLite after the commit has taken place and
+** the associated write-lock on the database released)^, so the implementation
** may read, write or [checkpoint] the database as required.
**
** ^The first parameter passed to the callback function when it is invoked
@@ -7212,97 +7265,114 @@ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
/*
** CAPI3REF: Checkpoint a database
**
-** ^The [sqlite3_wal_checkpoint(D,X)] interface causes database named X
-** on [database connection] D to be [checkpointed]. ^If X is NULL or an
-** empty string, then a checkpoint is run on all databases of
-** connection D. ^If the database connection D is not in
-** [WAL | write-ahead log mode] then this interface is a harmless no-op.
-** ^The [sqlite3_wal_checkpoint(D,X)] interface initiates a
-** [sqlite3_wal_checkpoint_v2|PASSIVE] checkpoint.
-** Use the [sqlite3_wal_checkpoint_v2()] interface to get a FULL
-** or RESET checkpoint.
+** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to
+** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^
**
-** ^The [wal_checkpoint pragma] can be used to invoke this interface
-** from SQL. ^The [sqlite3_wal_autocheckpoint()] interface and the
-** [wal_autocheckpoint pragma] can be used to cause this interface to be
-** run whenever the WAL reaches a certain size threshold.
+** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the
+** [write-ahead log] for database X on [database connection] D to be
+** transferred into the database file and for the write-ahead log to
+** be reset. See the [checkpointing] documentation for addition
+** information.
**
-** See also: [sqlite3_wal_checkpoint_v2()]
+** This interface used to be the only way to cause a checkpoint to
+** occur. But then the newer and more powerful [sqlite3_wal_checkpoint_v2()]
+** interface was added. This interface is retained for backwards
+** compatibility and as a convenience for applications that need to manually
+** start a callback but which do not need the full power (and corresponding
+** complication) of [sqlite3_wal_checkpoint_v2()].
*/
SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
/*
** CAPI3REF: Checkpoint a database
**
-** Run a checkpoint operation on WAL database zDb attached to database
-** handle db. The specific operation is determined by the value of the
-** eMode parameter:
+** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint
+** operation on database X of [database connection] D in mode M. Status
+** information is written back into integers pointed to by L and C.)^
+** ^(The M parameter must be a valid [checkpoint mode]:)^
**
**
**
SQLITE_CHECKPOINT_PASSIVE
-** Checkpoint as many frames as possible without waiting for any database
-** readers or writers to finish. Sync the db file if all frames in the log
-** are checkpointed. This mode is the same as calling
-** sqlite3_wal_checkpoint(). The [sqlite3_busy_handler|busy-handler callback]
-** is never invoked.
+** ^Checkpoint as many frames as possible without waiting for any database
+** readers or writers to finish, then sync the database file if all frames
+** in the log were checkpointed. ^The [busy-handler callback]
+** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode.
+** ^On the other hand, passive mode might leave the checkpoint unfinished
+** if there are concurrent readers or writers.
**
**
SQLITE_CHECKPOINT_FULL
-** This mode blocks (it invokes the
+** ^This mode blocks (it invokes the
** [sqlite3_busy_handler|busy-handler callback]) until there is no
** database writer and all readers are reading from the most recent database
-** snapshot. It then checkpoints all frames in the log file and syncs the
-** database file. This call blocks database writers while it is running,
-** but not database readers.
+** snapshot. ^It then checkpoints all frames in the log file and syncs the
+** database file. ^This mode blocks new database writers while it is pending,
+** but new database readers are allowed to continue unimpeded.
**
**
SQLITE_CHECKPOINT_RESTART
-** This mode works the same way as SQLITE_CHECKPOINT_FULL, except after
-** checkpointing the log file it blocks (calls the
-** [sqlite3_busy_handler|busy-handler callback])
-** until all readers are reading from the database file only. This ensures
-** that the next client to write to the database file restarts the log file
-** from the beginning. This call blocks database writers while it is running,
-** but not database readers.
+** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition
+** that after checkpointing the log file it blocks (calls the
+** [busy-handler callback])
+** until all readers are reading from the database file only. ^This ensures
+** that the next writer will restart the log file from the beginning.
+** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new
+** database writer attempts while it is pending, but does not impede readers.
+**
+**
SQLITE_CHECKPOINT_TRUNCATE
+** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the
+** addition that it also truncates the log file to zero bytes just prior
+** to a successful return.
**
**
-** If pnLog is not NULL, then *pnLog is set to the total number of frames in
-** the log file before returning. If pnCkpt is not NULL, then *pnCkpt is set to
-** the total number of checkpointed frames (including any that were already
-** checkpointed when this function is called). *pnLog and *pnCkpt may be
-** populated even if sqlite3_wal_checkpoint_v2() returns other than SQLITE_OK.
-** If no values are available because of an error, they are both set to -1
-** before returning to communicate this to the caller.
+** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in
+** the log file or to -1 if the checkpoint could not run because
+** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not
+** NULL,then *pnCkpt is set to the total number of checkpointed frames in the
+** log file (including any that were already checkpointed before the function
+** was called) or to -1 if the checkpoint could not run due to an error or
+** because the database is not in WAL mode. ^Note that upon successful
+** completion of an SQLITE_CHECKPOINT_TRUNCATE, the log file will have been
+** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero.
**
-** All calls obtain an exclusive "checkpoint" lock on the database file. If
+** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If
** any other process is running a checkpoint operation at the same time, the
-** lock cannot be obtained and SQLITE_BUSY is returned. Even if there is a
+** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a
** busy-handler configured, it will not be invoked in this case.
**
-** The SQLITE_CHECKPOINT_FULL and RESTART modes also obtain the exclusive
-** "writer" lock on the database file. If the writer lock cannot be obtained
-** immediately, and a busy-handler is configured, it is invoked and the writer
-** lock retried until either the busy-handler returns 0 or the lock is
-** successfully obtained. The busy-handler is also invoked while waiting for
-** database readers as described above. If the busy-handler returns 0 before
+** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the
+** exclusive "writer" lock on the database file. ^If the writer lock cannot be
+** obtained immediately, and a busy-handler is configured, it is invoked and
+** the writer lock retried until either the busy-handler returns 0 or the lock
+** is successfully obtained. ^The busy-handler is also invoked while waiting for
+** database readers as described above. ^If the busy-handler returns 0 before
** the writer lock is obtained or while waiting for database readers, the
** checkpoint operation proceeds from that point in the same way as
** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible
-** without blocking any further. SQLITE_BUSY is returned in this case.
+** without blocking any further. ^SQLITE_BUSY is returned in this case.
**
-** If parameter zDb is NULL or points to a zero length string, then the
-** specified operation is attempted on all WAL databases. In this case the
-** values written to output parameters *pnLog and *pnCkpt are undefined. If
+** ^If parameter zDb is NULL or points to a zero length string, then the
+** specified operation is attempted on all WAL databases [attached] to
+** [database connection] db. In this case the
+** values written to output parameters *pnLog and *pnCkpt are undefined. ^If
** an SQLITE_BUSY error is encountered when processing one or more of the
** attached WAL databases, the operation is still attempted on any remaining
-** attached databases and SQLITE_BUSY is returned to the caller. If any other
+** attached databases and SQLITE_BUSY is returned at the end. ^If any other
** error occurs while processing an attached database, processing is abandoned
-** and the error code returned to the caller immediately. If no error
+** and the error code is returned to the caller immediately. ^If no error
** (SQLITE_BUSY or otherwise) is encountered while processing the attached
** databases, SQLITE_OK is returned.
**
-** If database zDb is the name of an attached database that is not in WAL
-** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. If
+** ^If database zDb is the name of an attached database that is not in WAL
+** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. ^If
** zDb is not NULL (or a zero length string) and is not the name of any
** attached database, SQLITE_ERROR is returned to the caller.
+**
+** ^Unless it returns SQLITE_MISUSE,
+** the sqlite3_wal_checkpoint_v2() interface
+** sets the error information that is queried by
+** [sqlite3_errcode()] and [sqlite3_errmsg()].
+**
+** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface
+** from SQL.
*/
SQLITE_API int sqlite3_wal_checkpoint_v2(
sqlite3 *db, /* Database handle */
@@ -7313,16 +7383,18 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
);
/*
-** CAPI3REF: Checkpoint operation parameters
+** CAPI3REF: Checkpoint Mode Values
+** KEYWORDS: {checkpoint mode}
**
-** These constants can be used as the 3rd parameter to
-** [sqlite3_wal_checkpoint_v2()]. See the [sqlite3_wal_checkpoint_v2()]
-** documentation for additional information about the meaning and use of
-** each of these values.
+** These constants define all valid values for the "checkpoint mode" passed
+** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface.
+** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the
+** meaning of each of these checkpoint modes.
*/
-#define SQLITE_CHECKPOINT_PASSIVE 0
-#define SQLITE_CHECKPOINT_FULL 1
-#define SQLITE_CHECKPOINT_RESTART 2
+#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
+#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */
+#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */
+#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */
/*
** CAPI3REF: Virtual Table Interface Configuration
@@ -7411,6 +7483,106 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
/* #define SQLITE_ABORT 4 // Also an error code */
#define SQLITE_REPLACE 5
+/*
+** CAPI3REF: Prepared Statement Scan Status Opcodes
+** KEYWORDS: {scanstatus options}
+**
+** The following constants can be used for the T parameter to the
+** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a
+** different metric for sqlite3_stmt_scanstatus() to return.
+**
+** When the value returned to V is a string, space to hold that string is
+** managed by the prepared statement S and will be automatically freed when
+** S is finalized.
+**
+**
+** [[SQLITE_SCANSTAT_NLOOP]]
SQLITE_SCANSTAT_NLOOP
+**
^The [sqlite3_int64] variable pointed to by the T parameter will be
+** set to the total number of times that the X-th loop has run.
+**
+** [[SQLITE_SCANSTAT_NVISIT]]
SQLITE_SCANSTAT_NVISIT
+**
^The [sqlite3_int64] variable pointed to by the T parameter will be set
+** to the total number of rows examined by all iterations of the X-th loop.
+**
+** [[SQLITE_SCANSTAT_EST]]
SQLITE_SCANSTAT_EST
+**
^The "double" variable pointed to by the T parameter will be set to the
+** query planner's estimate for the average number of rows output from each
+** iteration of the X-th loop. If the query planner's estimates was accurate,
+** then this value will approximate the quotient NVISIT/NLOOP and the
+** product of this value for all prior loops with the same SELECTID will
+** be the NLOOP value for the current loop.
+**
+** [[SQLITE_SCANSTAT_NAME]]
SQLITE_SCANSTAT_NAME
+**
^The "const char *" variable pointed to by the T parameter will be set
+** to a zero-terminated UTF-8 string containing the name of the index or table
+** used for the X-th loop.
+**
+** [[SQLITE_SCANSTAT_EXPLAIN]]
SQLITE_SCANSTAT_EXPLAIN
+**
^The "const char *" variable pointed to by the T parameter will be set
+** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
+** description for the X-th loop.
+**
+** [[SQLITE_SCANSTAT_SELECTID]]
SQLITE_SCANSTAT_SELECT
+**
^The "int" variable pointed to by the T parameter will be set to the
+** "select-id" for the X-th loop. The select-id identifies which query or
+** subquery the loop is part of. The main query has a select-id of zero.
+** The select-id is the same value as is output in the first column
+** of an [EXPLAIN QUERY PLAN] query.
+**
+*/
+#define SQLITE_SCANSTAT_NLOOP 0
+#define SQLITE_SCANSTAT_NVISIT 1
+#define SQLITE_SCANSTAT_EST 2
+#define SQLITE_SCANSTAT_NAME 3
+#define SQLITE_SCANSTAT_EXPLAIN 4
+#define SQLITE_SCANSTAT_SELECTID 5
+
+/*
+** CAPI3REF: Prepared Statement Scan Status
+**
+** This interface returns information about the predicted and measured
+** performance for pStmt. Advanced applications can use this
+** interface to compare the predicted and the measured performance and
+** issue warnings and/or rerun [ANALYZE] if discrepancies are found.
+**
+** Since this interface is expected to be rarely used, it is only
+** available if SQLite is compiled using the [SQLITE_ENABLE_STMT_SCANSTATUS]
+** compile-time option.
+**
+** The "iScanStatusOp" parameter determines which status information to return.
+** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior
+** of this interface is undefined.
+** ^The requested measurement is written into a variable pointed to by
+** the "pOut" parameter.
+** Parameter "idx" identifies the specific loop to retrieve statistics for.
+** Loops are numbered starting from zero. ^If idx is out of range - less than
+** zero or greater than or equal to the total number of loops used to implement
+** the statement - a non-zero value is returned and the variable that pOut
+** points to is unchanged.
+**
+** ^Statistics might not be available for all loops in all statements. ^In cases
+** where there exist loops with no available statistics, this function behaves
+** as if the loop did not exist - it returns non-zero and leave the variable
+** that pOut points to unchanged.
+**
+** See also: [sqlite3_stmt_scanstatus_reset()]
+*/
+SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_stmt_scanstatus(
+ sqlite3_stmt *pStmt, /* Prepared statement for which info desired */
+ int idx, /* Index of loop to report on */
+ int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */
+ void *pOut /* Result written here */
+);
+
+/*
+** CAPI3REF: Zero Scan-Status Counters
+**
+** ^Zero all [sqlite3_stmt_scanstatus()] related event counters.
+**
+** This API is only available if the library is built with pre-processor
+** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined.
+*/
+SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
/*
diff --git a/TMessagesProj/libs/armeabi-v7a/libtmessages.5.so b/TMessagesProj/libs/armeabi-v7a/libtmessages.5.so
index 46e200f5..132154ab 100755
Binary files a/TMessagesProj/libs/armeabi-v7a/libtmessages.5.so and b/TMessagesProj/libs/armeabi-v7a/libtmessages.5.so differ
diff --git a/TMessagesProj/libs/armeabi/libtmessages.5.so b/TMessagesProj/libs/armeabi/libtmessages.5.so
index 397e267c..3e08cd8f 100755
Binary files a/TMessagesProj/libs/armeabi/libtmessages.5.so and b/TMessagesProj/libs/armeabi/libtmessages.5.so differ
diff --git a/TMessagesProj/libs/x86/libtmessages.5.so b/TMessagesProj/libs/x86/libtmessages.5.so
index ead3c521..326a064f 100755
Binary files a/TMessagesProj/libs/x86/libtmessages.5.so and b/TMessagesProj/libs/x86/libtmessages.5.so differ
diff --git a/TMessagesProj/src/main/AndroidManifest.xml b/TMessagesProj/src/main/AndroidManifest.xml
index a85bdd03..5ca90c91 100644
--- a/TMessagesProj/src/main/AndroidManifest.xml
+++ b/TMessagesProj/src/main/AndroidManifest.xml
@@ -149,6 +149,7 @@
+
diff --git a/TMessagesProj/src/main/java/org/telegram/android/ImageLoader.java b/TMessagesProj/src/main/java/org/telegram/android/ImageLoader.java
index 555806d1..1e9a454a 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/ImageLoader.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/ImageLoader.java
@@ -18,6 +18,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.media.ExifInterface;
+import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@@ -56,12 +57,19 @@ public class ImageLoader {
private HashMap bitmapUseCounts = new HashMap<>();
private LruCache memCache;
- private ConcurrentHashMap imageLoadingByUrl = new ConcurrentHashMap<>();
- private ConcurrentHashMap imageLoadingByKeys = new ConcurrentHashMap<>();
+ private HashMap imageLoadingByUrl = new HashMap<>();
+ private HashMap imageLoadingByKeys = new HashMap<>();
private HashMap imageLoadingByTag = new HashMap<>();
+ private HashMap waitingForQualityThumb = new HashMap<>();
+ private HashMap waitingForQualityThumbByTag = new HashMap<>();
private LinkedList httpTasks = new LinkedList<>();
private DispatchQueue cacheOutQueue = new DispatchQueue("cacheOutQueue");
+ private DispatchQueue cacheThumbOutQueue = new DispatchQueue("cacheThumbOutQueue");
+ private DispatchQueue thumbGeneratingQueue = new DispatchQueue("thumbGeneratingQueue");
+ private DispatchQueue imageLoadQueue = new DispatchQueue("imageLoadQueue");
+ private DispatchQueue recycleQueue = new DispatchQueue("recycleQueue");
private ConcurrentHashMap fileProgresses = new ConcurrentHashMap<>();
+ private HashMap thumbGenerateTasks = new HashMap<>();
private int currentHttpTasksCount = 0;
private LinkedList httpFileLoadTasks = new LinkedList<>();
@@ -78,6 +86,12 @@ public class ImageLoader {
private File telegramPath = null;
+ private class ThumbGenerateInfo {
+ private int count;
+ private TLRPC.FileLocation fileLocation;
+ private String filter;
+ }
+
private class HttpFileTask extends AsyncTask {
private String url;
@@ -298,7 +312,9 @@ public class ImageLoader {
if (done) {
if (cacheImage.tempFilePath != null) {
- cacheImage.tempFilePath.renameTo(cacheImage.finalFilePath);
+ if (!cacheImage.tempFilePath.renameTo(cacheImage.finalFilePath)) {
+ cacheImage.finalFilePath = cacheImage.tempFilePath;
+ }
}
}
@@ -308,7 +324,7 @@ public class ImageLoader {
@Override
protected void onPostExecute(final Boolean result) {
if (result || !canRetry) {
- fileDidLoaded(cacheImage.url, cacheImage.finalFilePath, cacheImage.tempFilePath);
+ fileDidLoaded(cacheImage.url, cacheImage.finalFilePath, FileLoader.MEDIA_DIR_IMAGE);
} else {
httpFileLoadError(cacheImage.url);
}
@@ -359,15 +375,120 @@ public class ImageLoader {
}
}
+ private class ThumbGenerateTask implements Runnable {
+
+ private File originalPath;
+ private int mediaType;
+ private TLRPC.FileLocation thumbLocation;
+ private String filter;
+
+ public ThumbGenerateTask(int type, File path, TLRPC.FileLocation location, String f) {
+ mediaType = type;
+ originalPath = path;
+ thumbLocation = location;
+ filter = f;
+ }
+
+ private void removeTask() {
+ if (thumbLocation == null) {
+ return;
+ }
+ final String name = FileLoader.getAttachFileName(thumbLocation);
+ imageLoadQueue.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ thumbGenerateTasks.remove(name);
+ }
+ });
+ }
+
+ @Override
+ public void run() {
+ try {
+ if (thumbLocation == null) {
+ removeTask();
+ return;
+ }
+ final String key = thumbLocation.volume_id + "_" + thumbLocation.local_id;
+ File thumbFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "q_" + key + ".jpg");
+ if (thumbFile.exists() || !originalPath.exists()) {
+ removeTask();
+ return;
+ }
+ int size = Math.min(180, Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) / 4);
+ Bitmap originalBitmap = null;
+ if (mediaType == FileLoader.MEDIA_DIR_IMAGE) {
+ originalBitmap = ImageLoader.loadBitmap(originalPath.toString(), null, size, size, false);
+ } else if (mediaType == FileLoader.MEDIA_DIR_VIDEO) {
+ originalBitmap = ThumbnailUtils.createVideoThumbnail(originalPath.toString(), MediaStore.Video.Thumbnails.MINI_KIND);
+ } else if (mediaType == FileLoader.MEDIA_DIR_DOCUMENT) {
+ String path = originalPath.toString().toLowerCase();
+ if (!path.endsWith(".jpg") && !path.endsWith(".jpeg") && !path.endsWith(".png") && !path.endsWith(".gif")) {
+ removeTask();
+ return;
+ }
+ originalBitmap = ImageLoader.loadBitmap(path, null, size, size, false);
+ }
+ if (originalBitmap == null) {
+ removeTask();
+ return;
+ }
+
+ int w = originalBitmap.getWidth();
+ int h = originalBitmap.getHeight();
+ if (w == 0 || h == 0) {
+ removeTask();
+ return;
+ }
+ float scaleFactor = Math.min((float) w / size, (float) h / size);
+ Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, (int) (w / scaleFactor), (int) (h / scaleFactor), true);
+ if (scaledBitmap != originalBitmap) {
+ originalBitmap.recycle();
+ callGC();
+ }
+ originalBitmap = scaledBitmap;
+ FileOutputStream stream = new FileOutputStream(thumbFile);
+ originalBitmap.compress(Bitmap.CompressFormat.JPEG, 60, stream);
+ final BitmapDrawable bitmapDrawable = new BitmapDrawable(originalBitmap);
+ AndroidUtilities.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ removeTask();
+
+ String kf = key;
+ if (filter != null) {
+ kf += "@" + filter;
+ }
+ NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageThumbGenerated, bitmapDrawable, kf);
+ /*BitmapDrawable old = memCache.get(kf);
+ if (old != null) {
+ Bitmap image = old.getBitmap();
+ if (runtimeHack != null) {
+ runtimeHack.trackAlloc(image.getRowBytes() * image.getHeight());
+ }
+ if (!image.isRecycled()) {
+ image.recycle();
+ }
+ }*/
+ memCache.put(kf, bitmapDrawable);
+ }
+ });
+ } catch (Throwable e) {
+ FileLog.e("tmessages", e);
+ removeTask();
+ }
+ }
+ }
+
private class CacheOutTask implements Runnable {
- private Thread runningThread = null;
+ private Thread runningThread;
private final Object sync = new Object();
- private CacheImage cacheImage = null;
- private boolean isCancelled = false;
+ private CacheImage cacheImage;
+ private boolean isCancelled;
- public CacheOutTask(CacheImage cacheImage) {
- this.cacheImage = cacheImage;
+ public CacheOutTask(CacheImage image) {
+ cacheImage = image;
}
@Override
@@ -382,138 +503,185 @@ public class ImageLoader {
Long mediaId = null;
Bitmap image = null;
- File cacheFileFinal = null;
+ File cacheFileFinal = cacheImage.finalFilePath;
boolean canDeleteFile = true;
boolean isWebp = false;
- if (cacheImage.finalFilePath != null && cacheImage.finalFilePath.exists()) {
- cacheFileFinal = cacheImage.finalFilePath;
- } else if (cacheImage.tempFilePath != null && cacheImage.tempFilePath.exists()) {
- cacheFileFinal = cacheImage.tempFilePath;
- } else if (cacheImage.finalFilePath != null) {
- cacheFileFinal = cacheImage.finalFilePath;
- }
-
if (cacheFileFinal.toString().endsWith("webp")) {
isWebp = true;
}
- try {
- if (cacheImage.httpUrl != null) {
- if (cacheImage.httpUrl.startsWith("thumb://")) {
- int idx = cacheImage.httpUrl.indexOf(":", 8);
- if (idx >= 0) {
- mediaId = Long.parseLong(cacheImage.httpUrl.substring(8, idx));
- }
- canDeleteFile = false;
- } else if (!cacheImage.httpUrl.startsWith("http")) {
- canDeleteFile = false;
- }
- }
+ if (cacheImage.thumb) {
- int delay = 20;
- if (runtimeHack != null) {
- delay = 60;
- }
- if (mediaId != null) {
- delay = 0;
- }
- if (delay != 0 && lastCacheOutTime != 0 && lastCacheOutTime > System.currentTimeMillis() - delay) {
- Thread.sleep(delay);
- }
- lastCacheOutTime = System.currentTimeMillis();
- synchronized (sync) {
- if (isCancelled) {
- return;
- }
- }
-
- BitmapFactory.Options opts = new BitmapFactory.Options();
-
- float w_filter = 0;
- float h_filter = 0;
- boolean blur = false;
+ int blurType = 0;
if (cacheImage.filter != null) {
- String args[] = cacheImage.filter.split("_");
- w_filter = Float.parseFloat(args[0]) * AndroidUtilities.density;
- h_filter = Float.parseFloat(args[1]) * AndroidUtilities.density;
- if (args.length > 2) {
- blur = true;
+ if (cacheImage.filter.contains("b2")) {
+ blurType = 3;
+ } else if (cacheImage.filter.contains("b1")) {
+ blurType = 2;
+ } else if (cacheImage.filter.contains("b")) {
+ blurType = 1;
}
- opts.inJustDecodeBounds = true;
+ }
- if (mediaId != null) {
- MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts);
- } else {
- if (cacheImage.finalFilePath != null && cacheImage.finalFilePath.exists()) {
- BitmapFactory.decodeFile(cacheImage.finalFilePath.getAbsolutePath(), opts);
- } else if (cacheImage.tempFilePath != null && cacheImage.tempFilePath.exists()) {
- BitmapFactory.decodeFile(cacheImage.tempFilePath.getAbsolutePath(), opts);
+ try {
+ lastCacheOutTime = System.currentTimeMillis();
+ synchronized (sync) {
+ if (isCancelled) {
+ return;
}
}
- float photoW = opts.outWidth;
- float photoH = opts.outHeight;
- float scaleFactor = Math.max(photoW / w_filter, photoH / h_filter);
- if (scaleFactor < 1) {
- scaleFactor = 1;
+ if (image == null) {
+ if (isWebp) {
+ RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r");
+ ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length());
+ image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
+ file.close();
+ } else {
+ FileInputStream is = new FileInputStream(cacheFileFinal);
+ image = BitmapFactory.decodeStream(is, null, null);
+ is.close();
+ }
}
- opts.inJustDecodeBounds = false;
- opts.inSampleSize = (int)scaleFactor;
- }
- synchronized (sync) {
- if (isCancelled) {
- return;
- }
- }
-
- if (cacheImage.filter == null || blur) {
- opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
- } else {
- opts.inPreferredConfig = Bitmap.Config.RGB_565;
- }
- opts.inDither = false;
- if (mediaId != null) {
- image = MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, null);
- }
- if (image == null) {
- if (isWebp) {
- RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r");
- ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length());
- image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
- file.close();
+ if (image == null) {
+ if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) {
+ cacheFileFinal.delete();
+ }
} else {
- FileInputStream is = new FileInputStream(cacheFileFinal);
- image = BitmapFactory.decodeStream(is, null, opts);
- is.close();
- }
- }
- if (image == null) {
- if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) {
- cacheFileFinal.delete();
- }
- } else {
- if (cacheImage.filter != null) {
- float bitmapW = image.getWidth();
- float bitmapH = image.getHeight();
- if (bitmapW != w_filter && bitmapW > w_filter) {
- float scaleFactor = bitmapW / w_filter;
- Bitmap scaledBitmap = Bitmap.createScaledBitmap(image, (int)w_filter, (int)(bitmapH / scaleFactor), true);
- if (image != scaledBitmap) {
- image.recycle();
- image = scaledBitmap;
+ if (image != null) {
+ if (blurType == 1) {
+ Utilities.blurBitmap(image, 3);
+ } else if (blurType == 2) {
+ Utilities.blurBitmap(image, 1);
+ } else if (blurType == 3) {
+ Utilities.blurBitmap(image, 7);
+ Utilities.blurBitmap(image, 7);
+ Utilities.blurBitmap(image, 7);
}
}
- if (image != null && blur && bitmapH < 100 && bitmapW < 100) {
- Utilities.blurBitmap(image, 3);
+ if (runtimeHack != null) {
+ runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
}
}
- if (runtimeHack != null) {
- runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
- }
+ } catch (Throwable e) {
+ FileLog.e("tmessages", e);
+ }
+ } else {
+ try {
+ if (cacheImage.httpUrl != null) {
+ if (cacheImage.httpUrl.startsWith("thumb://")) {
+ int idx = cacheImage.httpUrl.indexOf(":", 8);
+ if (idx >= 0) {
+ mediaId = Long.parseLong(cacheImage.httpUrl.substring(8, idx));
+ }
+ canDeleteFile = false;
+ } else if (!cacheImage.httpUrl.startsWith("http")) {
+ canDeleteFile = false;
+ }
+ }
+
+ int delay = 20;
+ if (runtimeHack != null) {
+ delay = 60;
+ }
+ if (mediaId != null) {
+ delay = 0;
+ }
+ if (delay != 0 && lastCacheOutTime != 0 && lastCacheOutTime > System.currentTimeMillis() - delay && Build.VERSION.SDK_INT < 21) {
+ Thread.sleep(delay);
+ }
+ lastCacheOutTime = System.currentTimeMillis();
+ synchronized (sync) {
+ if (isCancelled) {
+ return;
+ }
+ }
+
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+
+ float w_filter = 0;
+ float h_filter = 0;
+ boolean blur = false;
+ if (cacheImage.filter != null) {
+ String args[] = cacheImage.filter.split("_");
+ w_filter = Float.parseFloat(args[0]) * AndroidUtilities.density;
+ h_filter = Float.parseFloat(args[1]) * AndroidUtilities.density;
+ if (args.length > 2) {
+ blur = true;
+ }
+ opts.inJustDecodeBounds = true;
+
+ if (mediaId != null) {
+ MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts);
+ } else {
+ BitmapFactory.decodeFile(cacheImage.finalFilePath.getAbsolutePath(), opts);
+ }
+
+ float photoW = opts.outWidth;
+ float photoH = opts.outHeight;
+ float scaleFactor = Math.max(photoW / w_filter, photoH / h_filter);
+ if (scaleFactor < 1) {
+ scaleFactor = 1;
+ }
+ opts.inJustDecodeBounds = false;
+ opts.inSampleSize = (int)scaleFactor;
+ }
+ synchronized (sync) {
+ if (isCancelled) {
+ return;
+ }
+ }
+
+ if (cacheImage.filter == null || blur) {
+ opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ } else {
+ opts.inPreferredConfig = Bitmap.Config.RGB_565;
+ }
+ opts.inDither = false;
+ if (mediaId != null) {
+ image = MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, null);
+ }
+ if (image == null) {
+ if (isWebp) {
+ RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r");
+ ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length());
+ image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
+ file.close();
+ } else {
+ FileInputStream is = new FileInputStream(cacheFileFinal);
+ image = BitmapFactory.decodeStream(is, null, opts);
+ is.close();
+ }
+ }
+ if (image == null) {
+ if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) {
+ cacheFileFinal.delete();
+ }
+ } else {
+ if (cacheImage.filter != null) {
+ float bitmapW = image.getWidth();
+ float bitmapH = image.getHeight();
+ if (bitmapW != w_filter && bitmapW > w_filter) {
+ float scaleFactor = bitmapW / w_filter;
+ Bitmap scaledBitmap = Bitmap.createScaledBitmap(image, (int)w_filter, (int)(bitmapH / scaleFactor), true);
+ if (image != scaledBitmap) {
+ image.recycle();
+ callGC();
+ image = scaledBitmap;
+ }
+ }
+ if (image != null && blur && bitmapH < 100 && bitmapW < 100) {
+ Utilities.blurBitmap(image, 3);
+ }
+ }
+ if (runtimeHack != null) {
+ runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
+ }
+ }
+ } catch (Throwable e) {
+ //don't promt
}
- } catch (Throwable e) {
- //don't promt
}
Thread.interrupted();
onPostExecute(image != null ? new BitmapDrawable(image) : null);
@@ -523,10 +691,28 @@ public class ImageLoader {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
- if (bitmapDrawable != null && memCache.get(cacheImage.key) == null) {
- memCache.put(cacheImage.key, bitmapDrawable);
+ BitmapDrawable toSet = null;
+ if (bitmapDrawable != null) {
+ toSet = memCache.get(cacheImage.key);
+ if (toSet == null) {
+ memCache.put(cacheImage.key, bitmapDrawable);
+ toSet = bitmapDrawable;
+ } else {
+ Bitmap image = bitmapDrawable.getBitmap();
+ if (runtimeHack != null) {
+ runtimeHack.trackAlloc(image.getRowBytes() * image.getHeight());
+ }
+ image.recycle();
+ callGC();
+ }
}
- cacheImage.setImageAndClear(bitmapDrawable);
+ final BitmapDrawable toSetFinal = toSet;
+ imageLoadQueue.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ cacheImage.setImageAndClear(toSetFinal);
+ }
+ });
}
});
}
@@ -593,83 +779,97 @@ public class ImageLoader {
}
private class CacheImage {
- protected String key = null;
- protected String url = null;
- protected String filter = null;
- protected TLObject fileLocation = null;
- protected String httpUrl = null;
- protected File finalFilePath = null;
- protected File tempFilePath = null;
- protected CacheOutTask cacheTask;
- protected HttpImageTask httpTask;
- protected ArrayList imageViewArray = new ArrayList<>();
+ protected String key;
+ protected String url;
+ protected String filter;
+ protected TLObject location;
- public void addImageView(ImageReceiver imageView) {
+ protected File finalFilePath;
+ protected File tempFilePath;
+ protected boolean thumb;
+
+ protected String httpUrl;
+ protected HttpImageTask httpTask;
+ protected CacheOutTask cacheTask;
+
+ protected ArrayList imageReceiverArray = new ArrayList<>();
+
+ public void addImageReceiver(ImageReceiver imageReceiver) {
boolean exist = false;
- for (ImageReceiver v : imageViewArray) {
- if (v == imageView) {
+ for (ImageReceiver v : imageReceiverArray) {
+ if (v == imageReceiver) {
exist = true;
break;
}
}
if (!exist) {
- imageViewArray.add(imageView);
- imageLoadingByTag.put(imageView.getTag(), this);
+ imageReceiverArray.add(imageReceiver);
+ imageLoadingByTag.put(imageReceiver.getTag(thumb), this);
}
}
- public void removeImageView(ImageReceiver imageView) {
- for (int a = 0; a < imageViewArray.size(); a++) {
- ImageReceiver obj = imageViewArray.get(a);
- if (obj == null || obj == imageView) {
- imageViewArray.remove(a);
+ public void removeImageReceiver(ImageReceiver imageReceiver) {
+ for (int a = 0; a < imageReceiverArray.size(); a++) {
+ ImageReceiver obj = imageReceiverArray.get(a);
+ if (obj == null || obj == imageReceiver) {
+ imageReceiverArray.remove(a);
if (obj != null) {
- imageLoadingByTag.remove(obj.getTag());
+ imageLoadingByTag.remove(obj.getTag(thumb));
}
a--;
}
}
-
- if (imageViewArray.size() == 0) {
- cancelAndClear();
+ if (imageReceiverArray.size() == 0) {
+ for (ImageReceiver receiver : imageReceiverArray) {
+ imageLoadingByTag.remove(receiver.getTag(thumb));
+ }
+ imageReceiverArray.clear();
+ if (location != null) {
+ if (location instanceof TLRPC.FileLocation) {
+ FileLoader.getInstance().cancelLoadFile((TLRPC.FileLocation) location);
+ } else if (location instanceof TLRPC.Document) {
+ FileLoader.getInstance().cancelLoadFile((TLRPC.Document) location);
+ }
+ }
+ if (cacheTask != null) {
+ if (thumb) {
+ cacheThumbOutQueue.cancelRunnable(cacheTask);
+ } else {
+ cacheOutQueue.cancelRunnable(cacheTask);
+ }
+ cacheTask.cancel();
+ cacheTask = null;
+ }
+ if (httpTask != null) {
+ httpTasks.remove(httpTask);
+ httpTask.cancel(true);
+ httpTask = null;
+ }
+ if (url != null) {
+ imageLoadingByUrl.remove(url);
+ }
+ if (key != null) {
+ imageLoadingByKeys.remove(key);
+ }
}
}
- public void setImageAndClear(BitmapDrawable image) {
+ public void setImageAndClear(final BitmapDrawable image) {
if (image != null) {
- for (ImageReceiver imgView : imageViewArray) {
- imgView.setImageBitmap(image, key);
- }
+ final ArrayList finalImageReceiverArray = new ArrayList<>(imageReceiverArray);
+ AndroidUtilities.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ for (ImageReceiver imgView : finalImageReceiverArray) {
+ imgView.setImageBitmapByKey(image, key, thumb);
+ }
+ }
+ });
}
- clear();
- }
-
- public void cancelAndClear() {
- if (fileLocation != null) {
- if (fileLocation instanceof TLRPC.FileLocation) {
- FileLoader.getInstance().cancelLoadFile((TLRPC.FileLocation) fileLocation);
- } else if (fileLocation instanceof TLRPC.Document) {
- FileLoader.getInstance().cancelLoadFile((TLRPC.Document) fileLocation);
- }
+ for (ImageReceiver imageReceiver : imageReceiverArray) {
+ imageLoadingByTag.remove(imageReceiver.getTag(thumb));
}
- if (cacheTask != null) {
- cacheOutQueue.cancelRunnable(cacheTask);
- cacheTask.cancel();
- cacheTask = null;
- }
- if (httpTask != null) {
- httpTasks.remove(httpTask);
- httpTask.cancel(true);
- httpTask = null;
- }
- clear();
- }
-
- private void clear() {
- for (ImageReceiver imageReceiver : imageViewArray) {
- imageLoadingByTag.remove(imageReceiver.getTag());
- }
- imageViewArray.clear();
+ imageReceiverArray.clear();
if (url != null) {
imageLoadingByUrl.remove(url);
}
@@ -711,11 +911,11 @@ public class ImageLoader {
}
}
@Override
- protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldBitmap, BitmapDrawable newBitmap) {
+ protected void entryRemoved(boolean evicted, String key, final BitmapDrawable oldBitmap, BitmapDrawable newBitmap) {
if (ignoreRemoval != null && key != null && ignoreRemoval.equals(key)) {
return;
}
- Integer count = bitmapUseCounts.get(key);
+ final Integer count = bitmapUseCounts.get(key);
if (count == null || count == 0) {
Bitmap b = oldBitmap.getBitmap();
if (runtimeHack != null) {
@@ -768,7 +968,7 @@ public class ImageLoader {
}
@Override
- public void fileDidLoaded(final String location, final File finalFile, final File tempFile) {
+ public void fileDidLoaded(final String location, final File finalFile, final int type) {
fileProgresses.remove(location);
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
@@ -780,20 +980,20 @@ public class ImageLoader {
}
}
}
- ImageLoader.this.fileDidLoaded(location, finalFile, tempFile);
+ ImageLoader.this.fileDidLoaded(location, finalFile, type);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidLoaded, location);
}
});
}
@Override
- public void fileDidFailedLoad(final String location, final int state) {
+ public void fileDidFailedLoad(final String location, final int canceled) {
fileProgresses.remove(location);
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
- ImageLoader.this.fileDidFailedLoad(location);
- NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, location, state);
+ ImageLoader.this.fileDidFailedLoad(location, canceled);
+ NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, location, canceled);
}
});
}
@@ -971,6 +1171,17 @@ public class ImageLoader {
}
}
+ public void callGC() {
+ if (Build.VERSION.SDK_INT > 13) {
+ recycleQueue.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ System.gc();
+ }
+ });
+ }
+ }
+
public boolean decrementUseCount(String key) {
Integer count = bitmapUseCounts.get(key);
if (count == null) {
@@ -998,25 +1209,55 @@ public class ImageLoader {
memCache.evictAll();
}
- public void cancelLoadingForImageView(ImageReceiver imageView) {
- if (imageView == null) {
- return;
- }
- Integer TAG = imageView.getTag();
- if (TAG == null) {
- imageView.setTag(TAG = lastImageNum);
- lastImageNum++;
- if (lastImageNum == Integer.MAX_VALUE) {
- lastImageNum = 0;
+ private void removeFromWaitingForThumb(Integer TAG) {
+ String location = waitingForQualityThumbByTag.get(TAG);
+ if (location != null) {
+ ThumbGenerateInfo info = waitingForQualityThumb.get(location);
+ if (info != null) {
+ info.count--;
+ if (info.count == 0) {
+ waitingForQualityThumb.remove(location);
+ }
}
- }
- CacheImage ei = imageLoadingByTag.get(TAG);
- if (ei != null) {
- ei.removeImageView(imageView);
+ waitingForQualityThumbByTag.remove(TAG);
}
}
- public BitmapDrawable getImageFromMemory(TLObject fileLocation, String httpUrl, String filter, ImageReceiver imageReceiver) {
+ public void cancelLoadingForImageReceiver(final ImageReceiver imageReceiver, final int type) {
+ if (imageReceiver == null) {
+ return;
+ }
+ imageLoadQueue.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ int start = 0;
+ int count = 2;
+ if (type == 1) {
+ count = 1;
+ } else if (type == 2) {
+ start = 1;
+ }
+ for (int a = start; a < count; a++) {
+ Integer TAG = imageReceiver.getTag(a == 0);
+ if (a == 0) {
+ removeFromWaitingForThumb(TAG);
+ }
+ if (TAG != null) {
+ CacheImage ei = imageLoadingByTag.get(TAG);
+ if (ei != null) {
+ ei.removeImageReceiver(imageReceiver);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ public BitmapDrawable getImageFromMemory(String key) {
+ return memCache.get(key);
+ }
+
+ public BitmapDrawable getImageFromMemory(TLObject fileLocation, String httpUrl, String filter) {
if (fileLocation == null && httpUrl == null) {
return null;
}
@@ -1035,17 +1276,7 @@ public class ImageLoader {
if (filter != null) {
key += "@" + filter;
}
- BitmapDrawable bitmapDrawable = memCache.get(key);
- if (bitmapDrawable != null && imageReceiver != null) {
- Integer TAG = imageReceiver.getTag();
- if (TAG != null) {
- CacheImage alreadyLoadingImage = imageLoadingByTag.get(TAG);
- if (alreadyLoadingImage != null) {
- alreadyLoadingImage.removeImageView(imageReceiver);
- }
- }
- }
- return bitmapDrawable;
+ return memCache.get(key);
}
public void replaceImageInCache(final String oldKey, final String newKey) {
@@ -1068,177 +1299,331 @@ public class ImageLoader {
memCache.put(key, bitmap);
}
- public void loadImage(final TLObject fileLocation, final String httpUrl, final ImageReceiver imageView, final int size, final boolean cacheOnly) {
- if ((fileLocation == null && httpUrl == null) || imageView == null || (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation) && !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation) && !(fileLocation instanceof TLRPC.TL_document))) {
+ private void generateThumb(int mediaType, File originalPath, TLRPC.FileLocation thumbLocation, String filter) {
+ if (mediaType != FileLoader.MEDIA_DIR_IMAGE && mediaType != FileLoader.MEDIA_DIR_VIDEO && mediaType != FileLoader.MEDIA_DIR_DOCUMENT || originalPath == null || thumbLocation == null) {
return;
}
-
- String url = null;
- String key = null;
- boolean writeToCache = false;
- if (httpUrl != null) {
- key = Utilities.MD5(httpUrl);
- url = key + "." + getHttpUrlExtension(httpUrl);
- } else {
- if (fileLocation instanceof TLRPC.FileLocation) {
- TLRPC.FileLocation location = (TLRPC.FileLocation) fileLocation;
- key = location.volume_id + "_" + location.local_id;
- url = key + "." + (location.ext != null ? location.ext : "jpg");
- if (location.ext != null) {
- writeToCache = true;
- }
- if (!writeToCache) {
- writeToCache = location.key != null || location.volume_id == Integer.MIN_VALUE && location.local_id < 0;
- }
- } else if (fileLocation instanceof TLRPC.Document) {
- TLRPC.Document location = (TLRPC.Document) fileLocation;
- if (location.id == 0 || location.dc_id == 0) {
- return;
- }
- key = location.dc_id + "_" + location.id;
- url = key + ".webp";
- writeToCache = true;
- }
- }
- String filter = imageView.getFilter();
- if (filter != null) {
- key += "@" + filter;
+ String name = FileLoader.getAttachFileName(thumbLocation);
+ ThumbGenerateTask task = thumbGenerateTasks.get(name);
+ if (task == null) {
+ task = new ThumbGenerateTask(mediaType, originalPath, thumbLocation, filter);
+ thumbGeneratingQueue.postRunnable(task);
}
+ }
- Integer TAG = imageView.getTag();
+ private void createLoadOperationForImageReceiver(final ImageReceiver imageReceiver, final String key, final String url, final TLObject imageLocation, final String httpLocation, final String filter, final int size, final boolean cacheOnly, final int thumb) {
+ if (imageReceiver == null || url == null || key == null) {
+ return;
+ }
+ Integer TAG = imageReceiver.getTag(thumb != 0);
if (TAG == null) {
- imageView.setTag(TAG = lastImageNum);
+ imageReceiver.setTag(TAG = lastImageNum, thumb != 0);
lastImageNum++;
if (lastImageNum == Integer.MAX_VALUE) {
lastImageNum = 0;
}
}
- boolean added = false;
- CacheImage alreadyLoadingUrl = imageLoadingByUrl.get(url);
- CacheImage alreadyLoadingCache = imageLoadingByKeys.get(key);
- CacheImage alreadyLoadingImage = imageLoadingByTag.get(TAG);
- if (alreadyLoadingImage != null) {
- if (alreadyLoadingImage == alreadyLoadingUrl || alreadyLoadingImage == alreadyLoadingCache) {
- added = true;
- } else {
- alreadyLoadingImage.removeImageView(imageView);
- }
- }
-
- if (!added && alreadyLoadingCache != null) {
- alreadyLoadingCache.addImageView(imageView);
- added = true;
- }
- if (!added && alreadyLoadingUrl != null) {
- alreadyLoadingUrl.addImageView(imageView);
- added = true;
- }
-
- if (!added) {
- boolean onlyCache = false;
- File cacheFile = null;
- if (cacheOnly || size == 0 || httpUrl != null || fileLocation != null && writeToCache) {
- cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), url);
- } else {
- cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_IMAGE), url);
- }
- if (httpUrl != null) {
- if (!httpUrl.startsWith("http")) {
- onlyCache = true;
- if (httpUrl.startsWith("thumb://")) {
- int idx = httpUrl.indexOf(":", 8);
- if (idx >= 0) {
- cacheFile = new File(httpUrl.substring(idx + 1));
+ final Integer finalTag = TAG;
+ final boolean finalIsNeedsQualityThumb = imageReceiver.isNeedsQualityThumb();
+ final MessageObject parentMessageObject = imageReceiver.getParentMessageObject();
+ final boolean shouldGenerateQualityThumb = imageReceiver.isShouldGenerateQualityThumb();
+ imageLoadQueue.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ boolean added = false;
+ if (thumb != 2) {
+ CacheImage alreadyLoadingUrl = imageLoadingByUrl.get(url);
+ CacheImage alreadyLoadingCache = imageLoadingByKeys.get(key);
+ CacheImage alreadyLoadingImage = imageLoadingByTag.get(finalTag);
+ if (alreadyLoadingImage != null) {
+ if (alreadyLoadingImage == alreadyLoadingUrl || alreadyLoadingImage == alreadyLoadingCache) {
+ added = true;
+ } else {
+ alreadyLoadingImage.removeImageReceiver(imageReceiver);
+ }
+ }
+
+ if (!added && alreadyLoadingCache != null) {
+ alreadyLoadingCache.addImageReceiver(imageReceiver);
+ added = true;
+ }
+ if (!added && alreadyLoadingUrl != null) {
+ alreadyLoadingUrl.addImageReceiver(imageReceiver);
+ added = true;
+ }
+ }
+
+ if (!added) {
+ boolean onlyCache = false;
+ boolean isQuality = false;
+ File cacheFile = null;
+
+ if (httpLocation != null) {
+ if (!httpLocation.startsWith("http")) {
+ onlyCache = true;
+ if (httpLocation.startsWith("thumb://")) {
+ int idx = httpLocation.indexOf(":", 8);
+ if (idx >= 0) {
+ cacheFile = new File(httpLocation.substring(idx + 1));
+ }
+ } else {
+ cacheFile = new File(httpLocation);
+ }
+ }
+ } else if (thumb != 0) {
+ if (finalIsNeedsQualityThumb) {
+ cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "q_" + url);
+ if (!cacheFile.exists()) {
+ cacheFile = null;
+ }
+ }
+
+ if (parentMessageObject != null) {
+ File attachPath = null;
+ if (parentMessageObject.messageOwner.attachPath != null && parentMessageObject.messageOwner.attachPath.length() > 0) {
+ attachPath = new File(parentMessageObject.messageOwner.attachPath);
+ if (!attachPath.exists()) {
+ attachPath = null;
+ }
+ }
+ if (attachPath == null) {
+ attachPath = FileLoader.getPathToMessage(parentMessageObject.messageOwner);
+ }
+ if (finalIsNeedsQualityThumb && cacheFile == null) {
+ String location = parentMessageObject.getFileName();
+ ThumbGenerateInfo info = waitingForQualityThumb.get(location);
+ if (info == null) {
+ info = new ThumbGenerateInfo();
+ info.fileLocation = (TLRPC.TL_fileLocation) imageLocation;
+ info.filter = filter;
+ waitingForQualityThumb.put(location, info);
+ }
+ info.count++;
+ waitingForQualityThumbByTag.put(finalTag, location);
+ }
+ if (attachPath.exists() && shouldGenerateQualityThumb) {
+ generateThumb(parentMessageObject.getFileType(), attachPath, (TLRPC.TL_fileLocation) imageLocation, filter);
+ }
+ }
+ }
+
+ if (thumb != 2) {
+ if (cacheFile == null) {
+ if (cacheOnly || size == 0 || httpLocation != null) {
+ cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), url);
+ } else {
+ cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_IMAGE), url);
+ }
+ }
+
+ CacheImage img = new CacheImage();
+ img.thumb = thumb != 0;
+ img.key = key;
+ img.filter = filter;
+ img.httpUrl = httpLocation;
+ img.addImageReceiver(imageReceiver);
+ if (onlyCache || cacheFile.exists()) {
+ img.finalFilePath = cacheFile;
+ img.cacheTask = new CacheOutTask(img);
+ imageLoadingByKeys.put(key, img);
+ if (thumb != 0) {
+ cacheThumbOutQueue.postRunnable(img.cacheTask);
+ } else {
+ cacheOutQueue.postRunnable(img.cacheTask);
+ }
+ } else {
+ img.url = url;
+ img.location = imageLocation;
+ imageLoadingByUrl.put(url, img);
+ if (httpLocation == null) {
+ if (imageLocation instanceof TLRPC.FileLocation) {
+ TLRPC.FileLocation location = (TLRPC.FileLocation) imageLocation;
+ FileLoader.getInstance().loadFile(location, size, size == 0 || location.key != null || cacheOnly);
+ } else if (imageLocation instanceof TLRPC.Document) {
+ FileLoader.getInstance().loadFile((TLRPC.Document) imageLocation, true, true);
+ }
+ } else {
+ String file = Utilities.MD5(httpLocation);
+ File cacheDir = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE);
+ img.tempFilePath = new File(cacheDir, file + "_temp.jpg");
+ img.finalFilePath = cacheFile;
+ img.httpTask = new HttpImageTask(img, size);
+ httpTasks.add(img.httpTask);
+ runHttpTasks(false);
+ }
}
- } else {
- cacheFile = new File(httpUrl);
}
}
}
- CacheImage img = new CacheImage();
- if (onlyCache || cacheFile.exists()) {
- img.finalFilePath = cacheFile;
- img.key = key;
- img.httpUrl = httpUrl;
- if (imageView.getFilter() != null) {
- img.filter = imageView.getFilter();
+ });
+ }
+
+ public void loadImageForImageReceiver(ImageReceiver imageReceiver) {
+ if (imageReceiver == null) {
+ return;
+ }
+
+ String key = imageReceiver.getKey();
+ if (key != null) {
+ BitmapDrawable bitmapDrawable = memCache.get(key);
+ if (bitmapDrawable != null) {
+ cancelLoadingForImageReceiver(imageReceiver, 0);
+ if (!imageReceiver.isForcePreview()) {
+ imageReceiver.setImageBitmapByKey(bitmapDrawable, key, false);
+ return;
}
- img.addImageView(imageView);
- imageLoadingByKeys.put(key, img);
- img.cacheTask = new CacheOutTask(img);
- cacheOutQueue.postRunnable(img.cacheTask);
+ }
+ }
+ boolean thumbSet = false;
+ String thumbKey = imageReceiver.getThumbKey();
+ if (thumbKey != null) {
+ BitmapDrawable bitmapDrawable = memCache.get(thumbKey);
+ if (bitmapDrawable != null) {
+ imageReceiver.setImageBitmapByKey(bitmapDrawable, thumbKey, true);
+ cancelLoadingForImageReceiver(imageReceiver, 1);
+ thumbSet = true;
+ }
+ }
+
+ TLRPC.FileLocation thumbLocation = imageReceiver.getThumbLocation();
+ TLObject imageLocation = imageReceiver.getImageLocation();
+ String httpLocation = imageReceiver.getHttpImageLocation();
+
+ boolean saveImageToCache = false;
+
+ String url = null;
+ String thumbUrl = null;
+ key = null;
+ thumbKey = null;
+ String ext = null;
+ if (httpLocation != null) {
+ key = Utilities.MD5(httpLocation);
+ url = key + "." + getHttpUrlExtension(httpLocation);
+ } else if (imageLocation != null) {
+ if (imageLocation instanceof TLRPC.FileLocation) {
+ TLRPC.FileLocation location = (TLRPC.FileLocation) imageLocation;
+ key = location.volume_id + "_" + location.local_id;
+ ext = "." + (location.ext != null ? location.ext : "jpg");
+ url = key + ext;
+ if (location.ext != null || location.key != null || location.volume_id == Integer.MIN_VALUE && location.local_id < 0) {
+ saveImageToCache = true;
+ }
+ } else if (imageLocation instanceof TLRPC.Document) {
+ TLRPC.Document location = (TLRPC.Document) imageLocation;
+ if (location.id == 0 || location.dc_id == 0) {
+ return;
+ }
+ key = location.dc_id + "_" + location.id;
+ ext = ".webp";
+ url = key + ext;
+ if (thumbKey != null) {
+ thumbUrl = thumbKey + ext;
+ }
+ saveImageToCache = true;
+ }
+ if (imageLocation == thumbLocation) {
+ imageLocation = null;
+ key = null;
+ url = null;
+ }
+ }
+
+ if (thumbLocation != null) {
+ thumbKey = thumbLocation.volume_id + "_" + thumbLocation.local_id;
+ if (ext != null) {
+ thumbUrl = thumbKey + ext;
} else {
- img.url = url;
- img.fileLocation = fileLocation;
- img.httpUrl = httpUrl;
- img.addImageView(imageView);
- imageLoadingByUrl.put(url, img);
- if (httpUrl == null) {
- if (fileLocation instanceof TLRPC.FileLocation) {
- TLRPC.FileLocation location = (TLRPC.FileLocation) fileLocation;
- FileLoader.getInstance().loadFile(location, size, size == 0 || location.key != null || cacheOnly);
- } else if (fileLocation instanceof TLRPC.Document) {
- FileLoader.getInstance().loadFile((TLRPC.Document) fileLocation, true, true);
+ thumbUrl = thumbKey + "." + (thumbLocation.ext != null ? thumbLocation.ext : "jpg");
+ }
+ }
+
+ String filter = imageReceiver.getFilter();
+ String thumbFilter = imageReceiver.getThumbFilter();
+ if (key != null && filter != null) {
+ key += "@" + filter;
+ }
+ if (thumbKey != null && thumbFilter != null) {
+ thumbKey += "@" + thumbFilter;
+ }
+
+ if (httpLocation != null) {
+ createLoadOperationForImageReceiver(imageReceiver, key, url, null, httpLocation, filter, 0, true, 0);
+ } else {
+ createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, thumbLocation, null, thumbFilter, 0, true, thumbSet ? 2 : 1);
+ createLoadOperationForImageReceiver(imageReceiver, key, url, imageLocation, null, filter, imageReceiver.getSize(), saveImageToCache || imageReceiver.getCacheOnly(), 0);
+ }
+ }
+
+ private void httpFileLoadError(final String location) {
+ imageLoadQueue.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ CacheImage img = imageLoadingByUrl.get(location);
+ if (img == null) {
+ return;
+ }
+ HttpImageTask oldTask = img.httpTask;
+ img.httpTask = new HttpImageTask(oldTask.cacheImage, oldTask.imageSize);
+ httpTasks.add(img.httpTask);
+ runHttpTasks(false);
+ }
+ });
+ }
+
+ private void fileDidLoaded(final String location, final File finalFile, final int type) {
+ imageLoadQueue.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ ThumbGenerateInfo info = waitingForQualityThumb.get(location);
+ if (info != null) {
+ generateThumb(type, finalFile, info.fileLocation, info.filter);
+ waitingForQualityThumb.remove(location);
+ }
+ CacheImage img = imageLoadingByUrl.get(location);
+ if (img == null) {
+ return;
+ }
+ imageLoadingByUrl.remove(location);
+ CacheOutTask task = null;
+ for (ImageReceiver imageReceiver : img.imageReceiverArray) {
+ CacheImage cacheImage = imageLoadingByKeys.get(img.key);
+ if (cacheImage == null) {
+ cacheImage = new CacheImage();
+ cacheImage.finalFilePath = finalFile;
+ cacheImage.key = img.key;
+ cacheImage.httpUrl = img.httpUrl;
+ cacheImage.thumb = img.thumb;
+ cacheImage.cacheTask = task = new CacheOutTask(cacheImage);
+ cacheImage.filter = img.filter;
+ imageLoadingByKeys.put(cacheImage.key, cacheImage);
+ }
+ cacheImage.addImageReceiver(imageReceiver);
+ }
+ if (task != null) {
+ if (img.thumb) {
+ cacheThumbOutQueue.postRunnable(task);
+ } else {
+ cacheOutQueue.postRunnable(task);
}
- } else {
- String file = Utilities.MD5(httpUrl);
- File cacheDir = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE);
- img.tempFilePath = new File(cacheDir, file + "_temp.jpg");
- img.finalFilePath = cacheFile;
- img.httpTask = new HttpImageTask(img, size);
- httpTasks.add(img.httpTask);
- runHttpTasks(false);
}
}
- }
+ });
}
- private void httpFileLoadError(String location) {
- CacheImage img = imageLoadingByUrl.get(location);
- if (img == null) {
+ private void fileDidFailedLoad(final String location, int canceled) {
+ if (canceled == 1) {
return;
}
- HttpImageTask oldTask = img.httpTask;
- img.httpTask = new HttpImageTask(oldTask.cacheImage, oldTask.imageSize);
- httpTasks.add(img.httpTask);
- runHttpTasks(false);
- }
-
- private void fileDidLoaded(String location, File finalFile, File tempFile) {
- CacheImage img = imageLoadingByUrl.get(location);
- if (img == null) {
- return;
- }
- imageLoadingByUrl.remove(location);
- for (ImageReceiver imageReceiver : img.imageViewArray) {
- String key = imageReceiver.getKey();
- if (key == null) {
- continue;
- }
- CacheImage cacheImage = imageLoadingByKeys.get(key);
- if (cacheImage == null) {
- cacheImage = new CacheImage();
- cacheImage.finalFilePath = finalFile;
- cacheImage.tempFilePath = tempFile;
- cacheImage.key = key;
- cacheImage.httpUrl = img.httpUrl;
- cacheImage.cacheTask = new CacheOutTask(cacheImage);
- if (imageReceiver.getFilter() != null) {
- cacheImage.filter = imageReceiver.getFilter();
+ imageLoadQueue.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ CacheImage img = imageLoadingByUrl.get(location);
+ if (img != null) {
+ img.setImageAndClear(null);
}
- imageLoadingByKeys.put(cacheImage.key, cacheImage);
- cacheOutQueue.postRunnable(cacheImage.cacheTask);
}
- cacheImage.addImageView(imageReceiver);
- }
- }
-
- private void fileDidFailedLoad(String location) {
- CacheImage img = imageLoadingByUrl.get(location);
- if (img != null) {
- img.setImageAndClear(null);
- }
+ });
}
private void runHttpTasks(boolean complete) {
@@ -1331,7 +1716,7 @@ public class ImageLoader {
}
}
- public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxHeight) {
+ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxHeight, boolean useMaxScale) {
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
FileDescriptor fileDescriptor = null;
@@ -1372,7 +1757,7 @@ public class ImageLoader {
}
float photoW = bmOptions.outWidth;
float photoH = bmOptions.outHeight;
- float scaleFactor = Math.max(photoW / maxWidth, photoH / maxHeight);
+ float scaleFactor = useMaxScale ? Math.max(photoW / maxWidth, photoH / maxHeight) : Math.min(photoW / maxWidth, photoH / maxHeight);
if (scaleFactor < 1) {
scaleFactor = 1;
}
@@ -1466,12 +1851,7 @@ public class ImageLoader {
location.dc_id = Integer.MIN_VALUE;
location.local_id = UserConfig.lastLocalId;
UserConfig.lastLocalId--;
- TLRPC.PhotoSize size;
- if (!cache) {
- size = new TLRPC.TL_photoSize();
- } else {
- size = new TLRPC.TL_photoCachedSize();
- }
+ TLRPC.PhotoSize size = new TLRPC.TL_photoSize();
size.location = location;
size.w = scaledBitmap.getWidth();
size.h = scaledBitmap.getHeight();
@@ -1487,18 +1867,20 @@ public class ImageLoader {
size.type = "w";
}
- if (!cache) {
- String fileName = location.volume_id + "_" + location.local_id + ".jpg";
- final File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName);
- FileOutputStream stream = new FileOutputStream(cacheFile);
- scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream);
- size.size = (int)stream.getChannel().size();
- } else {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream);
- size.bytes = stream.toByteArray();
+ String fileName = location.volume_id + "_" + location.local_id + ".jpg";
+ final File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName);
+ FileOutputStream stream = new FileOutputStream(cacheFile);
+ scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream);
+ if (cache) {
+ ByteArrayOutputStream stream2 = new ByteArrayOutputStream();
+ scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream2);
+ size.bytes = stream2.toByteArray();
size.size = size.bytes.length;
+ stream2.close();
+ } else {
+ size.size = (int)stream.getChannel().size();
}
+ stream.close();
if (scaledBitmap != bitmap) {
scaledBitmap.recycle();
}
@@ -1557,4 +1939,77 @@ public class ImageLoader {
}
return ext;
}
+
+ public static void saveMessageThumbs(TLRPC.Message message) {
+ TLRPC.PhotoSize photoSize = null;
+ if (message.media instanceof TLRPC.TL_messageMediaPhoto) {
+ for (TLRPC.PhotoSize size : message.media.photo.sizes) {
+ if (size instanceof TLRPC.TL_photoCachedSize) {
+ photoSize = size;
+ break;
+ }
+ }
+ } else if (message.media instanceof TLRPC.TL_messageMediaVideo) {
+ if (message.media.video.thumb instanceof TLRPC.TL_photoCachedSize) {
+ photoSize = message.media.video.thumb;
+ }
+ } else if (message.media instanceof TLRPC.TL_messageMediaDocument) {
+ if (message.media.document.thumb instanceof TLRPC.TL_photoCachedSize) {
+ photoSize = message.media.document.thumb;
+ for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) {
+ if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
+ photoSize.location.ext = "webp";
+ break;
+ }
+ }
+ }
+ }
+ if (photoSize != null && photoSize.bytes != null && photoSize.bytes.length != 0) {
+ if (photoSize.location instanceof TLRPC.TL_fileLocationUnavailable) {
+ photoSize.location = new TLRPC.TL_fileLocation();
+ photoSize.location.volume_id = Integer.MIN_VALUE;
+ photoSize.location.dc_id = Integer.MIN_VALUE;
+ photoSize.location.local_id = UserConfig.lastLocalId;
+ UserConfig.lastLocalId--;
+ }
+ File file = FileLoader.getPathToAttach(photoSize, true);
+ if (!file.exists()) {
+ try {
+ RandomAccessFile writeFile = new RandomAccessFile(file, "rws");
+ writeFile.write(photoSize.bytes);
+ writeFile.close();
+ } catch (Exception e) {
+ FileLog.e("tmessages", e);
+ }
+ }
+ TLRPC.TL_photoSize newPhotoSize = new TLRPC.TL_photoSize();
+ newPhotoSize.w = photoSize.w;
+ newPhotoSize.h = photoSize.h;
+ newPhotoSize.location = photoSize.location;
+ newPhotoSize.size = photoSize.size;
+ newPhotoSize.type = photoSize.type;
+
+ if (message.media instanceof TLRPC.TL_messageMediaPhoto) {
+ for (int a = 0; a < message.media.photo.sizes.size(); a++) {
+ if (message.media.photo.sizes.get(a) instanceof TLRPC.TL_photoCachedSize) {
+ message.media.photo.sizes.set(a, newPhotoSize);
+ break;
+ }
+ }
+ } else if (message.media instanceof TLRPC.TL_messageMediaVideo) {
+ message.media.video.thumb = newPhotoSize;
+ } else if (message.media instanceof TLRPC.TL_messageMediaDocument) {
+ message.media.document.thumb = newPhotoSize;
+ }
+ }
+ }
+
+ public static void saveMessagesThumbs(ArrayList messages) {
+ if (messages == null || messages.isEmpty()) {
+ return;
+ }
+ for (TLRPC.Message message : messages) {
+ saveMessageThumbs(message);
+ }
+ }
}
diff --git a/TMessagesProj/src/main/java/org/telegram/android/ImageReceiver.java b/TMessagesProj/src/main/java/org/telegram/android/ImageReceiver.java
index 8290a5f8..9c467f72 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/ImageReceiver.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/ImageReceiver.java
@@ -27,33 +27,49 @@ import org.telegram.messenger.TLRPC;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.Utilities;
-public class ImageReceiver {
- private TLObject last_path = null;
- private String last_httpUrl = null;
- private String last_filter = null;
- private Drawable last_placeholder = null;
- private TLRPC.FileLocation last_placeholderLocation = null;
- private int last_size = 0;
- private String currentPath = null;
- private boolean isPlaceholder = false;
- private Drawable currentImage = null;
- private Integer tag = null;
- private View parentView = null;
- private int imageX = 0, imageY = 0, imageW = 0, imageH = 0;
+public class ImageReceiver implements NotificationCenter.NotificationCenterDelegate {
+
+ public static interface ImageReceiverDelegate {
+ public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb);
+ }
+
+ private View parentView;
+ private Integer tag;
+ private Integer thumbTag;
+ private MessageObject parentMessageObject;
+ private boolean canceledLoading;
+
+ private TLObject currentImageLocation;
+ private String currentKey;
+ private String currentThumbKey;
+ private String currentHttpUrl;
+ private String currentFilter;
+ private String currentThumbFilter;
+ private TLRPC.FileLocation currentThumbLocation;
+ private int currentSize;
+ private boolean currentCacheOnly;
+ private BitmapDrawable currentImage;
+ private BitmapDrawable currentThumb;
+ private Drawable staticThumb;
+
+ private boolean needsQualityThumb;
+ private boolean shouldGenerateQualityThumb;
+
+ private int imageX, imageY, imageW, imageH;
private Rect drawRegion = new Rect();
private boolean isVisible = true;
- private boolean isAspectFit = false;
- private boolean lastCacheOnly = false;
- private boolean forcePreview = false;
- private int roundRadius = 0;
- private BitmapShader bitmapShader = null;
- private Paint roundPaint = null;
- private RectF roundRect = null;
- private RectF bitmapRect = null;
- private Matrix shaderMatrix = null;
+ private boolean isAspectFit;
+ private boolean forcePreview;
+ private int roundRadius;
+ private BitmapShader bitmapShader;
+ private Paint roundPaint;
+ private RectF roundRect;
+ private RectF bitmapRect;
+ private Matrix shaderMatrix;
private int alpha = 255;
private boolean isPressed;
private boolean disableRecycle;
+ private ImageReceiverDelegate delegate;
public ImageReceiver() {
@@ -63,38 +79,64 @@ public class ImageReceiver {
parentView = view;
}
- public void setImage(TLObject path, String filter, Drawable placeholder, boolean cacheOnly) {
- setImage(path, null, filter, placeholder, null, 0, cacheOnly);
+ public void cancelLoadImage() {
+ ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
+ canceledLoading = true;
}
- public void setImage(TLObject path, String filter, Drawable placeholder, int size, boolean cacheOnly) {
- setImage(path, null, filter, placeholder, null, size, cacheOnly);
+ public void setImage(TLObject path, String filter, Drawable thumb, boolean cacheOnly) {
+ setImage(path, null, filter, thumb, null, null, 0, cacheOnly);
}
- public void setImage(String path, String filter, Drawable placeholder, int size) {
- setImage(null, path, filter, placeholder, null, size, true);
+ public void setImage(TLObject path, String filter, Drawable thumb, int size, boolean cacheOnly) {
+ setImage(path, null, filter, thumb, null, null, size, cacheOnly);
}
- public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawable placeholder, TLRPC.FileLocation placeholderLocation, int size, boolean cacheOnly) {
- if ((fileLocation == null && httpUrl == null) || (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation) && !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation) && !(fileLocation instanceof TLRPC.TL_document))) {
- recycleBitmap(null);
- currentPath = null;
- isPlaceholder = true;
- last_path = null;
- last_httpUrl = null;
- last_filter = null;
- lastCacheOnly = false;
- bitmapShader = null;
- last_placeholder = placeholder;
- last_placeholderLocation = placeholderLocation;
- last_size = 0;
+ public void setImage(String httpUrl, String filter, Drawable thumb, int size) {
+ setImage(null, httpUrl, filter, thumb, null, null, size, true);
+ }
+
+ public void setImage(TLObject fileLocation, String filter, TLRPC.FileLocation thumbLocation, String thumbFilter, boolean cacheOnly) {
+ setImage(fileLocation, null, filter, null, thumbLocation, thumbFilter, 0, cacheOnly);
+ }
+
+ public void setImage(TLObject fileLocation, String filter, TLRPC.FileLocation thumbLocation, String thumbFilter, int size, boolean cacheOnly) {
+ setImage(fileLocation, null, filter, null, thumbLocation, thumbFilter, size, cacheOnly);
+ }
+
+ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawable thumb, TLRPC.FileLocation thumbLocation, String thumbFilter, int size, boolean cacheOnly) {
+ if ((fileLocation == null && httpUrl == null && thumbLocation == null)
+ || (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation)
+ && !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation)
+ && !(fileLocation instanceof TLRPC.TL_document))) {
+ recycleBitmap(null, false);
+ recycleBitmap(null, true);
+ currentKey = null;
+ currentThumbKey = null;
+ currentThumbFilter = null;
+ currentImageLocation = null;
+ currentHttpUrl = null;
+ currentFilter = null;
+ currentCacheOnly = false;
+ staticThumb = thumb;
+ currentThumbLocation = null;
+ currentSize = 0;
currentImage = null;
- ImageLoader.getInstance().cancelLoadingForImageView(this);
+ bitmapShader = null;
+ ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
if (parentView != null) {
parentView.invalidate();
}
+ if (delegate != null) {
+ delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
+ }
return;
}
+
+ if (!(thumbLocation instanceof TLRPC.TL_fileLocation)) {
+ thumbLocation = null;
+ }
+
String key = null;
if (fileLocation != null) {
if (fileLocation instanceof TLRPC.FileLocation) {
@@ -104,80 +146,61 @@ public class ImageReceiver {
TLRPC.Document location = (TLRPC.Document) fileLocation;
key = location.dc_id + "_" + location.id;
}
- } else {
+ } else if (httpUrl != null) {
key = Utilities.MD5(httpUrl);
}
- if (filter != null) {
- key += "@" + filter;
- }
- boolean sameFile = false;
- BitmapDrawable img = null;
- if (currentPath != null) {
- if (currentPath.equals(key)) {
- sameFile = true;
- if (currentImage != null) {
- return;
- } else {
- img = ImageLoader.getInstance().getImageFromMemory(fileLocation, httpUrl, filter, this);
- }
- } else {
- img = ImageLoader.getInstance().getImageFromMemory(fileLocation, httpUrl, filter, this);
- recycleBitmap(img);
+ if (key != null) {
+ if (filter != null) {
+ key += "@" + filter;
}
}
- img = ImageLoader.getInstance().getImageFromMemory(fileLocation, httpUrl, filter, this);
- currentPath = key;
- last_path = fileLocation;
- last_httpUrl = httpUrl;
- last_filter = filter;
- if (!sameFile) {
- last_placeholder = placeholder;
- last_placeholderLocation = placeholderLocation;
- }
- last_size = size;
- lastCacheOnly = cacheOnly;
- bitmapShader = null;
- if (img == null) {
- isPlaceholder = true;
- if (!sameFile && last_placeholderLocation != null && last_placeholder == null) {
- last_placeholder = ImageLoader.getInstance().getImageFromMemory(last_placeholderLocation, null, null, null);
- if (last_placeholder != null) {
- try {
- Bitmap bitmap = ((BitmapDrawable) last_placeholder).getBitmap();
- bitmap = bitmap.copy(bitmap.getConfig(), true);
- Utilities.blurBitmap(bitmap, 1);
- last_placeholder = new BitmapDrawable(bitmap);
- } catch (Exception e) {
- FileLog.e("tmessages", e);
- }
- }
- }
- ImageLoader.getInstance().loadImage(fileLocation, httpUrl, this, size, cacheOnly);
- if (parentView != null) {
- parentView.invalidate();
- }
- } else {
- setImageBitmap(img, currentPath);
- }
- }
- public void setImageBitmap(BitmapDrawable bitmap, String imgKey) {
- if (currentPath == null || !imgKey.equals(currentPath)) {
- return;
+ if (currentKey != null && key != null && currentKey.equals(key)) {
+ if (delegate != null) {
+ delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
+ }
+ if (!canceledLoading && !forcePreview) {
+ return;
+ }
}
- isPlaceholder = false;
- ImageLoader.getInstance().incrementUseCount(currentPath);
- currentImage = bitmap;
- if (roundRadius != 0) {
- bitmapShader = new BitmapShader(bitmap.getBitmap(), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
- roundPaint.setShader(bitmapShader);
- bitmapRect.set(0, 0, bitmap.getBitmap().getWidth(), bitmap.getBitmap().getHeight());
+
+ String thumbKey = null;
+ if (thumbLocation != null) {
+ thumbKey = thumbLocation.volume_id + "_" + thumbLocation.local_id;
+ if (thumbFilter != null) {
+ thumbKey += "@" + thumbFilter;
+ }
}
+
+ recycleBitmap(key, false);
+ recycleBitmap(thumbKey, true);
+
+ currentThumbKey = thumbKey;
+ currentKey = key;
+ currentImageLocation = fileLocation;
+ currentHttpUrl = httpUrl;
+ currentFilter = filter;
+ currentThumbFilter = thumbFilter;
+ currentSize = size;
+ currentCacheOnly = cacheOnly;
+ currentThumbLocation = thumbLocation;
+ staticThumb = thumb;
+ bitmapShader = null;
+
+ if (delegate != null) {
+ delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
+ }
+
+ ImageLoader.getInstance().loadImageForImageReceiver(this);
if (parentView != null) {
parentView.invalidate();
}
}
+ public void setDelegate(ImageReceiverDelegate delegate) {
+ this.delegate = delegate;
+ }
+
public void setPressed(boolean value) {
isPressed = value;
}
@@ -195,62 +218,50 @@ public class ImageReceiver {
}
public void setImageBitmap(Drawable bitmap) {
- ImageLoader.getInstance().cancelLoadingForImageView(this);
- recycleBitmap(null);
- last_placeholder = bitmap;
- isPlaceholder = true;
- last_placeholderLocation = null;
- currentPath = null;
+ ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
+ recycleBitmap(null, false);
+ recycleBitmap(null, true);
+ staticThumb = bitmap;
+ currentThumbLocation = null;
+ currentKey = null;
+ currentThumbKey = null;
currentImage = null;
- last_path = null;
- last_httpUrl = null;
- last_filter = null;
+ currentThumbFilter = null;
+ currentImageLocation = null;
+ currentHttpUrl = null;
+ currentFilter = null;
+ currentSize = 0;
+ currentCacheOnly = false;
bitmapShader = null;
- last_size = 0;
- lastCacheOnly = false;
+ if (delegate != null) {
+ delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
+ }
if (parentView != null) {
parentView.invalidate();
}
}
public void clearImage() {
- recycleBitmap(null);
- }
-
- private void recycleBitmap(BitmapDrawable newBitmap) {
- if (currentImage == null || isPlaceholder || disableRecycle) {
- return;
- }
- if (currentImage instanceof BitmapDrawable) {
- if (currentImage != newBitmap) {
- if (currentPath != null) {
- Bitmap bitmap = ((BitmapDrawable) currentImage).getBitmap();
- boolean canDelete = ImageLoader.getInstance().decrementUseCount(currentPath);
- if (!ImageLoader.getInstance().isInCache(currentPath)) {
- if (ImageLoader.getInstance().runtimeHack != null) {
- ImageLoader.getInstance().runtimeHack.trackAlloc(bitmap.getRowBytes() * bitmap.getHeight());
- }
- if (canDelete) {
- currentImage = null;
- bitmap.recycle();
- }
- } else {
- currentImage = null;
- }
- currentPath = null;
- }
- }
+ recycleBitmap(null, false);
+ recycleBitmap(null, true);
+ if (needsQualityThumb) {
+ NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageThumbGenerated);
+ ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
}
}
public boolean draw(Canvas canvas) {
try {
- Drawable bitmapDrawable = currentImage;
- if (forcePreview || bitmapDrawable == null && last_placeholder != null && last_placeholder instanceof BitmapDrawable) {
- bitmapDrawable = last_placeholder;
+ BitmapDrawable bitmapDrawable = null;
+ if (!forcePreview && currentImage != null) {
+ bitmapDrawable = currentImage;
+ } else if (staticThumb instanceof BitmapDrawable) {
+ bitmapDrawable = (BitmapDrawable) staticThumb;
+ } else if (currentThumb != null) {
+ bitmapDrawable = currentThumb;
}
if (bitmapDrawable != null) {
- Paint paint = ((BitmapDrawable) bitmapDrawable).getPaint();
+ Paint paint = bitmapDrawable.getPaint();
boolean hasFilter = paint != null && paint.getColorFilter() != null;
if (hasFilter && !isPressed) {
bitmapDrawable.setColorFilter(null);
@@ -285,11 +296,14 @@ public class ImageReceiver {
bitmapDrawable.setAlpha(alpha);
bitmapDrawable.draw(canvas);
} catch (Exception e) {
- if (currentPath != null) {
- ImageLoader.getInstance().removeImage(currentPath);
- currentPath = null;
+ if (bitmapDrawable == currentImage && currentKey != null) {
+ ImageLoader.getInstance().removeImage(currentKey);
+ currentKey = null;
+ } else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
+ ImageLoader.getInstance().removeImage(currentThumbKey);
+ currentThumbKey = null;
}
- setImage(last_path, last_httpUrl, last_filter, last_placeholder, last_placeholderLocation, last_size, lastCacheOnly);
+ setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly);
FileLog.e("tmessages", e);
}
canvas.restore();
@@ -311,11 +325,14 @@ public class ImageReceiver {
bitmapDrawable.setAlpha(alpha);
bitmapDrawable.draw(canvas);
} catch (Exception e) {
- if (currentPath != null) {
- ImageLoader.getInstance().removeImage(currentPath);
- currentPath = null;
+ if (bitmapDrawable == currentImage && currentKey != null) {
+ ImageLoader.getInstance().removeImage(currentKey);
+ currentKey = null;
+ } else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
+ ImageLoader.getInstance().removeImage(currentThumbKey);
+ currentThumbKey = null;
}
- setImage(last_path, last_httpUrl, last_filter, last_placeholder, last_placeholderLocation, last_size, lastCacheOnly);
+ setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly);
FileLog.e("tmessages", e);
}
}
@@ -329,11 +346,14 @@ public class ImageReceiver {
bitmapDrawable.setAlpha(alpha);
bitmapDrawable.draw(canvas);
} catch (Exception e) {
- if (currentPath != null) {
- ImageLoader.getInstance().removeImage(currentPath);
- currentPath = null;
+ if (bitmapDrawable == currentImage && currentKey != null) {
+ ImageLoader.getInstance().removeImage(currentKey);
+ currentKey = null;
+ } else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
+ ImageLoader.getInstance().removeImage(currentThumbKey);
+ currentThumbKey = null;
}
- setImage(last_path, last_httpUrl, last_filter, last_placeholder, last_placeholderLocation, last_size, lastCacheOnly);
+ setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly);
FileLog.e("tmessages", e);
}
}
@@ -341,19 +361,14 @@ public class ImageReceiver {
}
}
return true;
- } else if (last_placeholder != null) {
+ } else if (staticThumb != null) {
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
- last_placeholder.setBounds(drawRegion);
+ staticThumb.setBounds(drawRegion);
if (isVisible) {
try {
- last_placeholder.setAlpha(alpha);
- last_placeholder.draw(canvas);
+ staticThumb.setAlpha(alpha);
+ staticThumb.draw(canvas);
} catch (Exception e) {
- if (currentPath != null) {
- ImageLoader.getInstance().removeImage(currentPath);
- currentPath = null;
- }
- setImage(last_path, last_httpUrl, last_filter, last_placeholder, last_placeholderLocation, last_size, lastCacheOnly);
FileLog.e("tmessages", e);
}
}
@@ -366,10 +381,12 @@ public class ImageReceiver {
}
public Bitmap getBitmap() {
- if (currentImage != null && currentImage instanceof BitmapDrawable) {
- return ((BitmapDrawable)currentImage).getBitmap();
- } else if (isPlaceholder && last_placeholder != null && last_placeholder instanceof BitmapDrawable) {
- return ((BitmapDrawable)last_placeholder).getBitmap();
+ if (currentImage != null) {
+ return currentImage.getBitmap();
+ } else if (currentThumb != null) {
+ return currentThumb.getBitmap();
+ } else if (staticThumb instanceof BitmapDrawable) {
+ return ((BitmapDrawable) staticThumb).getBitmap();
}
return null;
}
@@ -393,7 +410,7 @@ public class ImageReceiver {
}
public boolean hasImage() {
- return currentImage != null || last_placeholder != null || currentPath != null || last_httpUrl != null;
+ return currentImage != null || currentThumb != null || currentKey != null || currentHttpUrl != null || staticThumb != null;
}
public void setAspectFit(boolean value) {
@@ -404,14 +421,6 @@ public class ImageReceiver {
parentView = view;
}
- protected Integer getTag() {
- return tag;
- }
-
- protected void setTag(Integer tag) {
- this.tag = tag;
- }
-
public void setImageCoords(int x, int y, int width, int height) {
imageX = x;
imageY = y;
@@ -444,17 +453,49 @@ public class ImageReceiver {
}
public String getFilter() {
- return last_filter;
+ return currentFilter;
+ }
+
+ public String getThumbFilter() {
+ return currentThumbFilter;
}
public String getKey() {
- return currentPath;
+ return currentKey;
+ }
+
+ public String getThumbKey() {
+ return currentThumbKey;
+ }
+
+ public int getSize() {
+ return currentSize;
+ }
+
+ public TLObject getImageLocation() {
+ return currentImageLocation;
+ }
+
+ public TLRPC.FileLocation getThumbLocation() {
+ return currentThumbLocation;
+ }
+
+ public String getHttpImageLocation() {
+ return currentHttpUrl;
+ }
+
+ public boolean getCacheOnly() {
+ return currentCacheOnly;
}
public void setForcePreview(boolean value) {
forcePreview = value;
}
+ public boolean isForcePreview() {
+ return forcePreview;
+ }
+
public void setRoundRadius(int value) {
roundRadius = value;
if (roundRadius != 0) {
@@ -475,4 +516,146 @@ public class ImageReceiver {
public int getRoundRadius() {
return roundRadius;
}
+
+ public void setParentMessageObject(MessageObject messageObject) {
+ parentMessageObject = messageObject;
+ }
+
+ public MessageObject getParentMessageObject() {
+ return parentMessageObject;
+ }
+
+ public void setNeedsQualityThumb(boolean value) {
+ needsQualityThumb = value;
+ if (needsQualityThumb) {
+ NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageThumbGenerated);
+ } else {
+ NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageThumbGenerated);
+ }
+ }
+
+ public boolean isNeedsQualityThumb() {
+ return needsQualityThumb;
+ }
+
+ public void setShouldGenerateQualityThumb(boolean value) {
+ shouldGenerateQualityThumb = value;
+ }
+
+ public boolean isShouldGenerateQualityThumb() {
+ return shouldGenerateQualityThumb;
+ }
+
+ protected Integer getTag(boolean thumb) {
+ if (thumb) {
+ return thumbTag;
+ } else {
+ return tag;
+ }
+ }
+
+ protected void setTag(Integer value, boolean thumb) {
+ if (thumb) {
+ thumbTag = value;
+ } else {
+ tag = value;
+ }
+ }
+
+ protected void setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean thumb) {
+ if (bitmap == null || key == null) {
+ return;
+ }
+ if (!thumb) {
+ if (currentKey == null || !key.equals(currentKey)) {
+ return;
+ }
+ ImageLoader.getInstance().incrementUseCount(currentKey);
+ currentImage = bitmap;
+ if (roundRadius != 0 && bitmap instanceof BitmapDrawable) {
+ Bitmap object = bitmap.getBitmap();
+ bitmapShader = new BitmapShader(object, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ roundPaint.setShader(bitmapShader);
+ bitmapRect.set(0, 0, object.getWidth(), object.getHeight());
+ }
+ if (parentView != null) {
+ parentView.invalidate();
+ }
+ } else if (currentThumb == null && (currentImage == null || forcePreview)) {
+ if (currentThumbKey == null || !key.equals(currentThumbKey)) {
+ return;
+ }
+ ImageLoader.getInstance().incrementUseCount(currentThumbKey);
+ currentThumb = bitmap;
+ if (!(staticThumb instanceof BitmapDrawable) && parentView != null) {
+ parentView.invalidate();
+ }
+ }
+
+ if (delegate != null) {
+ delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
+ }
+ }
+
+ private void recycleBitmap(String newKey, boolean thumb) {
+ String key;
+ BitmapDrawable image;
+ if (thumb) {
+ if (currentThumb == null) {
+ return;
+ }
+ key = currentThumbKey;
+ image = currentThumb;
+ } else {
+ if (currentImage == null) {
+ return;
+ }
+ key = currentKey;
+ image = currentImage;
+ }
+ BitmapDrawable newBitmap = null;
+ if (newKey != null) {
+ newBitmap = ImageLoader.getInstance().getImageFromMemory(newKey);
+ }
+ if (key == null || image == null || image == newBitmap || disableRecycle) {
+ return;
+ }
+ Bitmap bitmap = image.getBitmap();
+ boolean canDelete = ImageLoader.getInstance().decrementUseCount(key);
+ if (!ImageLoader.getInstance().isInCache(key)) {
+ if (ImageLoader.getInstance().runtimeHack != null) {
+ ImageLoader.getInstance().runtimeHack.trackAlloc(bitmap.getRowBytes() * bitmap.getHeight());
+ }
+ if (canDelete) {
+ bitmap.recycle();
+ ImageLoader.getInstance().callGC();
+ }
+ }
+ if (thumb) {
+ currentThumb = null;
+ currentThumbKey = null;
+ } else {
+ currentImage = null;
+ currentKey = null;
+ }
+ }
+
+ @Override
+ public void didReceivedNotification(int id, Object... args) {
+ if (id == NotificationCenter.messageThumbGenerated) {
+ String key = (String) args[1];
+ if (currentThumbKey != null && currentThumbKey.equals(key)) {
+ if (currentThumb == null) {
+ ImageLoader.getInstance().incrementUseCount(currentThumbKey);
+ }
+ currentThumb = (BitmapDrawable) args[0];
+ if (staticThumb instanceof BitmapDrawable) {
+ staticThumb = null;
+ }
+ if (parentView != null) {
+ parentView.invalidate();
+ }
+ }
+ }
+ }
}
diff --git a/TMessagesProj/src/main/java/org/telegram/android/LocaleController.java b/TMessagesProj/src/main/java/org/telegram/android/LocaleController.java
index 0aae84ab..db7b228b 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/LocaleController.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/LocaleController.java
@@ -58,17 +58,19 @@ public class LocaleController {
public static FastDateFormat chatDate;
public static FastDateFormat chatFullDate;
- private HashMap allRules = new HashMap();
+ private HashMap allRules = new HashMap<>();
private Locale currentLocale;
private Locale systemDefaultLocale;
private PluralRules currentPluralRules;
private LocaleInfo currentLocaleInfo;
private LocaleInfo defaultLocalInfo;
- private HashMap localeValues = new HashMap();
+ private HashMap localeValues = new HashMap<>();
private String languageOverride;
private boolean changingConfiguration = false;
+ private HashMap translitChars;
+
private class TimeZoneChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -110,10 +112,10 @@ public class LocaleController {
}
}
- public ArrayList sortedLanguages = new ArrayList();
- public HashMap languagesDict = new HashMap();
+ public ArrayList sortedLanguages = new ArrayList<>();
+ public HashMap languagesDict = new HashMap<>();
- private ArrayList otherLanguages = new ArrayList();
+ private ArrayList otherLanguages = new ArrayList<>();
private static volatile LocaleController Instance = null;
public static LocaleController getInstance() {
@@ -442,7 +444,7 @@ public class LocaleController {
private HashMap getLocaleFileStrings(File file) {
try {
- HashMap stringMap = new HashMap();
+ HashMap stringMap = new HashMap<>();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new FileInputStream(file), "UTF-8");
int eventType = parser.getEventType();
@@ -818,6 +820,542 @@ public class LocaleController {
}
}
+ public String getTranslitString(String src) {
+ if (translitChars == null) {
+ translitChars = new HashMap<>(520);
+ translitChars.put("ȼ", "c");
+ translitChars.put("ᶇ", "n");
+ translitChars.put("ɖ", "d");
+ translitChars.put("ỿ", "y");
+ translitChars.put("ᴓ", "o");
+ translitChars.put("ø", "o");
+ translitChars.put("ḁ", "a");
+ translitChars.put("ʯ", "h");
+ translitChars.put("ŷ", "y");
+ translitChars.put("ʞ", "k");
+ translitChars.put("ừ", "u");
+ translitChars.put("ꜳ", "aa");
+ translitChars.put("ij", "ij");
+ translitChars.put("ḽ", "l");
+ translitChars.put("ɪ", "i");
+ translitChars.put("ḇ", "b");
+ translitChars.put("ʀ", "r");
+ translitChars.put("ě", "e");
+ translitChars.put("ffi", "ffi");
+ translitChars.put("ơ", "o");
+ translitChars.put("ⱹ", "r");
+ translitChars.put("ồ", "o");
+ translitChars.put("ǐ", "i");
+ translitChars.put("ꝕ", "p");
+ translitChars.put("ý", "y");
+ translitChars.put("ḝ", "e");
+ translitChars.put("ₒ", "o");
+ translitChars.put("ⱥ", "a");
+ translitChars.put("ʙ", "b");
+ translitChars.put("ḛ", "e");
+ translitChars.put("ƈ", "c");
+ translitChars.put("ɦ", "h");
+ translitChars.put("ᵬ", "b");
+ translitChars.put("ṣ", "s");
+ translitChars.put("đ", "d");
+ translitChars.put("ỗ", "o");
+ translitChars.put("ɟ", "j");
+ translitChars.put("ẚ", "a");
+ translitChars.put("ɏ", "y");
+ translitChars.put("л", "l");
+ translitChars.put("ʌ", "v");
+ translitChars.put("ꝓ", "p");
+ translitChars.put("fi", "fi");
+ translitChars.put("ᶄ", "k");
+ translitChars.put("ḏ", "d");
+ translitChars.put("ᴌ", "l");
+ translitChars.put("ė", "e");
+ translitChars.put("ё", "yo");
+ translitChars.put("ᴋ", "k");
+ translitChars.put("ċ", "c");
+ translitChars.put("ʁ", "r");
+ translitChars.put("ƕ", "hv");
+ translitChars.put("ƀ", "b");
+ translitChars.put("ṍ", "o");
+ translitChars.put("ȣ", "ou");
+ translitChars.put("ǰ", "j");
+ translitChars.put("ᶃ", "g");
+ translitChars.put("ṋ", "n");
+ translitChars.put("ɉ", "j");
+ translitChars.put("ǧ", "g");
+ translitChars.put("dz", "dz");
+ translitChars.put("ź", "z");
+ translitChars.put("ꜷ", "au");
+ translitChars.put("ǖ", "u");
+ translitChars.put("ᵹ", "g");
+ translitChars.put("ȯ", "o");
+ translitChars.put("ɐ", "a");
+ translitChars.put("ą", "a");
+ translitChars.put("õ", "o");
+ translitChars.put("ɻ", "r");
+ translitChars.put("ꝍ", "o");
+ translitChars.put("ǟ", "a");
+ translitChars.put("ȴ", "l");
+ translitChars.put("ʂ", "s");
+ translitChars.put("fl", "fl");
+ translitChars.put("ȉ", "i");
+ translitChars.put("ⱻ", "e");
+ translitChars.put("ṉ", "n");
+ translitChars.put("ï", "i");
+ translitChars.put("ñ", "n");
+ translitChars.put("ᴉ", "i");
+ translitChars.put("ʇ", "t");
+ translitChars.put("ẓ", "z");
+ translitChars.put("ỷ", "y");
+ translitChars.put("ȳ", "y");
+ translitChars.put("ṩ", "s");
+ translitChars.put("ɽ", "r");
+ translitChars.put("ĝ", "g");
+ translitChars.put("в", "v");
+ translitChars.put("ᴝ", "u");
+ translitChars.put("ḳ", "k");
+ translitChars.put("ꝫ", "et");
+ translitChars.put("ī", "i");
+ translitChars.put("ť", "t");
+ translitChars.put("ꜿ", "c");
+ translitChars.put("ʟ", "l");
+ translitChars.put("ꜹ", "av");
+ translitChars.put("û", "u");
+ translitChars.put("æ", "ae");
+ translitChars.put("и", "i");
+ translitChars.put("ă", "a");
+ translitChars.put("ǘ", "u");
+ translitChars.put("ꞅ", "s");
+ translitChars.put("ᵣ", "r");
+ translitChars.put("ᴀ", "a");
+ translitChars.put("ƃ", "b");
+ translitChars.put("ḩ", "h");
+ translitChars.put("ṧ", "s");
+ translitChars.put("ₑ", "e");
+ translitChars.put("ʜ", "h");
+ translitChars.put("ẋ", "x");
+ translitChars.put("ꝅ", "k");
+ translitChars.put("ḋ", "d");
+ translitChars.put("ƣ", "oi");
+ translitChars.put("ꝑ", "p");
+ translitChars.put("ħ", "h");
+ translitChars.put("ⱴ", "v");
+ translitChars.put("ẇ", "w");
+ translitChars.put("ǹ", "n");
+ translitChars.put("ɯ", "m");
+ translitChars.put("ɡ", "g");
+ translitChars.put("ɴ", "n");
+ translitChars.put("ᴘ", "p");
+ translitChars.put("ᵥ", "v");
+ translitChars.put("ū", "u");
+ translitChars.put("ḃ", "b");
+ translitChars.put("ṗ", "p");
+ translitChars.put("ь", "");
+ translitChars.put("å", "a");
+ translitChars.put("ɕ", "c");
+ translitChars.put("ọ", "o");
+ translitChars.put("ắ", "a");
+ translitChars.put("ƒ", "f");
+ translitChars.put("ǣ", "ae");
+ translitChars.put("ꝡ", "vy");
+ translitChars.put("ff", "ff");
+ translitChars.put("ᶉ", "r");
+ translitChars.put("ô", "o");
+ translitChars.put("ǿ", "o");
+ translitChars.put("ṳ", "u");
+ translitChars.put("ȥ", "z");
+ translitChars.put("ḟ", "f");
+ translitChars.put("ḓ", "d");
+ translitChars.put("ȇ", "e");
+ translitChars.put("ȕ", "u");
+ translitChars.put("п", "p");
+ translitChars.put("ȵ", "n");
+ translitChars.put("ʠ", "q");
+ translitChars.put("ấ", "a");
+ translitChars.put("ǩ", "k");
+ translitChars.put("ĩ", "i");
+ translitChars.put("ṵ", "u");
+ translitChars.put("ŧ", "t");
+ translitChars.put("ɾ", "r");
+ translitChars.put("ƙ", "k");
+ translitChars.put("ṫ", "t");
+ translitChars.put("ꝗ", "q");
+ translitChars.put("ậ", "a");
+ translitChars.put("н", "n");
+ translitChars.put("ʄ", "j");
+ translitChars.put("ƚ", "l");
+ translitChars.put("ᶂ", "f");
+ translitChars.put("д", "d");
+ translitChars.put("ᵴ", "s");
+ translitChars.put("ꞃ", "r");
+ translitChars.put("ᶌ", "v");
+ translitChars.put("ɵ", "o");
+ translitChars.put("ḉ", "c");
+ translitChars.put("ᵤ", "u");
+ translitChars.put("ẑ", "z");
+ translitChars.put("ṹ", "u");
+ translitChars.put("ň", "n");
+ translitChars.put("ʍ", "w");
+ translitChars.put("ầ", "a");
+ translitChars.put("lj", "lj");
+ translitChars.put("ɓ", "b");
+ translitChars.put("ɼ", "r");
+ translitChars.put("ò", "o");
+ translitChars.put("ẘ", "w");
+ translitChars.put("ɗ", "d");
+ translitChars.put("ꜽ", "ay");
+ translitChars.put("ư", "u");
+ translitChars.put("ᶀ", "b");
+ translitChars.put("ǜ", "u");
+ translitChars.put("ẹ", "e");
+ translitChars.put("ǡ", "a");
+ translitChars.put("ɥ", "h");
+ translitChars.put("ṏ", "o");
+ translitChars.put("ǔ", "u");
+ translitChars.put("ʎ", "y");
+ translitChars.put("ȱ", "o");
+ translitChars.put("ệ", "e");
+ translitChars.put("ế", "e");
+ translitChars.put("ĭ", "i");
+ translitChars.put("ⱸ", "e");
+ translitChars.put("ṯ", "t");
+ translitChars.put("ᶑ", "d");
+ translitChars.put("ḧ", "h");
+ translitChars.put("ṥ", "s");
+ translitChars.put("ë", "e");
+ translitChars.put("ᴍ", "m");
+ translitChars.put("ö", "o");
+ translitChars.put("é", "e");
+ translitChars.put("ı", "i");
+ translitChars.put("ď", "d");
+ translitChars.put("ᵯ", "m");
+ translitChars.put("ỵ", "y");
+ translitChars.put("я", "ya");
+ translitChars.put("ŵ", "w");
+ translitChars.put("ề", "e");
+ translitChars.put("ứ", "u");
+ translitChars.put("ƶ", "z");
+ translitChars.put("ĵ", "j");
+ translitChars.put("ḍ", "d");
+ translitChars.put("ŭ", "u");
+ translitChars.put("ʝ", "j");
+ translitChars.put("ж", "zh");
+ translitChars.put("ê", "e");
+ translitChars.put("ǚ", "u");
+ translitChars.put("ġ", "g");
+ translitChars.put("ṙ", "r");
+ translitChars.put("ƞ", "n");
+ translitChars.put("ъ", "");
+ translitChars.put("ḗ", "e");
+ translitChars.put("ẝ", "s");
+ translitChars.put("ᶁ", "d");
+ translitChars.put("ķ", "k");
+ translitChars.put("ᴂ", "ae");
+ translitChars.put("ɘ", "e");
+ translitChars.put("ợ", "o");
+ translitChars.put("ḿ", "m");
+ translitChars.put("ꜰ", "f");
+ translitChars.put("а", "a");
+ translitChars.put("ẵ", "a");
+ translitChars.put("ꝏ", "oo");
+ translitChars.put("ᶆ", "m");
+ translitChars.put("ᵽ", "p");
+ translitChars.put("ц", "ts");
+ translitChars.put("ữ", "u");
+ translitChars.put("ⱪ", "k");
+ translitChars.put("ḥ", "h");
+ translitChars.put("ţ", "t");
+ translitChars.put("ᵱ", "p");
+ translitChars.put("ṁ", "m");
+ translitChars.put("á", "a");
+ translitChars.put("ᴎ", "n");
+ translitChars.put("ꝟ", "v");
+ translitChars.put("è", "e");
+ translitChars.put("ᶎ", "z");
+ translitChars.put("ꝺ", "d");
+ translitChars.put("ᶈ", "p");
+ translitChars.put("м", "m");
+ translitChars.put("ɫ", "l");
+ translitChars.put("ᴢ", "z");
+ translitChars.put("ɱ", "m");
+ translitChars.put("ṝ", "r");
+ translitChars.put("ṽ", "v");
+ translitChars.put("ũ", "u");
+ translitChars.put("ß", "ss");
+ translitChars.put("т", "t");
+ translitChars.put("ĥ", "h");
+ translitChars.put("ᵵ", "t");
+ translitChars.put("ʐ", "z");
+ translitChars.put("ṟ", "r");
+ translitChars.put("ɲ", "n");
+ translitChars.put("à", "a");
+ translitChars.put("ẙ", "y");
+ translitChars.put("ỳ", "y");
+ translitChars.put("ᴔ", "oe");
+ translitChars.put("ы", "i");
+ translitChars.put("ₓ", "x");
+ translitChars.put("ȗ", "u");
+ translitChars.put("ⱼ", "j");
+ translitChars.put("ẫ", "a");
+ translitChars.put("ʑ", "z");
+ translitChars.put("ẛ", "s");
+ translitChars.put("ḭ", "i");
+ translitChars.put("ꜵ", "ao");
+ translitChars.put("ɀ", "z");
+ translitChars.put("ÿ", "y");
+ translitChars.put("ǝ", "e");
+ translitChars.put("ǭ", "o");
+ translitChars.put("ᴅ", "d");
+ translitChars.put("ᶅ", "l");
+ translitChars.put("ù", "u");
+ translitChars.put("ạ", "a");
+ translitChars.put("ḅ", "b");
+ translitChars.put("ụ", "u");
+ translitChars.put("к", "k");
+ translitChars.put("ằ", "a");
+ translitChars.put("ᴛ", "t");
+ translitChars.put("ƴ", "y");
+ translitChars.put("ⱦ", "t");
+ translitChars.put("з", "z");
+ translitChars.put("ⱡ", "l");
+ translitChars.put("ȷ", "j");
+ translitChars.put("ᵶ", "z");
+ translitChars.put("ḫ", "h");
+ translitChars.put("ⱳ", "w");
+ translitChars.put("ḵ", "k");
+ translitChars.put("ờ", "o");
+ translitChars.put("î", "i");
+ translitChars.put("ģ", "g");
+ translitChars.put("ȅ", "e");
+ translitChars.put("ȧ", "a");
+ translitChars.put("ẳ", "a");
+ translitChars.put("щ", "sch");
+ translitChars.put("ɋ", "q");
+ translitChars.put("ṭ", "t");
+ translitChars.put("ꝸ", "um");
+ translitChars.put("ᴄ", "c");
+ translitChars.put("ẍ", "x");
+ translitChars.put("ủ", "u");
+ translitChars.put("ỉ", "i");
+ translitChars.put("ᴚ", "r");
+ translitChars.put("ś", "s");
+ translitChars.put("ꝋ", "o");
+ translitChars.put("ỹ", "y");
+ translitChars.put("ṡ", "s");
+ translitChars.put("nj", "nj");
+ translitChars.put("ȁ", "a");
+ translitChars.put("ẗ", "t");
+ translitChars.put("ĺ", "l");
+ translitChars.put("ž", "z");
+ translitChars.put("ᵺ", "th");
+ translitChars.put("ƌ", "d");
+ translitChars.put("ș", "s");
+ translitChars.put("š", "s");
+ translitChars.put("ᶙ", "u");
+ translitChars.put("ẽ", "e");
+ translitChars.put("ẜ", "s");
+ translitChars.put("ɇ", "e");
+ translitChars.put("ṷ", "u");
+ translitChars.put("ố", "o");
+ translitChars.put("ȿ", "s");
+ translitChars.put("ᴠ", "v");
+ translitChars.put("ꝭ", "is");
+ translitChars.put("ᴏ", "o");
+ translitChars.put("ɛ", "e");
+ translitChars.put("ǻ", "a");
+ translitChars.put("ffl", "ffl");
+ translitChars.put("ⱺ", "o");
+ translitChars.put("ȋ", "i");
+ translitChars.put("ᵫ", "ue");
+ translitChars.put("ȡ", "d");
+ translitChars.put("ⱬ", "z");
+ translitChars.put("ẁ", "w");
+ translitChars.put("ᶏ", "a");
+ translitChars.put("ꞇ", "t");
+ translitChars.put("ğ", "g");
+ translitChars.put("ɳ", "n");
+ translitChars.put("ʛ", "g");
+ translitChars.put("ᴜ", "u");
+ translitChars.put("ф", "f");
+ translitChars.put("ẩ", "a");
+ translitChars.put("ṅ", "n");
+ translitChars.put("ɨ", "i");
+ translitChars.put("ᴙ", "r");
+ translitChars.put("ǎ", "a");
+ translitChars.put("ſ", "s");
+ translitChars.put("у", "u");
+ translitChars.put("ȫ", "o");
+ translitChars.put("ɿ", "r");
+ translitChars.put("ƭ", "t");
+ translitChars.put("ḯ", "i");
+ translitChars.put("ǽ", "ae");
+ translitChars.put("ⱱ", "v");
+ translitChars.put("ɶ", "oe");
+ translitChars.put("ṃ", "m");
+ translitChars.put("ż", "z");
+ translitChars.put("ĕ", "e");
+ translitChars.put("ꜻ", "av");
+ translitChars.put("ở", "o");
+ translitChars.put("ễ", "e");
+ translitChars.put("ɬ", "l");
+ translitChars.put("ị", "i");
+ translitChars.put("ᵭ", "d");
+ translitChars.put("st", "st");
+ translitChars.put("ḷ", "l");
+ translitChars.put("ŕ", "r");
+ translitChars.put("ᴕ", "ou");
+ translitChars.put("ʈ", "t");
+ translitChars.put("ā", "a");
+ translitChars.put("э", "e");
+ translitChars.put("ḙ", "e");
+ translitChars.put("ᴑ", "o");
+ translitChars.put("ç", "c");
+ translitChars.put("ᶊ", "s");
+ translitChars.put("ặ", "a");
+ translitChars.put("ų", "u");
+ translitChars.put("ả", "a");
+ translitChars.put("ǥ", "g");
+ translitChars.put("р", "r");
+ translitChars.put("ꝁ", "k");
+ translitChars.put("ẕ", "z");
+ translitChars.put("ŝ", "s");
+ translitChars.put("ḕ", "e");
+ translitChars.put("ɠ", "g");
+ translitChars.put("ꝉ", "l");
+ translitChars.put("ꝼ", "f");
+ translitChars.put("ᶍ", "x");
+ translitChars.put("х", "h");
+ translitChars.put("ǒ", "o");
+ translitChars.put("ę", "e");
+ translitChars.put("ổ", "o");
+ translitChars.put("ƫ", "t");
+ translitChars.put("ǫ", "o");
+ translitChars.put("i̇", "i");
+ translitChars.put("ṇ", "n");
+ translitChars.put("ć", "c");
+ translitChars.put("ᵷ", "g");
+ translitChars.put("ẅ", "w");
+ translitChars.put("ḑ", "d");
+ translitChars.put("ḹ", "l");
+ translitChars.put("ч", "ch");
+ translitChars.put("œ", "oe");
+ translitChars.put("ᵳ", "r");
+ translitChars.put("ļ", "l");
+ translitChars.put("ȑ", "r");
+ translitChars.put("ȭ", "o");
+ translitChars.put("ᵰ", "n");
+ translitChars.put("ᴁ", "ae");
+ translitChars.put("ŀ", "l");
+ translitChars.put("ä", "a");
+ translitChars.put("ƥ", "p");
+ translitChars.put("ỏ", "o");
+ translitChars.put("į", "i");
+ translitChars.put("ȓ", "r");
+ translitChars.put("dž", "dz");
+ translitChars.put("ḡ", "g");
+ translitChars.put("ṻ", "u");
+ translitChars.put("ō", "o");
+ translitChars.put("ľ", "l");
+ translitChars.put("ẃ", "w");
+ translitChars.put("ț", "t");
+ translitChars.put("ń", "n");
+ translitChars.put("ɍ", "r");
+ translitChars.put("ȃ", "a");
+ translitChars.put("ü", "u");
+ translitChars.put("ꞁ", "l");
+ translitChars.put("ᴐ", "o");
+ translitChars.put("ớ", "o");
+ translitChars.put("ᴃ", "b");
+ translitChars.put("ɹ", "r");
+ translitChars.put("ᵲ", "r");
+ translitChars.put("ʏ", "y");
+ translitChars.put("ᵮ", "f");
+ translitChars.put("ⱨ", "h");
+ translitChars.put("ŏ", "o");
+ translitChars.put("ú", "u");
+ translitChars.put("ṛ", "r");
+ translitChars.put("ʮ", "h");
+ translitChars.put("ó", "o");
+ translitChars.put("ů", "u");
+ translitChars.put("ỡ", "o");
+ translitChars.put("ṕ", "p");
+ translitChars.put("ᶖ", "i");
+ translitChars.put("ự", "u");
+ translitChars.put("ã", "a");
+ translitChars.put("ᵢ", "i");
+ translitChars.put("ṱ", "t");
+ translitChars.put("ể", "e");
+ translitChars.put("ử", "u");
+ translitChars.put("í", "i");
+ translitChars.put("ɔ", "o");
+ translitChars.put("с", "s");
+ translitChars.put("й", "i");
+ translitChars.put("ɺ", "r");
+ translitChars.put("ɢ", "g");
+ translitChars.put("ř", "r");
+ translitChars.put("ẖ", "h");
+ translitChars.put("ű", "u");
+ translitChars.put("ȍ", "o");
+ translitChars.put("ш", "sh");
+ translitChars.put("ḻ", "l");
+ translitChars.put("ḣ", "h");
+ translitChars.put("ȶ", "t");
+ translitChars.put("ņ", "n");
+ translitChars.put("ᶒ", "e");
+ translitChars.put("ì", "i");
+ translitChars.put("ẉ", "w");
+ translitChars.put("б", "b");
+ translitChars.put("ē", "e");
+ translitChars.put("ᴇ", "e");
+ translitChars.put("ł", "l");
+ translitChars.put("ộ", "o");
+ translitChars.put("ɭ", "l");
+ translitChars.put("ẏ", "y");
+ translitChars.put("ᴊ", "j");
+ translitChars.put("ḱ", "k");
+ translitChars.put("ṿ", "v");
+ translitChars.put("ȩ", "e");
+ translitChars.put("â", "a");
+ translitChars.put("ş", "s");
+ translitChars.put("ŗ", "r");
+ translitChars.put("ʋ", "v");
+ translitChars.put("ₐ", "a");
+ translitChars.put("ↄ", "c");
+ translitChars.put("ᶓ", "e");
+ translitChars.put("ɰ", "m");
+ translitChars.put("е", "e");
+ translitChars.put("ᴡ", "w");
+ translitChars.put("ȏ", "o");
+ translitChars.put("č", "c");
+ translitChars.put("ǵ", "g");
+ translitChars.put("ĉ", "c");
+ translitChars.put("ю", "yu");
+ translitChars.put("ᶗ", "o");
+ translitChars.put("ꝃ", "k");
+ translitChars.put("ꝙ", "q");
+ translitChars.put("г", "g");
+ translitChars.put("ṑ", "o");
+ translitChars.put("ꜱ", "s");
+ translitChars.put("ṓ", "o");
+ translitChars.put("ȟ", "h");
+ translitChars.put("ő", "o");
+ translitChars.put("ꜩ", "tz");
+ translitChars.put("ẻ", "e");
+ translitChars.put("о", "o");
+ }
+ StringBuilder dst = new StringBuilder(src.length());
+ int len = src.length();
+ for (int a = 0; a < len; a++) {
+ String ch = src.substring(a, a + 1);
+ String tch = translitChars.get(ch);
+ if (tch != null) {
+ dst.append(tch);
+ } else {
+ dst.append(ch);
+ }
+ }
+ return dst.toString();
+ }
abstract public static class PluralRules {
abstract int quantityForNumber(int n);
diff --git a/TMessagesProj/src/main/java/org/telegram/android/LruCache.java b/TMessagesProj/src/main/java/org/telegram/android/LruCache.java
index 7455bbce..3ed7d3c6 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/LruCache.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/LruCache.java
@@ -14,7 +14,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
-import java.util.Map;
/**
* Static library version of {@link android.util.LruCache}. Used to write apps
@@ -41,8 +40,8 @@ public class LruCache {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
- this.map = new LinkedHashMap(0, 0.75f, true);
- this.mapFilters = new LinkedHashMap>();
+ this.map = new LinkedHashMap<>(0, 0.75f, true);
+ this.mapFilters = new LinkedHashMap<>();
}
/**
@@ -69,7 +68,7 @@ public class LruCache {
public ArrayList getFilterKeys(String key) {
ArrayList arr = mapFilters.get(key);
if (arr != null) {
- return new ArrayList(arr);
+ return new ArrayList<>(arr);
}
return null;
}
@@ -98,14 +97,17 @@ public class LruCache {
if (args.length > 1) {
ArrayList arr = mapFilters.get(args[0]);
if (arr == null) {
- arr = new ArrayList();
+ arr = new ArrayList<>();
mapFilters.put(args[0], arr);
}
- arr.add(args[1]);
+ if (!arr.contains(args[1])) {
+ arr.add(args[1]);
+ }
}
if (previous != null) {
entryRemoved(false, key, previous, value);
+ ImageLoader.getInstance().callGC();
}
trimToSize(maxSize, key);
@@ -137,15 +139,16 @@ public class LruCache {
if (args.length > 1) {
ArrayList arr = mapFilters.get(args[0]);
if (arr != null) {
- arr.remove(key);
+ arr.remove(args[1]);
if (arr.isEmpty()) {
- mapFilters.remove(args[1]);
+ mapFilters.remove(args[0]);
}
}
}
entryRemoved(true, key, value, null);
}
+ ImageLoader.getInstance().callGC();
}
}
@@ -172,14 +175,15 @@ public class LruCache {
if (args.length > 1) {
ArrayList arr = mapFilters.get(args[0]);
if (arr != null) {
- arr.remove(key);
+ arr.remove(args[1]);
if (arr.isEmpty()) {
- mapFilters.remove(args[1]);
+ mapFilters.remove(args[0]);
}
}
}
entryRemoved(false, key, previous, null);
+ ImageLoader.getInstance().callGC();
}
return previous;
diff --git a/TMessagesProj/src/main/java/org/telegram/android/MediaController.java b/TMessagesProj/src/main/java/org/telegram/android/MediaController.java
index 2dc688aa..efa53b09 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/MediaController.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/MediaController.java
@@ -122,6 +122,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
public String bucketName;
public PhotoEntry coverPhoto;
public ArrayList photos = new ArrayList<>();
+ public HashMap photosByIds = new HashMap<>();
public AlbumEntry(int bucketId, String bucketName, PhotoEntry coverPhoto) {
this.bucketId = bucketId;
@@ -131,6 +132,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
public void addPhoto(PhotoEntry photoEntry) {
photos.add(photoEntry);
+ photosByIds.put(photoEntry.imageId, photoEntry);
}
}
@@ -140,6 +142,8 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
public long dateTaken;
public String path;
public int orientation;
+ public String thumbPath;
+ public String imagePath;
public PhotoEntry(int bucketId, int imageId, long dateTaken, String path, int orientation) {
this.bucketId = bucketId;
@@ -1095,11 +1099,11 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
@Override
public void onSensorChanged(SensorEvent event) {
- if (audioTrackPlayer == null && audioPlayer == null || isPaused || (useFrontSpeaker == (event.values[0] == 0))) {
+ if (proximitySensor != null && audioTrackPlayer == null && audioPlayer == null || isPaused || (useFrontSpeaker == (event.values[0] < proximitySensor.getMaximumRange() / 10))) {
return;
}
ignoreProximity = true;
- useFrontSpeaker = event.values[0] == 0;
+ useFrontSpeaker = event.values[0] < proximitySensor.getMaximumRange() / 10;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioRouteChanged, useFrontSpeaker);
MessageObject currentMessageObject = playingMessageObject;
float progress = playingMessageObject.audioProgress;
@@ -2450,11 +2454,15 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
decoder.start();
final int TIMEOUT_USEC = 2500;
- ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
- ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+ ByteBuffer[] decoderInputBuffers = null;
+ ByteBuffer[] encoderOutputBuffers = null;
ByteBuffer[] encoderInputBuffers = null;
- if (Build.VERSION.SDK_INT < 18) {
- encoderInputBuffers = encoder.getInputBuffers();
+ if (Build.VERSION.SDK_INT < 21) {
+ decoderInputBuffers = decoder.getInputBuffers();
+ encoderOutputBuffers = encoder.getOutputBuffers();
+ if (Build.VERSION.SDK_INT < 18) {
+ encoderInputBuffers = encoder.getInputBuffers();
+ }
}
checkConversionCanceled();
@@ -2467,7 +2475,12 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
if (index == videoIndex) {
int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
if (inputBufIndex >= 0) {
- ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+ ByteBuffer inputBuf = null;
+ if (Build.VERSION.SDK_INT < 21) {
+ inputBuf = decoderInputBuffers[inputBufIndex];
+ } else {
+ inputBuf = decoder.getInputBuffer(inputBufIndex);
+ }
int chunkSize = extractor.readSampleData(inputBuf, 0);
if (chunkSize < 0) {
decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
@@ -2497,7 +2510,9 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
encoderOutputAvailable = false;
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- encoderOutputBuffers = encoder.getOutputBuffers();
+ if (Build.VERSION.SDK_INT < 21) {
+ encoderOutputBuffers = encoder.getOutputBuffers();
+ }
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = encoder.getOutputFormat();
if (videoTrackIndex == -5) {
@@ -2506,7 +2521,12 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
} else if (encoderStatus < 0) {
throw new RuntimeException("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
} else {
- ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+ ByteBuffer encodedData = null;
+ if (Build.VERSION.SDK_INT < 21) {
+ encodedData = encoderOutputBuffers[encoderStatus];
+ } else {
+ encodedData = encoder.getOutputBuffer(encoderStatus);
+ }
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null");
}
diff --git a/TMessagesProj/src/main/java/org/telegram/android/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/android/MessageObject.java
index 3274ccf3..1c6c0dd4 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/MessageObject.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/MessageObject.java
@@ -8,7 +8,6 @@
package org.telegram.android;
-import android.graphics.Bitmap;
import android.graphics.Paint;
import android.text.Layout;
import android.text.Spannable;
@@ -42,12 +41,11 @@ public class MessageObject {
public CharSequence messageText;
public int type;
public int contentType;
- public ArrayList photoThumbs;
- public Bitmap imagePreview;
public String dateKey;
public boolean deleted = false;
public float audioProgress;
public int audioProgressSec;
+ public ArrayList photoThumbs;
private static TextPaint textPaint;
public int lastLineWidth;
@@ -66,11 +64,7 @@ public class MessageObject {
public ArrayList textLayoutBlocks;
- public MessageObject(TLRPC.Message message, AbstractMap users) {
- this(message, users, 1);
- }
-
- public MessageObject(TLRPC.Message message, AbstractMap users, int preview) {
+ public MessageObject(TLRPC.Message message, AbstractMap users, boolean generateLayout) {
if (textPaint == null) {
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(0xff000000);
@@ -319,6 +313,9 @@ public class MessageObject {
type = 8;
} else if (message.media.document.mime_type.equals("image/webp") && isSticker()) {
type = 13;
+ if (messageOwner.media.document.thumb != null && messageOwner.media.document.thumb.location != null) {
+ messageOwner.media.document.thumb.location.ext = "webp";
+ }
} else {
type = 9;
}
@@ -355,37 +352,25 @@ public class MessageObject {
int dateMonth = rightNow.get(Calendar.MONTH);
dateKey = String.format("%d_%02d_%02d", dateYear, dateMonth, dateDay);
- if (preview != 0) {
+ if (generateLayout) {
generateLayout();
}
- generateThumbs(false, preview);
+ generateThumbs(false);
}
- public CharSequence replaceWithLink(CharSequence source, String param, TLRPC.User user) {
- String name = ContactsController.formatName(user.first_name, user.last_name);
- int start = TextUtils.indexOf(source, param);
- URLSpanNoUnderline span = new URLSpanNoUnderline("" + user.id);
- SpannableStringBuilder builder = new SpannableStringBuilder(TextUtils.replace(source, new String[]{param}, new String[]{name}));
- builder.setSpan(span, start, start + name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- return builder;
- }
-
- public void generateThumbs(boolean update, int preview) {
+ public void generateThumbs(boolean update) {
if (messageOwner instanceof TLRPC.TL_messageService) {
if (messageOwner.action instanceof TLRPC.TL_messageActionChatEditPhoto) {
if (!update) {
- photoThumbs = new ArrayList<>();
- for (TLRPC.PhotoSize size : messageOwner.action.photo.sizes) {
- photoThumbs.add(new PhotoObject(size, preview, isSecretMedia()));
- }
+ photoThumbs = new ArrayList<>(messageOwner.action.photo.sizes);
} else if (photoThumbs != null && !photoThumbs.isEmpty()) {
- for (PhotoObject photoObject : photoThumbs) {
+ for (TLRPC.PhotoSize photoObject : photoThumbs) {
for (TLRPC.PhotoSize size : messageOwner.action.photo.sizes) {
if (size instanceof TLRPC.TL_photoSizeEmpty) {
continue;
}
- if (size.type.equals(photoObject.photoOwner.type)) {
- photoObject.photoOwner.location = size.location;
+ if (size.type.equals(photoObject.type)) {
+ photoObject.location = size.location;
break;
}
}
@@ -395,22 +380,15 @@ public class MessageObject {
} else if (messageOwner.media != null && !(messageOwner.media instanceof TLRPC.TL_messageMediaEmpty)) {
if (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) {
if (!update) {
- photoThumbs = new ArrayList<>();
- for (TLRPC.PhotoSize size : messageOwner.media.photo.sizes) {
- PhotoObject obj = new PhotoObject(size, preview, isSecretMedia());
- photoThumbs.add(obj);
- if (imagePreview == null && obj.image != null) {
- imagePreview = obj.image;
- }
- }
+ photoThumbs = new ArrayList<>(messageOwner.media.photo.sizes);
} else if (photoThumbs != null && !photoThumbs.isEmpty()) {
- for (PhotoObject photoObject : photoThumbs) {
+ for (TLRPC.PhotoSize photoObject : photoThumbs) {
for (TLRPC.PhotoSize size : messageOwner.media.photo.sizes) {
if (size instanceof TLRPC.TL_photoSizeEmpty) {
continue;
}
- if (size.type.equals(photoObject.photoOwner.type)) {
- photoObject.photoOwner.location = size.location;
+ if (size.type.equals(photoObject.type)) {
+ photoObject.location = size.location;
break;
}
}
@@ -419,36 +397,34 @@ public class MessageObject {
} else if (messageOwner.media instanceof TLRPC.TL_messageMediaVideo) {
if (!update) {
photoThumbs = new ArrayList<>();
- PhotoObject obj = new PhotoObject(messageOwner.media.video.thumb, preview, isSecretMedia());
- photoThumbs.add(obj);
- if (imagePreview == null && obj.image != null) {
- imagePreview = obj.image;
- }
+ photoThumbs.add(messageOwner.media.video.thumb);
} else if (photoThumbs != null && !photoThumbs.isEmpty() && messageOwner.media.video.thumb != null) {
- PhotoObject photoObject = photoThumbs.get(0);
- photoObject.photoOwner.location = messageOwner.media.video.thumb.location;
+ TLRPC.PhotoSize photoObject = photoThumbs.get(0);
+ photoObject.location = messageOwner.media.video.thumb.location;
}
} else if (messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
if (!(messageOwner.media.document.thumb instanceof TLRPC.TL_photoSizeEmpty)) {
if (!update) {
photoThumbs = new ArrayList<>();
- if (type == 13) {
- messageOwner.media.document.thumb.location.ext = "webp";
- }
- PhotoObject obj = new PhotoObject(messageOwner.media.document.thumb, preview, isSecretMedia());
- photoThumbs.add(obj);
- if (imagePreview == null && obj.image != null) {
- imagePreview = obj.image;
- }
+ photoThumbs.add(messageOwner.media.document.thumb);
} else if (photoThumbs != null && !photoThumbs.isEmpty() && messageOwner.media.document.thumb != null) {
- PhotoObject photoObject = photoThumbs.get(0);
- photoObject.photoOwner.location = messageOwner.media.document.thumb.location;
+ TLRPC.PhotoSize photoObject = photoThumbs.get(0);
+ photoObject.location = messageOwner.media.document.thumb.location;
}
}
}
}
}
+ public CharSequence replaceWithLink(CharSequence source, String param, TLRPC.User user) {
+ String name = ContactsController.formatName(user.first_name, user.last_name);
+ int start = TextUtils.indexOf(source, param);
+ URLSpanNoUnderline span = new URLSpanNoUnderline("" + user.id);
+ SpannableStringBuilder builder = new SpannableStringBuilder(TextUtils.replace(source, new String[]{param}, new String[]{name}));
+ builder.setSpan(span, start, start + name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return builder;
+ }
+
public String getFileName() {
if (messageOwner.media instanceof TLRPC.TL_messageMediaVideo) {
return FileLoader.getAttachFileName(messageOwner.media.video);
@@ -468,6 +444,19 @@ public class MessageObject {
return "";
}
+ public int getFileType() {
+ if (messageOwner.media instanceof TLRPC.TL_messageMediaVideo) {
+ return FileLoader.MEDIA_DIR_VIDEO;
+ } else if (messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
+ return FileLoader.MEDIA_DIR_DOCUMENT;
+ } else if (messageOwner.media instanceof TLRPC.TL_messageMediaAudio) {
+ return FileLoader.MEDIA_DIR_AUDIO;
+ } else if (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) {
+ return FileLoader.MEDIA_DIR_IMAGE;
+ }
+ return FileLoader.MEDIA_DIR_CACHE;
+ }
+
private boolean containsUrls(CharSequence message) {
if (message == null || message.length() < 3 || message.length() > 1024 * 20) {
return false;
@@ -787,9 +776,9 @@ public class MessageObject {
return "";
}
- public boolean isSticker() {
- if (messageOwner.media != null && messageOwner.media.document != null) {
- for (TLRPC.DocumentAttribute attribute : messageOwner.media.document.attributes) {
+ public static boolean isStickerMessage(TLRPC.Message message) {
+ if (message.media != null && message.media.document != null) {
+ for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
return true;
}
@@ -797,4 +786,8 @@ public class MessageObject {
}
return false;
}
+
+ public boolean isSticker() {
+ return isStickerMessage(messageOwner);
+ }
}
diff --git a/TMessagesProj/src/main/java/org/telegram/android/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/android/MessagesController.java
index 1c673e6b..71bd216e 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/MessagesController.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/MessagesController.java
@@ -314,9 +314,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter
sendingTypings.clear();
loadingFullUsers.clear();
loadedFullUsers.clear();
- loadingFullUsers.clear();
- loadedFullUsers.clear();
reloadingMessages.clear();
+ loadingFullChats.clear();
+ loadedFullChats.clear();
updatesStartWaitTime = 0;
currentDeletingTaskTime = 0;
@@ -568,6 +568,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
public void run(TLObject response, TLRPC.TL_error error) {
if (error == null) {
TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response;
+ ImageLoader.saveMessagesThumbs(messagesRes.messages);
MessagesStorage.getInstance().putMessages(messagesRes, dialog_id);
final ArrayList objects = new ArrayList<>();
@@ -578,7 +579,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
for (TLRPC.User u : messagesRes.users) {
usersLocal.put(u.id, u);
}
- objects.add(new MessageObject(message, usersLocal, 2));
+ objects.add(new MessageObject(message, usersLocal, true));
}
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
@@ -898,151 +899,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter
});
}
- public void processLoadedMedia(final TLRPC.messages_Messages res, final long uid, int offset, int count, int max_id, final boolean fromCache, final int classGuid) {
- int lower_part = (int)uid;
- if (fromCache && res.messages.isEmpty() && lower_part != 0) {
- loadMedia(uid, offset, count, max_id, false, classGuid);
- } else {
- if (!fromCache) {
- MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true);
- MessagesStorage.getInstance().putMedia(uid, res.messages);
- }
-
- final HashMap usersLocal = new HashMap<>();
- for (TLRPC.User u : res.users) {
- usersLocal.put(u.id, u);
- }
- final ArrayList objects = new ArrayList<>();
- for (TLRPC.Message message : res.messages) {
- objects.add(new MessageObject(message, usersLocal));
- }
-
- AndroidUtilities.runOnUIThread(new Runnable() {
- @Override
- public void run() {
- int totalCount;
- if (res instanceof TLRPC.TL_messages_messagesSlice) {
- totalCount = res.count;
- } else {
- totalCount = res.messages.size();
- }
- putUsers(res.users, fromCache);
- for (TLRPC.Chat chat : res.chats) {
- putChat(chat, fromCache);
- }
- NotificationCenter.getInstance().postNotificationName(NotificationCenter.mediaDidLoaded, uid, totalCount, objects, fromCache, classGuid);
- }
- });
- }
- }
-
- public void loadMedia(final long uid, final int offset, final int count, final int max_id, final boolean fromCache, final int classGuid) {
- int lower_part = (int)uid;
- if (fromCache || lower_part == 0) {
- MessagesStorage.getInstance().loadMedia(uid, offset, count, max_id, classGuid);
- } else {
- TLRPC.TL_messages_search req = new TLRPC.TL_messages_search();
- req.offset = offset;
- req.limit = count;
- req.max_id = max_id;
- req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo();
- req.q = "";
- if (uid < 0) {
- req.peer = new TLRPC.TL_inputPeerChat();
- req.peer.chat_id = -lower_part;
- } else {
- TLRPC.User user = getUser(lower_part);
- if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) {
- req.peer = new TLRPC.TL_inputPeerForeign();
- req.peer.access_hash = user.access_hash;
- } else {
- req.peer = new TLRPC.TL_inputPeerContact();
- }
- req.peer.user_id = lower_part;
- }
- long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
- @Override
- public void run(TLObject response, TLRPC.TL_error error) {
- if (error == null) {
- final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response;
- processLoadedMedia(res, uid, offset, count, max_id, false, classGuid);
- }
- }
- });
- ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
- }
- }
-
- public void processLoadedMediaCount(final int count, final long uid, final int classGuid, final boolean fromCache) {
- AndroidUtilities.runOnUIThread(new Runnable() {
- @Override
- public void run() {
- int lower_part = (int)uid;
- if (fromCache && count == -1 && lower_part != 0) {
- getMediaCount(uid, classGuid, false);
- } else {
- if (!fromCache) {
- MessagesStorage.getInstance().putMediaCount(uid, count);
- }
- NotificationCenter.getInstance().postNotificationName(NotificationCenter.mediaCountDidLoaded, uid, (fromCache && count == -1 ? 0 : count), fromCache);
- }
- }
- });
- }
-
- public void getMediaCount(final long uid, final int classGuid, boolean fromCache) {
- int lower_part = (int)uid;
- if (fromCache || lower_part == 0) {
- MessagesStorage.getInstance().getMediaCount(uid, classGuid);
- } else {
- TLRPC.TL_messages_search req = new TLRPC.TL_messages_search();
- req.offset = 0;
- req.limit = 1;
- req.max_id = 0;
- req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo();
- req.q = "";
- if (uid < 0) {
- req.peer = new TLRPC.TL_inputPeerChat();
- req.peer.chat_id = -lower_part;
- } else {
- TLRPC.User user = getUser(lower_part);
- if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) {
- req.peer = new TLRPC.TL_inputPeerForeign();
- req.peer.access_hash = user.access_hash;
- } else {
- req.peer = new TLRPC.TL_inputPeerContact();
- }
- req.peer.user_id = lower_part;
- }
- long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
- @Override
- public void run(TLObject response, TLRPC.TL_error error) {
- if (error == null) {
- final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response;
- MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true);
-
- AndroidUtilities.runOnUIThread(new Runnable() {
- @Override
- public void run() {
- putUsers(res.users, false);
- for (TLRPC.Chat chat : res.chats) {
- putChat(chat, false);
- }
- }
- });
-
- if (res instanceof TLRPC.TL_messages_messagesSlice) {
- processLoadedMediaCount(res.count, uid, classGuid, false);
- } else {
- processLoadedMediaCount(res.messages.size(), uid, classGuid, false);
- }
- }
- }
- });
- ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
- }
- }
-
public void uploadAndApplyUserAvatar(TLRPC.PhotoSize bigPhoto) {
if (bigPhoto != null) {
uploadingAvatar = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + bigPhoto.location.volume_id + "_" + bigPhoto.location.local_id + ".jpg";
@@ -1470,6 +1326,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter
public void run() {
int lower_id = (int)dialog_id;
int high_id = (int)(dialog_id >> 32);
+ if (!isCache) {
+ ImageLoader.saveMessagesThumbs(messagesRes.messages);
+ }
if (!isCache && allowCache) {
MessagesStorage.getInstance().putMessages(messagesRes, dialog_id);
}
@@ -1490,7 +1349,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
ArrayList messagesToReload = null;
for (TLRPC.Message message : messagesRes.messages) {
message.dialog_id = dialog_id;
- objects.add(new MessageObject(message, usersLocal, 2));
+ objects.add(new MessageObject(message, usersLocal, true));
if (isCache && message.media instanceof TLRPC.TL_messageMediaUnsupported) {
if (message.media.bytes.length == 0 || message.media.bytes.length == 1 && message.media.bytes[0] < TLRPC.LAYER) {
if (messagesToReload == null) {
@@ -1553,7 +1412,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter
dialog_id = -dialog.peer.chat_id;
}
if (dialog.notify_settings.mute_until != 0) {
- editor.putInt("notify2_" + dialog_id, 2);
+ if (dialog.notify_settings.mute_until > ConnectionsManager.getInstance().getCurrentTime() + 60 * 60 * 24 * 365) {
+ editor.putInt("notify2_" + dialog_id, 2);
+ dialog.notify_settings.mute_until = Integer.MAX_VALUE;
+ } else {
+ editor.putInt("notify2_" + dialog_id, 3);
+ editor.putInt("notifyuntil_" + dialog_id, dialog.notify_settings.mute_until);
+ }
}
}
}
@@ -1592,7 +1457,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
for (TLRPC.Message m : dialogsRes.messages) {
- new_dialogMessage.put(m.id, new MessageObject(m, usersLocal, 0));
+ new_dialogMessage.put(m.id, new MessageObject(m, usersLocal, false));
}
for (TLRPC.TL_dialog d : dialogsRes.dialogs) {
if (d.last_message_date == 0) {
@@ -1701,6 +1566,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
int new_totalDialogsCount;
if (!isCache) {
+ ImageLoader.saveMessagesThumbs(dialogsRes.messages);
MessagesStorage.getInstance().putDialogs(dialogsRes);
}
@@ -1716,7 +1582,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
for (TLRPC.Message m : dialogsRes.messages) {
- new_dialogMessage.put(m.id, new MessageObject(m, usersLocal, 0));
+ new_dialogMessage.put(m.id, new MessageObject(m, usersLocal, false));
}
for (TLRPC.TL_dialog d : dialogsRes.dialogs) {
if (d.last_message_date == 0) {
@@ -1990,7 +1856,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
newMsg.date = ConnectionsManager.getInstance().getCurrentTime();
newMsg.random_id = 0;
UserConfig.saveConfig(false);
- MessageObject newMsgObj = new MessageObject(newMsg, users);
+ MessageObject newMsgObj = new MessageObject(newMsg, users, true);
newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENT;
ArrayList objArr = new ArrayList<>();
@@ -2034,7 +1900,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
putUsers(res.users, false);
putChats(res.chats, false);
final ArrayList messagesObj = new ArrayList<>();
- messagesObj.add(new MessageObject(res.message, users));
+ messagesObj.add(new MessageObject(res.message, users, true));
TLRPC.Chat chat = res.chats.get(0);
updateInterfaceWithMessages(-chat.id, messagesObj);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatDidCreated, chat.id);
@@ -2081,7 +1947,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
putUsers(res.users, false);
putChats(res.chats, false);
final ArrayList messagesObj = new ArrayList<>();
- messagesObj.add(new MessageObject(res.message, users));
+ messagesObj.add(new MessageObject(res.message, users, true));
TLRPC.Chat chat = res.chats.get(0);
updateInterfaceWithMessages(-chat.id, messagesObj);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_MEMBERS);
@@ -2163,7 +2029,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
putChats(res.chats, false);
if (user.id != UserConfig.getClientUserId()) {
final ArrayList messagesObj = new ArrayList<>();
- messagesObj.add(new MessageObject(res.message, users));
+ messagesObj.add(new MessageObject(res.message, users, true));
TLRPC.Chat chat = res.chats.get(0);
updateInterfaceWithMessages(-chat.id, messagesObj);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_MEMBERS);
@@ -2247,7 +2113,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
putUsers(res.users, false);
putChats(res.chats, false);
final ArrayList messagesObj = new ArrayList<>();
- messagesObj.add(new MessageObject(res.message, users));
+ messagesObj.add(new MessageObject(res.message, users, true));
TLRPC.Chat chat = res.chats.get(0);
updateInterfaceWithMessages(-chat.id, messagesObj);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
@@ -2291,13 +2157,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter
final TLRPC.messages_StatedMessage res = (TLRPC.messages_StatedMessage) response;
MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true);
+ final ArrayList messages = new ArrayList<>();
+ messages.add(res.message);
+ ImageLoader.saveMessagesThumbs(messages);
+
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
putUsers(res.users, false);
putChats(res.chats, false);
final ArrayList messagesObj = new ArrayList<>();
- messagesObj.add(new MessageObject(res.message, users));
+ messagesObj.add(new MessageObject(res.message, users, true));
TLRPC.Chat chat = res.chats.get(0);
updateInterfaceWithMessages(-chat.id, messagesObj);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
@@ -2305,8 +2175,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
});
- final ArrayList messages = new ArrayList<>();
- messages.add(res.message);
MessagesStorage.getInstance().putMessages(messages, true, true, false, 0);
processNewDifferenceParams(res.seq, res.pts, -1);
}
@@ -2616,9 +2484,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
}
+ ImageLoader.saveMessagesThumbs(res.new_messages);
+
final ArrayList pushMessages = new ArrayList<>();
for (TLRPC.Message message : res.new_messages) {
- MessageObject obj = new MessageObject(message, usersDict, 2);
+ MessageObject obj = new MessageObject(message, usersDict, true);
long dialog_id = obj.messageOwner.dialog_id;
if (dialog_id == 0) {
@@ -2763,7 +2633,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
message.media = new TLRPC.TL_messageMediaEmpty();
MessagesStorage.lastSeqValue = updates.seq;
MessagesStorage.lastPtsValue = updates.pts;
- final MessageObject obj = new MessageObject(message, null);
+ final MessageObject obj = new MessageObject(message, null, true);
final ArrayList objArr = new ArrayList<>();
objArr.add(obj);
ArrayList arr = new ArrayList<>();
@@ -2834,7 +2704,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
MessagesStorage.lastSeqValue = updates.seq;
MessagesStorage.lastPtsValue = updates.pts;
MessagesStorage.lastDateValue = updates.date;
- final MessageObject obj = new MessageObject(message, null);
+ final MessageObject obj = new MessageObject(message, null, true);
final ArrayList objArr = new ArrayList<>();
objArr.add(obj);
ArrayList arr = new ArrayList<>();
@@ -2976,6 +2846,29 @@ public class MessagesController implements NotificationCenter.NotificationCenter
MessagesStorage.getInstance().saveDiffParams(MessagesStorage.lastSeqValue, MessagesStorage.lastPtsValue, MessagesStorage.lastDateValue, MessagesStorage.lastQtsValue);
}
+ private boolean isNotifySettingsMuted(TLRPC.PeerNotifySettings settings) {
+ return settings instanceof TLRPC.TL_peerNotifySettings && settings.mute_until > ConnectionsManager.getInstance().getCurrentTime();
+ }
+
+ public boolean isDialogMuted(long dialog_id) {
+ TLRPC.TL_dialog dialog = dialogs_dict.get(dialog_id);
+ if (dialog != null) {
+ return isNotifySettingsMuted(dialog.notify_settings);
+ } else {
+ SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
+ int mute_type = preferences.getInt("notify2_" + dialog_id, 0);
+ if (mute_type == 2) {
+ return true;
+ } else if (mute_type == 3) {
+ int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
+ if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
public boolean processUpdateArray(ArrayList updates, final ArrayList usersArr, final ArrayList chatsArr) {
if (updates.isEmpty()) {
return true;
@@ -3043,7 +2936,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
}
messagesArr.add(upd.message);
- MessageObject obj = new MessageObject(upd.message, usersDict, 2);
+ ImageLoader.saveMessageThumbs(upd.message);
+ MessageObject obj = new MessageObject(upd.message, usersDict, true);
if (obj.type == 11) {
interfaceUpdateMask |= UPDATE_MASK_CHAT_AVATAR;
} else if (obj.type == 10) {
@@ -3134,7 +3028,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
newMessage.dialog_id = update.user_id;
messagesArr.add(newMessage);
- MessageObject obj = new MessageObject(newMessage, usersDict);
+ MessageObject obj = new MessageObject(newMessage, usersDict, true);
ArrayList arr = messages.get(newMessage.dialog_id);
if (arr == null) {
arr = new ArrayList<>();
@@ -3178,7 +3072,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
newMessage.dialog_id = 777000;
messagesArr.add(newMessage);
- MessageObject obj = new MessageObject(newMessage, usersDict);
+ MessageObject obj = new MessageObject(newMessage, usersDict, true);
ArrayList arr = messages.get(newMessage.dialog_id);
if (arr == null) {
arr = new ArrayList<>();
@@ -3200,8 +3094,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter
messages.put(uid, arr);
}
for (TLRPC.Message message : decryptedMessages) {
+ ImageLoader.saveMessageThumbs(message);
messagesArr.add(message);
- MessageObject obj = new MessageObject(message, usersDict, 2);
+ MessageObject obj = new MessageObject(message, usersDict, true);
arr.add(obj);
pushMessages.add(obj);
}
@@ -3287,7 +3182,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
newMessage.message = ((TLRPC.TL_updateServiceNotification)update).message;
messagesArr.add(newMessage);
- MessageObject obj = new MessageObject(newMessage, usersDict);
+ MessageObject obj = new MessageObject(newMessage, usersDict, true);
ArrayList arr = messages.get(newMessage.dialog_id);
if (arr == null) {
arr = new ArrayList<>();
@@ -3370,6 +3265,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
toDbUser.status = update.status;
dbUsersStatus.add(toDbUser);
+ if (update.user_id == UserConfig.getClientUserId()) {
+ NotificationsController.getInstance().setLastOnlineFromOtherDevice(update.status.expires);
+ }
} else if (update instanceof TLRPC.TL_updateUserName) {
if (currentUser != null) {
currentUser.first_name = update.first_name;
@@ -3405,15 +3303,38 @@ public class MessagesController implements NotificationCenter.NotificationCenter
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
editor = preferences.edit();
}
- int dialog_id = update.peer.peer.user_id;
+ long dialog_id = update.peer.peer.user_id;
if (dialog_id == 0) {
dialog_id = -update.peer.peer.chat_id;
}
- if (update.notify_settings.mute_until != 0) {
- editor.putInt("notify2_" + dialog_id, 2);
- } else {
- editor.remove("notify2_" + dialog_id);
+ TLRPC.TL_dialog dialog = dialogs_dict.get(dialog_id);
+ if (dialog != null) {
+ dialog.notify_settings = update.notify_settings;
}
+ if (update.notify_settings.mute_until > ConnectionsManager.getInstance().getCurrentTime()) {
+ int until = 0;
+ if (update.notify_settings.mute_until > ConnectionsManager.getInstance().getCurrentTime() + 60 * 60 * 24 * 365) {
+ editor.putInt("notify2_" + dialog_id, 2);
+ if (dialog != null) {
+ dialog.notify_settings.mute_until = Integer.MAX_VALUE;
+ }
+ } else {
+ until = update.notify_settings.mute_until;
+ editor.putInt("notify2_" + dialog_id, 3);
+ editor.putInt("notifyuntil_" + dialog_id, update.notify_settings.mute_until);
+ if (dialog != null) {
+ dialog.notify_settings.mute_until = until;
+ }
+ }
+ MessagesStorage.getInstance().setDialogFlags(dialog_id, ((long)until << 32) | 1);
+ } else {
+ if (dialog != null) {
+ dialog.notify_settings.mute_until = 0;
+ }
+ editor.remove("notify2_" + dialog_id);
+ MessagesStorage.getInstance().setDialogFlags(dialog_id, 0);
+ }
+
}/* else if (update.peer instanceof TLRPC.TL_notifyChats) { disable global settings sync
if (editor == null) {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
diff --git a/TMessagesProj/src/main/java/org/telegram/android/MessagesStorage.java b/TMessagesProj/src/main/java/org/telegram/android/MessagesStorage.java
index 4b319175..87f0e198 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/MessagesStorage.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/MessagesStorage.java
@@ -17,6 +17,7 @@ import org.telegram.PhoneFormat.PhoneFormat;
import org.telegram.SQLite.SQLiteCursor;
import org.telegram.SQLite.SQLiteDatabase;
import org.telegram.SQLite.SQLitePreparedStatement;
+import org.telegram.android.query.SharedMediaQuery;
import org.telegram.messenger.BuffersStorage;
import org.telegram.messenger.ByteBufferDesc;
import org.telegram.messenger.ConnectionsManager;
@@ -109,8 +110,6 @@ public class MessagesStorage {
database.executeFast("CREATE TABLE chat_settings(uid INTEGER PRIMARY KEY, participants BLOB)").stepThis().dispose();
database.executeFast("CREATE TABLE contacts(uid INTEGER PRIMARY KEY, mutual INTEGER)").stepThis().dispose();
database.executeFast("CREATE TABLE pending_read(uid INTEGER PRIMARY KEY, max_id INTEGER)").stepThis().dispose();
- database.executeFast("CREATE TABLE media(mid INTEGER PRIMARY KEY, uid INTEGER, date INTEGER, data BLOB)").stepThis().dispose();
- database.executeFast("CREATE TABLE media_counts(uid INTEGER PRIMARY KEY, count INTEGER)").stepThis().dispose();
database.executeFast("CREATE TABLE wallpapers(uid INTEGER PRIMARY KEY, data BLOB)").stepThis().dispose();
database.executeFast("CREATE TABLE randoms(random_id INTEGER, mid INTEGER, PRIMARY KEY (random_id, mid))").stepThis().dispose();
database.executeFast("CREATE TABLE enc_tasks_v2(mid INTEGER PRIMARY KEY, date INTEGER)").stepThis().dispose();
@@ -123,9 +122,6 @@ public class MessagesStorage {
database.executeFast("CREATE TABLE messages_seq(mid INTEGER PRIMARY KEY, seq_in INTEGER, seq_out INTEGER);").stepThis().dispose();
database.executeFast("CREATE TABLE web_recent_v3(id TEXT, type INTEGER, image_url TEXT, thumb_url TEXT, local_url TEXT, width INTEGER, height INTEGER, size INTEGER, date INTEGER, PRIMARY KEY (id, type));").stepThis().dispose();
database.executeFast("CREATE TABLE stickers(id INTEGER PRIMARY KEY, data BLOB, date INTEGER);").stepThis().dispose();
- //database.executeFast("CREATE TABLE secret_holes(uid INTEGER, seq_in INTEGER, seq_out INTEGER, data BLOB, PRIMARY KEY (uid, seq_in, seq_out));").stepThis().dispose();
-
- //database.executeFast("CREATE TABLE attach_data(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose();
database.executeFast("CREATE TABLE user_contacts_v6(uid INTEGER PRIMARY KEY, fname TEXT, sname TEXT)").stepThis().dispose();
database.executeFast("CREATE TABLE user_phones_v6(uid INTEGER, phone TEXT, sphone TEXT, deleted INTEGER, PRIMARY KEY (uid, phone))").stepThis().dispose();
@@ -134,6 +130,8 @@ public class MessagesStorage {
//database.executeFast("CREATE TABLE messages_holes(uid INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, start));").stepThis().dispose();
//database.executeFast("CREATE INDEX IF NOT EXISTS type_uid_end_messages_holes ON messages_holes(uid, end);").stepThis().dispose();
+ //database.executeFast("CREATE TABLE secret_holes(uid INTEGER, seq_in INTEGER, seq_out INTEGER, data BLOB, PRIMARY KEY (uid, seq_in, seq_out));").stepThis().dispose();
+ //database.executeFast("CREATE TABLE attach_data(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose();
database.executeFast("CREATE INDEX IF NOT EXISTS type_date_idx_download_queue ON download_queue(type, date);").stepThis().dispose();
@@ -146,10 +144,6 @@ public class MessagesStorage {
database.executeFast("CREATE INDEX IF NOT EXISTS last_mid_idx_dialogs ON dialogs(last_mid);").stepThis().dispose();
database.executeFast("CREATE INDEX IF NOT EXISTS unread_count_idx_dialogs ON dialogs(unread_count);").stepThis().dispose();
- database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_idx_media ON media(uid, mid);").stepThis().dispose();
- database.executeFast("CREATE INDEX IF NOT EXISTS mid_idx_media ON media(mid);").stepThis().dispose();
- database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_media ON media(uid, date, mid);").stepThis().dispose();
-
database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_idx_messages ON messages(uid, mid);").stepThis().dispose();
database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_messages ON messages(uid, date, mid);").stepThis().dispose();
database.executeFast("CREATE INDEX IF NOT EXISTS mid_out_idx_messages ON messages(mid, out);").stepThis().dispose();
@@ -158,7 +152,17 @@ public class MessagesStorage {
database.executeFast("CREATE INDEX IF NOT EXISTS seq_idx_messages_seq ON messages_seq(seq_in, seq_out);").stepThis().dispose();
- database.executeFast("PRAGMA user_version = 12").stepThis().dispose();
+ //shared media
+ database.executeFast("CREATE TABLE media_v2(mid INTEGER PRIMARY KEY, uid INTEGER, date INTEGER, type INTEGER, data BLOB)").stepThis().dispose();
+ database.executeFast("CREATE TABLE media_counts_v2(uid INTEGER, type INTEGER, count INTEGER, PRIMARY KEY(uid, type))").stepThis().dispose();
+ database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_type_date_idx_media ON media_v2(uid, mid, type, date);").stepThis().dispose();
+ database.executeFast("CREATE INDEX IF NOT EXISTS mid_idx_media ON media_v2(mid);").stepThis().dispose();
+
+ //kev-value
+ database.executeFast("CREATE TABLE keyvalue(id TEXT PRIMARY KEY, value TEXT)").stepThis().dispose();
+
+ //version
+ database.executeFast("PRAGMA user_version = 13").stepThis().dispose();
} else {
try {
SQLiteCursor cursor = database.queryFinalized("SELECT seq, pts, date, qts, lsv, sg, pbytes FROM params WHERE id = 1");
@@ -189,7 +193,7 @@ public class MessagesStorage {
}
}
int version = database.executeInt("PRAGMA user_version");
- if (version < 12) {
+ if (version < 13) {
updateDbToLastVersion(version);
}
}
@@ -208,9 +212,6 @@ public class MessagesStorage {
if (version < 4) {
database.executeFast("CREATE TABLE IF NOT EXISTS user_photos(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose();
- database.executeFast("CREATE INDEX IF NOT EXISTS mid_idx_media ON media(mid);").stepThis().dispose();
- database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_media ON media(uid, date, mid);").stepThis().dispose();
-
database.executeFast("DROP INDEX IF EXISTS read_state_out_idx_messages;").stepThis().dispose();
database.executeFast("DROP INDEX IF EXISTS ttl_idx_messages;").stepThis().dispose();
database.executeFast("DROP INDEX IF EXISTS date_idx_messages;").stepThis().dispose();
@@ -350,6 +351,23 @@ public class MessagesStorage {
database.executeFast("PRAGMA user_version = 12").stepThis().dispose();
version = 12;
}
+ if (version == 12 && version < 13) {
+ database.executeFast("DROP INDEX IF EXISTS uid_mid_idx_media;").stepThis().dispose();
+ database.executeFast("DROP INDEX IF EXISTS mid_idx_media;").stepThis().dispose();
+ database.executeFast("DROP INDEX IF EXISTS uid_date_mid_idx_media;").stepThis().dispose();
+ database.executeFast("DROP TABLE IF EXISTS media;").stepThis().dispose();
+ database.executeFast("DROP TABLE IF EXISTS media_counts;").stepThis().dispose();
+
+ database.executeFast("CREATE TABLE IF NOT EXISTS media_v2(mid INTEGER PRIMARY KEY, uid INTEGER, date INTEGER, type INTEGER, data BLOB)").stepThis().dispose();
+ database.executeFast("CREATE TABLE IF NOT EXISTS media_counts_v2(uid INTEGER, type INTEGER, count INTEGER, PRIMARY KEY(uid, type))").stepThis().dispose();
+ database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_type_date_idx_media ON media_v2(uid, mid, type, date);").stepThis().dispose();
+ database.executeFast("CREATE INDEX IF NOT EXISTS mid_idx_media ON media_v2(mid);").stepThis().dispose();
+
+ database.executeFast("CREATE TABLE IF NOT EXISTS keyvalue(id TEXT PRIMARY KEY, value TEXT)").stepThis().dispose();
+
+ database.executeFast("PRAGMA user_version = 13").stepThis().dispose();
+ version = 13;
+ }
} catch (Exception e) {
FileLog.e("tmessages", e);
}
@@ -445,7 +463,7 @@ public class MessagesStorage {
});
}
- public void setDialogFlags(final long did, final int flags) {
+ public void setDialogFlags(final long did, final long flags) {
storageQueue.postRunnable(new Runnable() {
@Override
public void run() {
@@ -821,9 +839,9 @@ public class MessagesStorage {
}
}
database.executeFast("UPDATE dialogs SET unread_count = 0 WHERE did = " + did).stepThis().dispose();
- database.executeFast("DELETE FROM media_counts WHERE uid = " + did).stepThis().dispose();
database.executeFast("DELETE FROM messages WHERE uid = " + did).stepThis().dispose();
- database.executeFast("DELETE FROM media WHERE uid = " + did).stepThis().dispose();
+ database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + did).stepThis().dispose();
+ database.executeFast("DELETE FROM media_v2 WHERE uid = " + did).stepThis().dispose();
} catch (Exception e) {
FileLog.e("tmessages", e);
}
@@ -1479,149 +1497,6 @@ public class MessagesStorage {
});
}
- public void putMediaCount(final long uid, final int count) {
- storageQueue.postRunnable(new Runnable() {
- @Override
- public void run() {
- try {
- SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media_counts VALUES(?, ?)");
- state2.requery();
- state2.bindLong(1, uid);
- state2.bindInteger(2, count);
- state2.step();
- state2.dispose();
- } catch (Exception e) {
- FileLog.e("tmessages", e);
- }
- }
- });
- }
-
- public void getMediaCount(final long uid, final int classGuid) {
- storageQueue.postRunnable(new Runnable() {
- @Override
- public void run() {
- try {
- int count = -1;
- SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT count FROM media_counts WHERE uid = %d LIMIT 1", uid));
- if (cursor.next()) {
- count = cursor.intValue(0);
- }
- cursor.dispose();
- int lower_part = (int)uid;
- if (count == -1 && lower_part == 0) {
- cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media WHERE uid = %d LIMIT 1", uid));
- if (cursor.next()) {
- count = cursor.intValue(0);
- }
- cursor.dispose();
- if (count != -1) {
- putMediaCount(uid, count);
- }
- }
- MessagesController.getInstance().processLoadedMediaCount(count, uid, classGuid, true);
- } catch (Exception e) {
- FileLog.e("tmessages", e);
- }
- }
- });
- }
-
- public void loadMedia(final long uid, final int offset, final int count, final int max_id, final int classGuid) {
- storageQueue.postRunnable(new Runnable() {
- @Override
- public void run() {
- TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages();
- try {
- ArrayList loadedUsers = new ArrayList<>();
- ArrayList fromUser = new ArrayList<>();
-
- SQLiteCursor cursor;
-
- if ((int)uid != 0) {
- if (max_id != 0) {
- cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media WHERE uid = %d AND mid < %d ORDER BY date DESC, mid DESC LIMIT %d", uid, max_id, count));
- } else {
- cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media WHERE uid = %d ORDER BY date DESC, mid DESC LIMIT %d,%d", uid, offset, count));
- }
- } else {
- if (max_id != 0) {
- cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d ORDER BY m.mid ASC LIMIT %d", uid, max_id, count));
- } else {
- cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d ORDER BY m.mid ASC LIMIT %d,%d", uid, offset, count));
- }
- }
-
- while (cursor.next()) {
- ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
- if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
- TLRPC.Message message = (TLRPC.Message)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
- message.id = cursor.intValue(1);
- message.dialog_id = uid;
- if ((int)uid == 0) {
- message.random_id = cursor.longValue(2);
- }
- res.messages.add(message);
- fromUser.add(message.from_id);
- }
- buffersStorage.reuseFreeBuffer(data);
- }
- cursor.dispose();
-
- StringBuilder usersToLoad = new StringBuilder();
- for (int uid : fromUser) {
- if (!loadedUsers.contains(uid)) {
- if (usersToLoad.length() != 0) {
- usersToLoad.append(",");
- }
- usersToLoad.append(uid);
- loadedUsers.add(uid);
- }
- }
- if (usersToLoad.length() != 0) {
- getUsersInternal(usersToLoad.toString(), res.users);
- }
- } catch (Exception e) {
- res.messages.clear();
- res.chats.clear();
- res.users.clear();
- FileLog.e("tmessages", e);
- } finally {
- MessagesController.getInstance().processLoadedMedia(res, uid, offset, count, max_id, true, classGuid);
- }
- }
- });
- }
-
- public void putMedia(final long uid, final ArrayList messages) {
- storageQueue.postRunnable(new Runnable() {
- @Override
- public void run() {
- try {
- database.beginTransaction();
- SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media VALUES(?, ?, ?, ?)");
- for (TLRPC.Message message : messages) {
- if (message.media instanceof TLRPC.TL_messageMediaVideo || message.media instanceof TLRPC.TL_messageMediaPhoto) {
- state2.requery();
- ByteBufferDesc data = buffersStorage.getFreeBuffer(message.getObjectSize());
- message.serializeToStream(data);
- state2.bindInteger(1, message.id);
- state2.bindLong(2, uid);
- state2.bindInteger(3, message.date);
- state2.bindByteBuffer(4, data.buffer);
- state2.step();
- buffersStorage.reuseFreeBuffer(data);
- }
- }
- state2.dispose();
- database.commitTransaction();
- } catch (Exception e) {
- FileLog.e("tmessages", e);
- }
- }
- });
- }
-
public void getUnsentMessages(final int count) {
storageQueue.postRunnable(new Runnable() {
@Override
@@ -2105,10 +1980,11 @@ public class MessagesStorage {
storageQueue.postRunnable(new Runnable() {
@Override
public void run() {
+ SQLitePreparedStatement state = null;
try {
String id = Utilities.MD5(path);
if (id != null) {
- SQLitePreparedStatement state = database.executeFast("REPLACE INTO sent_files_v2 VALUES(?, ?, ?)");
+ state = database.executeFast("REPLACE INTO sent_files_v2 VALUES(?, ?, ?)");
state.requery();
ByteBufferDesc data = buffersStorage.getFreeBuffer(file.getObjectSize());
file.serializeToStream(data);
@@ -2116,11 +1992,14 @@ public class MessagesStorage {
state.bindInteger(2, type);
state.bindByteBuffer(3, data.buffer);
state.step();
- state.dispose();
buffersStorage.reuseFreeBuffer(data);
}
} catch (Exception e) {
FileLog.e("tmessages", e);
+ } finally {
+ if (state != null) {
+ state.dispose();
+ }
}
}
});
@@ -2439,7 +2318,7 @@ public class MessagesStorage {
state.dispose();
}
- private void getUsersInternal(String usersToLoad, ArrayList result) throws Exception {
+ public void getUsersInternal(String usersToLoad, ArrayList result) throws Exception {
if (usersToLoad == null || usersToLoad.length() == 0 || result == null) {
return;
}
@@ -2464,7 +2343,7 @@ public class MessagesStorage {
cursor.dispose();
}
- private void getChatsInternal(String chatsToLoad, ArrayList result) throws Exception {
+ public void getChatsInternal(String chatsToLoad, ArrayList result) throws Exception {
if (chatsToLoad == null || chatsToLoad.length() == 0 || result == null) {
return;
}
@@ -2486,7 +2365,7 @@ public class MessagesStorage {
cursor.dispose();
}
- private void getEncryptedChatsInternal(String chatsToLoad, ArrayList result, ArrayList usersToLoad) throws Exception {
+ public void getEncryptedChatsInternal(String chatsToLoad, ArrayList result, ArrayList usersToLoad) throws Exception {
if (chatsToLoad == null || chatsToLoad.length() == 0 || result == null) {
return;
}
@@ -2633,15 +2512,6 @@ public class MessagesStorage {
});
}
- private boolean canAddMessageToMedia(TLRPC.Message message) {
- if (message instanceof TLRPC.TL_message_secret && message.media instanceof TLRPC.TL_messageMediaPhoto && message.ttl != 0 && message.ttl <= 60) {
- return false;
- } else if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaVideo) {
- return true;
- }
- return false;
- }
-
private int getMessageMediaType(TLRPC.Message message) {
if (message instanceof TLRPC.TL_message_secret && (
message.media instanceof TLRPC.TL_messageMediaPhoto && message.ttl != 0 && message.ttl <= 60 ||
@@ -2661,13 +2531,14 @@ public class MessagesStorage {
}
HashMap messagesMap = new HashMap<>();
HashMap messagesCounts = new HashMap<>();
- HashMap mediaCounts = new HashMap<>();
+ HashMap> mediaCounts = new HashMap<>();
+ HashMap mediaTypes = new HashMap<>();
HashMap messagesIdsMap = new HashMap<>();
HashMap messagesMediaIdsMap = new HashMap<>();
StringBuilder messageIds = new StringBuilder();
StringBuilder messageMediaIds = new StringBuilder();
SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)");
- SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media VALUES(?, ?, ?, ?)");
+ SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)");
SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO randoms VALUES(?, ?)");
SQLitePreparedStatement state4 = database.executeFast("REPLACE INTO download_queue VALUES(?, ?, ?, ?)");
@@ -2689,12 +2560,39 @@ public class MessagesStorage {
messagesIdsMap.put(message.id, dialog_id);
}
- if (canAddMessageToMedia(message)) {
+ if (SharedMediaQuery.canAddMessageToMedia(message)) {
if (messageMediaIds.length() > 0) {
messageMediaIds.append(",");
}
messageMediaIds.append(message.id);
messagesMediaIdsMap.put(message.id, dialog_id);
+ mediaTypes.put(message.id, SharedMediaQuery.getMediaType(message));
+ }
+ }
+
+ if (messageMediaIds.length() > 0) {
+ SQLiteCursor cursor = database.queryFinalized("SELECT mid FROM media_v2 WHERE mid IN(" + messageMediaIds.toString() + ")");
+ while (cursor.next()) {
+ int mid = cursor.intValue(0);
+ messagesMediaIdsMap.remove(mid);
+ }
+ cursor.dispose();
+ for (HashMap.Entry entry : messagesMediaIdsMap.entrySet()) {
+ Integer type = mediaTypes.get(entry.getKey());
+ HashMap counts = mediaCounts.get(type);
+ Integer count;
+ if (counts == null) {
+ counts = new HashMap<>();
+ count = 0;
+ mediaCounts.put(type, counts);
+ } else {
+ count = counts.get(entry.getValue());
+ }
+ if (count == null) {
+ count = 0;
+ }
+ count++;
+ counts.put(entry.getValue(), count);
}
}
@@ -2715,23 +2613,6 @@ public class MessagesStorage {
}
}
- if (messageMediaIds.length() > 0) {
- SQLiteCursor cursor = database.queryFinalized("SELECT mid FROM media WHERE mid IN(" + messageMediaIds.toString() + ")");
- while (cursor.next()) {
- int mid = cursor.intValue(0);
- messagesMediaIdsMap.remove(mid);
- }
- cursor.dispose();
- for (Long dialog_id : messagesMediaIdsMap.values()) {
- Integer count = mediaCounts.get(dialog_id);
- if (count == null) {
- count = 0;
- }
- count++;
- mediaCounts.put(dialog_id, count);
- }
- }
-
int downloadMediaMask = 0;
for (TLRPC.Message message : messages) {
fixUnsupportedMedia(message);
@@ -2784,12 +2665,13 @@ public class MessagesStorage {
state3.step();
}
- if (canAddMessageToMedia(message)) {
+ if (SharedMediaQuery.canAddMessageToMedia(message)) {
state2.requery();
state2.bindInteger(1, messageId);
state2.bindLong(2, dialog_id);
state2.bindInteger(3, message.date);
- state2.bindByteBuffer(4, data.buffer);
+ state2.bindInteger(4, SharedMediaQuery.getMediaType(message));
+ state2.bindByteBuffer(5, data.buffer);
state2.step();
}
buffersStorage.reuseFreeBuffer(data);
@@ -2883,33 +2765,37 @@ public class MessagesStorage {
state.step();
}
state.dispose();
+
+ if (!mediaCounts.isEmpty()) {
+ state = database.executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?)");
+ for (HashMap.Entry> counts : mediaCounts.entrySet()) {
+ Integer type = counts.getKey();
+ for (HashMap.Entry pair : counts.getValue().entrySet()) {
+ long uid = pair.getKey();
+ int lower_part = (int) uid;
+ int count = -1;
+ SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT count FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type));
+ if (cursor.next()) {
+ count = cursor.intValue(0);
+ }
+ cursor.dispose();
+ if (count != -1) {
+ state.requery();
+ count += pair.getValue();
+ state.bindLong(1, uid);
+ state.bindInteger(2, type);
+ state.bindInteger(3, count);
+ state.step();
+ }
+ }
+ }
+ state.dispose();
+ }
if (withTransaction) {
database.commitTransaction();
}
MessagesController.getInstance().processDialogsUpdateRead(messagesCounts);
- if (!mediaCounts.isEmpty()) {
- state = database.executeFast("REPLACE INTO media_counts VALUES(?, ?)");
- for (HashMap.Entry pair : mediaCounts.entrySet()) {
- long uid = pair.getKey();
- int lower_part = (int)uid;
- int count = -1;
- SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT count FROM media_counts WHERE uid = %d LIMIT 1", uid));
- if (cursor.next()) {
- count = cursor.intValue(0);
- }
- if (count != -1) {
- state.requery();
- count += pair.getValue();
- state.bindLong(1, uid);
- state.bindInteger(2, count);
- state.step();
- }
- cursor.dispose();
- }
- state.dispose();
- }
-
if (downloadMediaMask != 0) {
final int downloadMediaMaskFinal = downloadMediaMask;
AndroidUtilities.runOnUIThread(new Runnable() {
@@ -3078,6 +2964,12 @@ public class MessagesStorage {
state.bindInteger(2, oldId);
state.step();
} catch (Exception e) {
+ try {
+ database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE mid = %d", oldId)).stepThis().dispose();
+ database.executeFast(String.format(Locale.US, "DELETE FROM messages_seq WHERE mid = %d", oldId)).stepThis().dispose();
+ } catch (Exception e2) {
+ FileLog.e("tmessages", e2);
+ }
FileLog.e("tmessages", e);
} finally {
if (state != null) {
@@ -3087,11 +2979,16 @@ public class MessagesStorage {
}
try {
- state = database.executeFast("UPDATE media SET mid = ? WHERE mid = ?");
+ state = database.executeFast("UPDATE media_v2 SET mid = ? WHERE mid = ?");
state.bindInteger(1, newId);
state.bindInteger(2, oldId);
state.step();
} catch (Exception e) {
+ try {
+ database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE mid = %d", oldId)).stepThis().dispose();
+ } catch (Exception e2) {
+ FileLog.e("tmessages", e2);
+ }
FileLog.e("tmessages", e);
} finally {
if (state != null) {
@@ -3352,8 +3249,8 @@ public class MessagesStorage {
FileLoader.getInstance().deleteFiles(filesToDelete);
database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE mid IN(%s)", ids)).stepThis().dispose();
database.executeFast(String.format(Locale.US, "DELETE FROM messages_seq WHERE mid IN(%s)", ids)).stepThis().dispose();
- database.executeFast(String.format(Locale.US, "DELETE FROM media WHERE mid IN(%s)", ids)).stepThis().dispose();
- database.executeFast("DELETE FROM media_counts WHERE 1").stepThis().dispose();
+ database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE mid IN(%s)", ids)).stepThis().dispose();
+ database.executeFast("DELETE FROM media_counts_v2 WHERE 1").stepThis().dispose();
} catch (Exception e) {
FileLog.e("tmessages", e);
}
@@ -3521,7 +3418,7 @@ public class MessagesStorage {
database.beginTransaction();
if (!messages.messages.isEmpty()) {
SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)");
- SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media VALUES(?, ?, ?, ?)");
+ SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)");
for (TLRPC.Message message : messages.messages) {
fixUnsupportedMedia(message);
state.requery();
@@ -3538,12 +3435,13 @@ public class MessagesStorage {
state.bindInteger(9, 0);
state.step();
- if (message.media instanceof TLRPC.TL_messageMediaVideo || message.media instanceof TLRPC.TL_messageMediaPhoto) {
+ if (SharedMediaQuery.canAddMessageToMedia(message)) {
state2.requery();
state2.bindInteger(1, message.id);
state2.bindLong(2, dialog_id);
state2.bindInteger(3, message.date);
- state2.bindByteBuffer(4, data.buffer);
+ state2.bindInteger(4, SharedMediaQuery.getMediaType(message));
+ state2.bindByteBuffer(5, data.buffer);
state2.step();
}
buffersStorage.reuseFreeBuffer(data);
@@ -3573,13 +3471,22 @@ public class MessagesStorage {
usersToLoad.add(UserConfig.getClientUserId());
ArrayList chatsToLoad = new ArrayList<>();
ArrayList encryptedToLoad = new ArrayList<>();
- SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid ORDER BY d.date DESC LIMIT %d,%d", offset, count));
+ SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid LEFT JOIN dialog_settings as s ON d.did = s.did ORDER BY d.date DESC LIMIT %d,%d", offset, count));
while (cursor.next()) {
TLRPC.TL_dialog dialog = new TLRPC.TL_dialog();
dialog.id = cursor.longValue(0);
dialog.top_message = cursor.intValue(1);
dialog.unread_count = cursor.intValue(2);
dialog.last_message_date = cursor.intValue(3);
+ long flags = cursor.longValue(8);
+ int low_flags = (int)flags;
+ dialog.notify_settings = new TLRPC.TL_peerNotifySettings();
+ if ((low_flags & 1) != 0) {
+ dialog.notify_settings.mute_until = (int)(flags >> 32);
+ if (dialog.notify_settings.mute_until == 0) {
+ dialog.notify_settings.mute_until = Integer.MAX_VALUE;
+ }
+ }
dialogs.dialogs.add(dialog);
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(4));
@@ -3680,7 +3587,7 @@ public class MessagesStorage {
if (!dialogs.dialogs.isEmpty()) {
SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)");
SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO dialogs(did, date, unread_count, last_mid) VALUES(?, ?, ?, ?)");
- SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO media VALUES(?, ?, ?, ?)");
+ SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)");
SQLitePreparedStatement state4 = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)");
for (TLRPC.TL_dialog dialog : dialogs.dialogs) {
@@ -3717,12 +3624,13 @@ public class MessagesStorage {
state4.bindInteger(2, dialog.notify_settings.mute_until != 0 ? 1 : 0);
state4.step();
- if (message.media instanceof TLRPC.TL_messageMediaVideo || message.media instanceof TLRPC.TL_messageMediaPhoto) {
+ if (SharedMediaQuery.canAddMessageToMedia(message)) {
state3.requery();
state3.bindLong(1, message.id);
state3.bindInteger(2, uid);
state3.bindInteger(3, message.date);
- state3.bindByteBuffer(4, data.buffer);
+ state3.bindInteger(4, SharedMediaQuery.getMediaType(message));
+ state3.bindByteBuffer(5, data.buffer);
state3.step();
}
buffersStorage.reuseFreeBuffer(data);
diff --git a/TMessagesProj/src/main/java/org/telegram/android/NotificationCenter.java b/TMessagesProj/src/main/java/org/telegram/android/NotificationCenter.java
index 4500948a..6e045c32 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/NotificationCenter.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/NotificationCenter.java
@@ -52,6 +52,8 @@ public class NotificationCenter {
public static final int httpFileDidLoaded = totalEvents++;
public static final int httpFileDidFailedLoad = totalEvents++;
+ public static final int messageThumbGenerated = totalEvents++;
+
public static final int wallpapersDidLoaded = totalEvents++;
public static final int closeOtherAppActivities = totalEvents++;
public static final int didUpdatedConnectionState = totalEvents++;
@@ -70,16 +72,16 @@ public class NotificationCenter {
public static final int FileNewChunkAvailable = totalEvents++;
public static final int FilePreparingFailed = totalEvents++;
- public final static int audioProgressDidChanged = totalEvents++;
- public final static int audioDidReset = totalEvents++;
- public final static int recordProgressChanged = totalEvents++;
- public final static int recordStarted = totalEvents++;
- public final static int recordStartError = totalEvents++;
- public final static int recordStopped = totalEvents++;
- public final static int screenshotTook = totalEvents++;
- public final static int albumsDidLoaded = totalEvents++;
- public final static int audioDidSent = totalEvents++;
- public final static int audioDidStarted = totalEvents++;
+ public static final int audioProgressDidChanged = totalEvents++;
+ public static final int audioDidReset = totalEvents++;
+ public static final int recordProgressChanged = totalEvents++;
+ public static final int recordStarted = totalEvents++;
+ public static final int recordStartError = totalEvents++;
+ public static final int recordStopped = totalEvents++;
+ public static final int screenshotTook = totalEvents++;
+ public static final int albumsDidLoaded = totalEvents++;
+ public static final int audioDidSent = totalEvents++;
+ public static final int audioDidStarted = totalEvents++;
public static final int audioRouteChanged = totalEvents++;
final private HashMap> observers = new HashMap<>();
diff --git a/TMessagesProj/src/main/java/org/telegram/android/NotificationDelay.java b/TMessagesProj/src/main/java/org/telegram/android/NotificationDelay.java
new file mode 100644
index 00000000..c47af63f
--- /dev/null
+++ b/TMessagesProj/src/main/java/org/telegram/android/NotificationDelay.java
@@ -0,0 +1,29 @@
+/*
+ * This is the source code of Telegram for Android v. 2.0.x.
+ * It is licensed under GNU GPL v. 2 or later.
+ * You should have received a copy of the license in this archive (see LICENSE).
+ *
+ * Copyright Nikolai Kudashov, 2013-2014.
+ */
+
+package org.telegram.android;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+public class NotificationDelay extends IntentService {
+
+ public NotificationDelay() {
+ super("NotificationDelay");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ AndroidUtilities.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ NotificationsController.getInstance().notificationDelayReached();
+ }
+ });
+ }
+}
diff --git a/TMessagesProj/src/main/java/org/telegram/android/NotificationsController.java b/TMessagesProj/src/main/java/org/telegram/android/NotificationsController.java
index 9d461166..c0cc5d4f 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/NotificationsController.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/NotificationsController.java
@@ -31,6 +31,8 @@ import org.json.JSONObject;
import org.telegram.messenger.ConnectionsManager;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.R;
+import org.telegram.messenger.RPCRequest;
+import org.telegram.messenger.TLObject;
import org.telegram.messenger.TLRPC;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.ApplicationLoader;
@@ -57,6 +59,7 @@ public class NotificationsController {
private int total_unread_count = 0;
private int personal_count = 0;
private boolean notifyCheck = false;
+ private int lastOnlineFromOtherDevice = 0;
private static volatile NotificationsController Instance = null;
public static NotificationsController getInstance() {
@@ -256,6 +259,27 @@ public class NotificationsController {
}
}
+ private void scheduleNotificationDelay(boolean onlineReason) {
+ try {
+ FileLog.e("tmessages", "delay notification start");
+ AlarmManager alarm = (AlarmManager) ApplicationLoader.applicationContext.getSystemService(Context.ALARM_SERVICE);
+ PendingIntent pintent = PendingIntent.getService(ApplicationLoader.applicationContext, 0, new Intent(ApplicationLoader.applicationContext, NotificationDelay.class), 0);
+ SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
+ if (onlineReason) {
+ alarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 3 * 1000, pintent);
+ } else {
+ alarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 500, pintent);
+ }
+ } catch (Exception e) {
+ FileLog.e("tmessages", e);
+ }
+ }
+
+ protected void notificationDelayReached() {
+ FileLog.e("tmessages", "delay reached");
+ showOrUpdateNotification(true);
+ }
+
protected void repeatNotificationMaybe() {
int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
if (hour >= 11 && hour <= 22) {
@@ -266,6 +290,15 @@ public class NotificationsController {
}
}
+ public void setLastOnlineFromOtherDevice(final int time) {
+ AndroidUtilities.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ lastOnlineFromOtherDevice = time;
+ }
+ });
+ }
+
private void showOrUpdateNotification(boolean notifyAboutLast) {
if (!UserConfig.isClientActivated() || pushMessages.isEmpty()) {
dismissNotification();
@@ -307,6 +340,12 @@ public class NotificationsController {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
+ if (notify_override == 3) {
+ int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
+ if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
+ notify_override = 2;
+ }
+ }
if (!notifyAboutLast || notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || chat_id != 0 && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0) {
notifyDisabled = true;
}
@@ -490,7 +529,7 @@ public class NotificationsController {
}
if (photoPath != null) {
- BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photoPath, null, "50_50", null);
+ BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photoPath, null, "50_50");
if (img != null) {
mBuilder.setLargeIcon(img.getBitmap());
}
@@ -773,6 +812,12 @@ public class NotificationsController {
popup = (int)dialog_id == 0 ? 0 : preferences.getInt(isChat ? "popupGroup" : "popupAll", 0);
if (value == null) {
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
+ if (notify_override == 3) {
+ int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
+ if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
+ notify_override = 2;
+ }
+ }
value = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || isChat && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0);
settingsCache.put(dialog_id, value);
}
@@ -808,6 +853,12 @@ public class NotificationsController {
long dialog_id = entry.getKey();
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
+ if (notify_override == 3) {
+ int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
+ if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
+ notify_override = 2;
+ }
+ }
boolean canAddValue = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || ((int)dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0);
Integer currentCount = pushDialogs.get(dialog_id);
@@ -842,6 +893,14 @@ public class NotificationsController {
pushDialogs.put(dialog_id, newCount);
}
}
+ /*if (old_unread_count != total_unread_count) { TODO
+ if (lastOnlineFromOtherDevice > ConnectionsManager.getInstance().getCurrentTime()) {
+ showOrUpdateNotification(false);
+ scheduleNotificationDelay(true);
+ } else {
+ showOrUpdateNotification(notifyCheck);
+ }
+ }*/
if (old_unread_count != total_unread_count) {
showOrUpdateNotification(notifyCheck);
}
@@ -869,6 +928,12 @@ public class NotificationsController {
Boolean value = settingsCache.get(dialog_id);
if (value == null) {
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
+ if (notify_override == 3) {
+ int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
+ if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
+ notify_override = 2;
+ }
+ }
value = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || ((int) dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0);
settingsCache.put(dialog_id, value);
}
@@ -884,7 +949,7 @@ public class NotificationsController {
if (pushMessagesDict.containsKey(message.id)) {
continue;
}
- MessageObject messageObject = new MessageObject(message, null, 0);
+ MessageObject messageObject = new MessageObject(message, null, false);
if (isPersonalMessage(messageObject)) {
personal_count++;
}
@@ -892,6 +957,12 @@ public class NotificationsController {
Boolean value = settingsCache.get(dialog_id);
if (value == null) {
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
+ if (notify_override == 3) {
+ int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
+ if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
+ notify_override = 2;
+ }
+ }
value = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || ((int) dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0);
settingsCache.put(dialog_id, value);
}
@@ -957,4 +1028,49 @@ public class NotificationsController {
return messageObject.messageOwner.to_id != null && messageObject.messageOwner.to_id.chat_id == 0
&& (messageObject.messageOwner.action == null || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty);
}
+
+ public static void updateServerNotificationsSettings(long dialog_id) {
+ NotificationCenter.getInstance().postNotificationName(NotificationCenter.notificationsSettingsUpdated);
+ if ((int)dialog_id == 0) {
+ return;
+ }
+ SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
+ TLRPC.TL_account_updateNotifySettings req = new TLRPC.TL_account_updateNotifySettings();
+ req.settings = new TLRPC.TL_inputPeerNotifySettings();
+ req.settings.sound = "default";
+ req.settings.events_mask = 0;
+ int mute_type = preferences.getInt("notify2_" + dialog_id, 0);
+ if (mute_type == 3) {
+ req.settings.mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
+ } else {
+ req.settings.mute_until = mute_type != 2 ? 0 : Integer.MAX_VALUE;
+ }
+ req.settings.show_previews = preferences.getBoolean("preview_" + dialog_id, true);
+
+ req.peer = new TLRPC.TL_inputNotifyPeer();
+
+ if ((int)dialog_id < 0) {
+ ((TLRPC.TL_inputNotifyPeer)req.peer).peer = new TLRPC.TL_inputPeerChat();
+ ((TLRPC.TL_inputNotifyPeer)req.peer).peer.chat_id = -(int)dialog_id;
+ } else {
+ TLRPC.User user = MessagesController.getInstance().getUser((int)dialog_id);
+ if (user == null) {
+ return;
+ }
+ if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) {
+ ((TLRPC.TL_inputNotifyPeer)req.peer).peer = new TLRPC.TL_inputPeerForeign();
+ ((TLRPC.TL_inputNotifyPeer)req.peer).peer.access_hash = user.access_hash;
+ } else {
+ ((TLRPC.TL_inputNotifyPeer)req.peer).peer = new TLRPC.TL_inputPeerContact();
+ }
+ ((TLRPC.TL_inputNotifyPeer)req.peer).peer.user_id = (int)dialog_id;
+ }
+
+ ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
+ @Override
+ public void run(TLObject response, TLRPC.TL_error error) {
+
+ }
+ });
+ }
}
diff --git a/TMessagesProj/src/main/java/org/telegram/android/PhotoObject.java b/TMessagesProj/src/main/java/org/telegram/android/PhotoObject.java
deleted file mode 100644
index d0d16e57..00000000
--- a/TMessagesProj/src/main/java/org/telegram/android/PhotoObject.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * This is the source code of Telegram for Android v. 1.3.2.
- * It is licensed under GNU GPL v. 2 or later.
- * You should have received a copy of the license in this archive (see LICENSE).
- *
- * Copyright Nikolai Kudashov, 2013.
- */
-
-package org.telegram.android;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-
-import org.telegram.messenger.FileLog;
-import org.telegram.messenger.TLRPC;
-import org.telegram.messenger.Utilities;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
-public class PhotoObject {
-
- public TLRPC.PhotoSize photoOwner;
- public Bitmap image;
-
- public PhotoObject(TLRPC.PhotoSize photo, int preview, boolean secret) {
- photoOwner = photo;
-
- if (preview != 0 && photo instanceof TLRPC.TL_photoCachedSize) {
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
- opts.inDither = false;
- opts.outWidth = photo.w;
- opts.outHeight = photo.h;
- try {
- if (photo.location.ext != null) {
- ByteBuffer buffer = ByteBuffer.allocateDirect(photo.bytes.length);
- buffer.put(photo.bytes);
- image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
- } else {
- image = BitmapFactory.decodeByteArray(photoOwner.bytes, 0, photoOwner.bytes.length, opts);
- }
- if (image != null) {
- if (preview == 2) {
- if (secret) {
- Utilities.blurBitmap(image, 7);
- Utilities.blurBitmap(image, 7);
- Utilities.blurBitmap(image, 7);
- } else {
- if (photo.location.ext != null) {
- Utilities.blurBitmap(image, 1);
- } else {
- Utilities.blurBitmap(image, 3);
- }
- }
- }
- if (ImageLoader.getInstance().runtimeHack != null) {
- ImageLoader.getInstance().runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
- }
- }
- } catch (Throwable throwable) {
- FileLog.e("tmessages", throwable);
- }
- }
- }
-
- public static PhotoObject getClosestImageWithSize(ArrayList arr, int side) {
- if (arr == null) {
- return null;
- }
-
- int lastSide = 0;
- PhotoObject closestObject = null;
- for (PhotoObject obj : arr) {
- if (obj == null || obj.photoOwner == null) {
- continue;
- }
- int currentSide = obj.photoOwner.w >= obj.photoOwner.h ? obj.photoOwner.w : obj.photoOwner.h;
- if (closestObject == null || closestObject.photoOwner instanceof TLRPC.TL_photoCachedSize || currentSide <= side && lastSide < currentSide) {
- closestObject = obj;
- lastSide = currentSide;
- }
- }
- return closestObject;
- }
-}
diff --git a/TMessagesProj/src/main/java/org/telegram/android/SecretChatHelper.java b/TMessagesProj/src/main/java/org/telegram/android/SecretChatHelper.java
index d9fd56a1..6a114024 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/SecretChatHelper.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/SecretChatHelper.java
@@ -478,7 +478,7 @@ public class SecretChatHelper {
reqSend.action.ttl_seconds = encryptedChat.ttl;
message = createServiceSecretMessage(encryptedChat, reqSend.action);
- MessageObject newMsgObj = new MessageObject(message, null);
+ MessageObject newMsgObj = new MessageObject(message, null, false);
newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING;
ArrayList objArr = new ArrayList<>();
objArr.add(newMsgObj);
@@ -514,7 +514,7 @@ public class SecretChatHelper {
reqSend.action.random_ids = random_ids;
message = createServiceSecretMessage(encryptedChat, reqSend.action);
- MessageObject newMsgObj = new MessageObject(message, null);
+ MessageObject newMsgObj = new MessageObject(message, null, false);
newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING;
ArrayList objArr = new ArrayList<>();
objArr.add(newMsgObj);
@@ -547,7 +547,7 @@ public class SecretChatHelper {
arr.add(newMsg);
MessagesStorage.getInstance().putMessages(arr, false, true, false, 0);
- MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.photo, 3);
+ //MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.photo, 3);
} else if (newMsg.media instanceof TLRPC.TL_messageMediaVideo && newMsg.media.video != null) {
TLRPC.Video video = newMsg.media.video;
newMsg.media.video = new TLRPC.TL_videoEncrypted();
@@ -578,7 +578,7 @@ public class SecretChatHelper {
arr.add(newMsg);
MessagesStorage.getInstance().putMessages(arr, false, true, false, 0);
- MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.video, 5);
+ //MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.video, 5);
} else if (newMsg.media instanceof TLRPC.TL_messageMediaDocument && newMsg.media.document != null) {
TLRPC.Document document = newMsg.media.document;
newMsg.media.document = new TLRPC.TL_documentEncrypted();
@@ -605,7 +605,7 @@ public class SecretChatHelper {
arr.add(newMsg);
MessagesStorage.getInstance().putMessages(arr, false, true, false, 0);
- MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.document, 4);
+ //MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.document, 4);
} else if (newMsg.media instanceof TLRPC.TL_messageMediaAudio && newMsg.media.audio != null) {
TLRPC.Audio audio = newMsg.media.audio;
newMsg.media.audio = new TLRPC.TL_audioEncrypted();
@@ -656,7 +656,8 @@ public class SecretChatHelper {
TLObject toEncryptObject = null;
if (AndroidUtilities.getPeerLayerVersion(chat.layer) >= 17) {
TLRPC.TL_decryptedMessageLayer layer = new TLRPC.TL_decryptedMessageLayer();
- layer.layer = Math.min(17, AndroidUtilities.getPeerLayerVersion(chat.layer));
+ int myLayer = Math.max(17, AndroidUtilities.getMyLayerVersion(chat.layer));
+ layer.layer = Math.min(myLayer, AndroidUtilities.getPeerLayerVersion(chat.layer));
layer.message = req;
layer.random_bytes = new byte[Math.max(1, (int) Math.ceil(Utilities.random.nextDouble() * 16))];
Utilities.random.nextBytes(layer.random_bytes);
@@ -698,6 +699,7 @@ public class SecretChatHelper {
toEncryptObject = req;
}
+
int len = toEncryptObject.getObjectSize();
ByteBufferDesc toEncrypt = BuffersStorage.getInstance().getFreeBuffer(4 + len);
toEncrypt.writeInt32(len);
diff --git a/TMessagesProj/src/main/java/org/telegram/android/SendMessagesHelper.java b/TMessagesProj/src/main/java/org/telegram/android/SendMessagesHelper.java
index ff92dab3..b7dc050d 100644
--- a/TMessagesProj/src/main/java/org/telegram/android/SendMessagesHelper.java
+++ b/TMessagesProj/src/main/java/org/telegram/android/SendMessagesHelper.java
@@ -301,7 +301,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
public void run() {
if (message.documentLocation.thumb.location instanceof TLRPC.TL_fileLocationUnavailable) {
try {
- Bitmap bitmap = ImageLoader.loadBitmap(cacheFile.getAbsolutePath(), null, 90, 90);
+ Bitmap bitmap = ImageLoader.loadBitmap(cacheFile.getAbsolutePath(), null, 90, 90, true);
if (bitmap != null) {
message.documentLocation.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, message.sendEncryptedRequest != null);
}
@@ -753,7 +753,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
- MessageObject newMsgObj = new MessageObject(newMsg, null, 2);
+ MessageObject newMsgObj = new MessageObject(newMsg, null, true);
newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING;
ArrayList objArr = new ArrayList<>();
@@ -1302,7 +1302,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (isBroadcast) {
for (TLRPC.Message message : sentMessages) {
ArrayList arr = new ArrayList<>();
- MessageObject messageObject = new MessageObject(message, null, 0);
+ MessageObject messageObject = new MessageObject(message, null, false);
arr.add(messageObject);
MessagesController.getInstance().updateInterfaceWithMessages(messageObject.getDialogId(), arr, isBroadcast);
}
@@ -1369,7 +1369,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName + ".jpg");
File cacheFile2 = null;
- if (sentMessage.media.photo.sizes.size() == 1 || size.w > 80 || size.h > 80) {
+ if (sentMessage.media.photo.sizes.size() == 1 || size.w > 90 || size.h > 90) {
cacheFile2 = FileLoader.getPathToAttach(size);
} else {
cacheFile2 = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName2 + ".jpg");
@@ -1431,6 +1431,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2);
size2.location = size.location;
}
+ } else if (MessageObject.isStickerMessage(sentMessage) && size2.location != null) {
+ size.location = size2.location;
}
newMsg.media.document.dc_id = sentMessage.media.document.dc_id;
@@ -1501,7 +1503,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
MessagesController.getInstance().putChats(chats, true);
MessagesController.getInstance().putEncryptedChats(encryptedChats, true);
for (TLRPC.Message message : messages) {
- MessageObject messageObject = new MessageObject(message, null, 0);
+ MessageObject messageObject = new MessageObject(message, null, false);
retrySendMessage(messageObject, true);
}
}
@@ -1509,9 +1511,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
public TLRPC.TL_photo generatePhotoSizes(String path, Uri imageUri) {
- Bitmap bitmap = ImageLoader.loadBitmap(path, imageUri, AndroidUtilities.getPhotoSize(), AndroidUtilities.getPhotoSize());
+ Bitmap bitmap = ImageLoader.loadBitmap(path, imageUri, AndroidUtilities.getPhotoSize(), AndroidUtilities.getPhotoSize(), true);
if (bitmap == null && AndroidUtilities.getPhotoSize() != 800) {
- bitmap = ImageLoader.loadBitmap(path, imageUri, 800, 800);
+ bitmap = ImageLoader.loadBitmap(path, imageUri, 800, 800, true);
}
ArrayList sizes = new ArrayList<>();
TLRPC.PhotoSize size = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, true);
@@ -1578,9 +1580,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
originalPath += "" + f.length();
}
- TLRPC.TL_document document = (TLRPC.TL_document)MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 1 : 4);
- if (document == null && !path.equals(originalPath)) {
- document = (TLRPC.TL_document)MessagesStorage.getInstance().getSentFile(path + f.length(), !isEncrypted ? 1 : 4);
+ TLRPC.TL_document document = null;
+ if (!isEncrypted) {
+ document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 1 : 4);
+ if (document == null && !path.equals(originalPath) && !isEncrypted) {
+ document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(path + f.length(), !isEncrypted ? 1 : 4);
+ }
}
if (document == null) {
document = new TLRPC.TL_document();
@@ -1603,7 +1608,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
if (document.mime_type.equals("image/gif")) {
try {
- Bitmap bitmap = ImageLoader.loadBitmap(f.getAbsolutePath(), null, 90, 90);
+ Bitmap bitmap = ImageLoader.loadBitmap(f.getAbsolutePath(), null, 90, 90, true);
if (bitmap != null) {
document.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, isEncrypted);
}
@@ -1727,7 +1732,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
boolean isEncrypted = (int)dialog_id == 0;
for (final MediaController.SearchImage searchImage : photos) {
if (searchImage.type == 1) {
- TLRPC.TL_document document = (TLRPC.TL_document)MessagesStorage.getInstance().getSentFile(searchImage.imageUrl, !isEncrypted ? 1 : 4);
+ TLRPC.TL_document document = null;
+ if (!isEncrypted) {
+ document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(searchImage.imageUrl, !isEncrypted ? 1 : 4);
+ }
String md5 = Utilities.MD5(searchImage.imageUrl) + "." + ImageLoader.getHttpUrlExtension(searchImage.imageUrl);
File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5);
if (document == null) {
@@ -1753,7 +1761,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
if (thumbFile != null) {
try {
- Bitmap bitmap = ImageLoader.loadBitmap(thumbFile.getAbsolutePath(), null, 90, 90);
+ Bitmap bitmap = ImageLoader.loadBitmap(thumbFile.getAbsolutePath(), null, 90, 90, true);
if (bitmap != null) {
document.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, isEncrypted);
}
@@ -1781,7 +1789,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
});
} else {
boolean needDownloadHttp = true;
- TLRPC.TL_photo photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(searchImage.imageUrl, !isEncrypted ? 0 : 3);
+ TLRPC.TL_photo photo = null;
+ if (!isEncrypted) {
+ photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(searchImage.imageUrl, !isEncrypted ? 0 : 3);
+ }
if (photo == null) {
String md5 = Utilities.MD5(searchImage.imageUrl) + "." + ImageLoader.getHttpUrlExtension(searchImage.imageUrl);
File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5);
@@ -1891,9 +1902,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
} else {
originalPath = null;
}
- TLRPC.TL_photo photo = (TLRPC.TL_photo)MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 0 : 3);
- if (photo == null && uri != null) {
- photo = (TLRPC.TL_photo)MessagesStorage.getInstance().getSentFile(Utilities.getPath(uri), !isEncrypted ? 0 : 3);
+ TLRPC.TL_photo photo = null;
+ if (!isEncrypted) {
+ photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 0 : 3);
+ if (photo == null && uri != null) {
+ photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(Utilities.getPath(uri), !isEncrypted ? 0 : 3);
+ }
}
if (photo == null) {
photo = SendMessagesHelper.getInstance().generatePhotoSizes(path, uri);
@@ -1935,7 +1949,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (videoEditedInfo != null) {
originalPath += duration + "_" + videoEditedInfo.startTime + "_" + videoEditedInfo.endTime;
}
- TLRPC.TL_video video = (TLRPC.TL_video)MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 2 : 5);
+ TLRPC.TL_video video = null;
+ if (!isEncrypted) {
+ video = (TLRPC.TL_video) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 2 : 5);
+ }
if (video == null) {
Bitmap thumb = ThumbnailUtils.createVideoThumbnail(videoPath, MediaStore.Video.Thumbnails.MINI_KIND);
TLRPC.PhotoSize size = ImageLoader.scaleAndSaveImage(thumb, 90, 90, 55, isEncrypted);
diff --git a/TMessagesProj/src/main/java/org/telegram/android/query/SharedMediaQuery.java b/TMessagesProj/src/main/java/org/telegram/android/query/SharedMediaQuery.java
new file mode 100644
index 00000000..aab9255e
--- /dev/null
+++ b/TMessagesProj/src/main/java/org/telegram/android/query/SharedMediaQuery.java
@@ -0,0 +1,398 @@
+/*
+ * This is the source code of Telegram for Android v. 2.0.x.
+ * It is licensed under GNU GPL v. 2 or later.
+ * You should have received a copy of the license in this archive (see LICENSE).
+ *
+ * Copyright Nikolai Kudashov, 2013-2014.
+ */
+
+package org.telegram.android.query;
+
+import org.telegram.SQLite.SQLiteCursor;
+import org.telegram.SQLite.SQLitePreparedStatement;
+import org.telegram.android.AndroidUtilities;
+import org.telegram.android.ImageLoader;
+import org.telegram.android.MessageObject;
+import org.telegram.android.MessagesController;
+import org.telegram.android.MessagesStorage;
+import org.telegram.android.NotificationCenter;
+import org.telegram.messenger.ByteBufferDesc;
+import org.telegram.messenger.ConnectionsManager;
+import org.telegram.messenger.FileLog;
+import org.telegram.messenger.RPCRequest;
+import org.telegram.messenger.TLClassStore;
+import org.telegram.messenger.TLObject;
+import org.telegram.messenger.TLRPC;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+
+public class SharedMediaQuery {
+
+ public final static int MEDIA_PHOTOVIDEO = 0;
+ public final static int MEDIA_FILE = 1;
+ public final static int MEDIA_AUDIO = 2;
+
+ public static void loadMedia(final long uid, final int offset, final int count, final int max_id, final int type, final boolean fromCache, final int classGuid) {
+ int lower_part = (int)uid;
+ if (fromCache || lower_part == 0) {
+ loadMediaDatabase(uid, offset, count, max_id, type, classGuid);
+ } else {
+ TLRPC.TL_messages_search req = new TLRPC.TL_messages_search();
+ req.offset = offset;
+ req.limit = count;
+ req.max_id = max_id;
+ if (type == MEDIA_PHOTOVIDEO) {
+ req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo();
+ } else if (type == MEDIA_FILE) {
+ req.filter = new TLRPC.TL_inputMessagesFilterDocument();
+ } else if (type == MEDIA_AUDIO) {
+ req.filter = new TLRPC.TL_inputMessagesFilterAudio();
+ }
+ req.q = "";
+ if (uid < 0) {
+ req.peer = new TLRPC.TL_inputPeerChat();
+ req.peer.chat_id = -lower_part;
+ } else {
+ TLRPC.User user = MessagesController.getInstance().getUser(lower_part);
+ if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) {
+ req.peer = new TLRPC.TL_inputPeerForeign();
+ req.peer.access_hash = user.access_hash;
+ } else {
+ req.peer = new TLRPC.TL_inputPeerContact();
+ }
+ req.peer.user_id = lower_part;
+ }
+ long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
+ @Override
+ public void run(TLObject response, TLRPC.TL_error error) {
+ if (error == null) {
+ final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response;
+ processLoadedMedia(res, uid, offset, count, max_id, type, false, classGuid);
+ }
+ }
+ });
+ ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
+ }
+ }
+
+ public static void getMediaCount(final long uid, final int type, final int classGuid, boolean fromCache) {
+ int lower_part = (int)uid;
+ if (fromCache || lower_part == 0) {
+ getMediaCountDatabase(uid, type, classGuid);
+ } else {
+ TLRPC.TL_messages_search req = new TLRPC.TL_messages_search();
+ req.offset = 0;
+ req.limit = 1;
+ req.max_id = 0;
+ if (type == MEDIA_PHOTOVIDEO) {
+ req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo();
+ } else if (type == MEDIA_FILE) {
+ req.filter = new TLRPC.TL_inputMessagesFilterDocument();
+ } else if (type == MEDIA_AUDIO) {
+ req.filter = new TLRPC.TL_inputMessagesFilterAudio();
+ }
+ req.q = "";
+ if (uid < 0) {
+ req.peer = new TLRPC.TL_inputPeerChat();
+ req.peer.chat_id = -lower_part;
+ } else {
+ TLRPC.User user = MessagesController.getInstance().getUser(lower_part);
+ if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) {
+ req.peer = new TLRPC.TL_inputPeerForeign();
+ req.peer.access_hash = user.access_hash;
+ } else {
+ req.peer = new TLRPC.TL_inputPeerContact();
+ }
+ req.peer.user_id = lower_part;
+ }
+ long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
+ @Override
+ public void run(TLObject response, TLRPC.TL_error error) {
+ if (error == null) {
+ final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response;
+ MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true);
+
+ AndroidUtilities.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ MessagesController.getInstance().putUsers(res.users, false);
+ MessagesController.getInstance().putChats(res.chats, false);
+ }
+ });
+
+ if (res instanceof TLRPC.TL_messages_messagesSlice) {
+ processLoadedMediaCount(res.count, uid, type, classGuid, false);
+ } else {
+ processLoadedMediaCount(res.messages.size(), uid, type, classGuid, false);
+ }
+ }
+ }
+ });
+ ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
+ }
+ }
+
+ public static int getMediaType(TLRPC.Message message) {
+ if (message == null) {
+ return -1;
+ }
+ if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaVideo) {
+ return SharedMediaQuery.MEDIA_PHOTOVIDEO;
+ } else if (message.media instanceof TLRPC.TL_messageMediaDocument) {
+ if (MessageObject.isStickerMessage(message)) {
+ return -1;
+ } else {
+ return SharedMediaQuery.MEDIA_FILE;
+ }
+ } else if (message.media instanceof TLRPC.TL_messageMediaAudio) {
+ return SharedMediaQuery.MEDIA_AUDIO;
+ }
+ return -1;
+ }
+
+ public static boolean canAddMessageToMedia(TLRPC.Message message) {
+ if (message instanceof TLRPC.TL_message_secret && message.media instanceof TLRPC.TL_messageMediaPhoto && message.ttl != 0 && message.ttl <= 60) {
+ return false;
+ } else if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaVideo || message.media instanceof TLRPC.TL_messageMediaDocument || message.media instanceof TLRPC.TL_messageMediaAudio) {
+ return true;
+ }
+ return false;
+ }
+
+ private static void processLoadedMedia(final TLRPC.messages_Messages res, final long uid, int offset, int count, int max_id, final int type, final boolean fromCache, final int classGuid) {
+ int lower_part = (int)uid;
+ if (fromCache && res.messages.isEmpty() && lower_part != 0) {
+ loadMedia(uid, offset, count, max_id, type, false, classGuid);
+ } else {
+ if (!fromCache) {
+ ImageLoader.saveMessagesThumbs(res.messages);
+ MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true);
+ putMediaDatabase(uid, type, res.messages);
+ }
+
+ final HashMap usersLocal = new HashMap<>();
+ for (TLRPC.User u : res.users) {
+ usersLocal.put(u.id, u);
+ }
+ final ArrayList objects = new ArrayList<>();
+ for (TLRPC.Message message : res.messages) {
+ objects.add(new MessageObject(message, usersLocal, false));
+ }
+
+ AndroidUtilities.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ int totalCount;
+ if (res instanceof TLRPC.TL_messages_messagesSlice) {
+ totalCount = res.count;
+ } else {
+ totalCount = res.messages.size();
+ }
+ MessagesController.getInstance().putUsers(res.users, fromCache);
+ MessagesController.getInstance().putChats(res.chats, fromCache);
+ NotificationCenter.getInstance().postNotificationName(NotificationCenter.mediaDidLoaded, uid, totalCount, objects, fromCache, classGuid, type);
+ }
+ });
+ }
+ }
+
+ private static void processLoadedMediaCount(final int count, final long uid, final int type, final int classGuid, final boolean fromCache) {
+ AndroidUtilities.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ int lower_part = (int)uid;
+ if (fromCache && count == -1 && lower_part != 0) {
+ getMediaCount(uid, type, classGuid, false);
+ } else {
+ if (!fromCache) {
+ putMediaCountDatabase(uid, type, count);
+ }
+ NotificationCenter.getInstance().postNotificationName(NotificationCenter.mediaCountDidLoaded, uid, (fromCache && count == -1 ? 0 : count), fromCache, type);
+ }
+ }
+ });
+ }
+
+ private static void putMediaCountDatabase(final long uid, final int type, final int count) {
+ MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ SQLitePreparedStatement state2 = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?)");
+ state2.requery();
+ state2.bindLong(1, uid);
+ state2.bindInteger(2, type);
+ state2.bindInteger(3, count);
+ state2.step();
+ state2.dispose();
+ } catch (Exception e) {
+ FileLog.e("tmessages", e);
+ }
+ }
+ });
+ }
+
+ private static void getMediaCountDatabase(final long uid, final int type, final int classGuid) {
+ MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ int count = -1;
+ SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT count FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type));
+ if (cursor.next()) {
+ count = cursor.intValue(0);
+ }
+ cursor.dispose();
+ int lower_part = (int)uid;
+ if (count == -1 && lower_part == 0) {
+ cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type));
+ if (cursor.next()) {
+ count = cursor.intValue(0);
+ }
+ cursor.dispose();
+
+ /*cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, send_state, date FROM messages WHERE uid = %d ORDER BY mid ASC LIMIT %d", uid, 1000));
+ ArrayList photos = new ArrayList<>();
+ ArrayList docs = new ArrayList<>();
+ while (cursor.next()) {
+ ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(1));
+ if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
+ TLRPC.Message message = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data, data.readInt32());
+ MessageObject.setIsUnread(message, cursor.intValue(0) != 1);
+ message.date = cursor.intValue(2);
+ message.send_state = cursor.intValue(1);
+ message.dialog_id = uid;
+ if (message.ttl > 60 && message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaVideo) {
+ photos.add(message);
+ } else if (message.media instanceof TLRPC.TL_messageMediaDocument) {
+ docs.add(message);
+ }
+ }
+ MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
+ }
+ cursor.dispose();
+ if (!photos.isEmpty() || !docs.isEmpty()) {
+ MessagesStorage.getInstance().getDatabase().beginTransaction();
+ if (!photos.isEmpty()) {
+ putMediaDatabaseInternal(uid, MEDIA_PHOTOVIDEO, photos);
+ }
+ if (docs.isEmpty()) {
+ putMediaDatabaseInternal(uid, MEDIA_FILE, docs);
+ }
+ MessagesStorage.getInstance().getDatabase().commitTransaction();
+ }*/
+
+ if (count != -1) {
+ putMediaCountDatabase(uid, type, count);
+ }
+ }
+ processLoadedMediaCount(count, uid, type, classGuid, true);
+ } catch (Exception e) {
+ FileLog.e("tmessages", e);
+ }
+ }
+ });
+ }
+
+ private static void loadMediaDatabase(final long uid, final int offset, final int count, final int max_id, final int type, final int classGuid) {
+ MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages();
+ try {
+ ArrayList loadedUsers = new ArrayList<>();
+ ArrayList fromUser = new ArrayList<>();
+
+ SQLiteCursor cursor;
+
+ if ((int)uid != 0) {
+ if (max_id != 0) {
+ cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, max_id, type, count));
+ } else {
+ cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d,%d", uid, type, offset, count));
+ }
+ } else {
+ if (max_id != 0) {
+ cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v2 as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, max_id, type, count));
+ } else {
+ cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v2 as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND type = %d ORDER BY m.mid ASC LIMIT %d,%d", uid, type, offset, count));
+ }
+ }
+
+ while (cursor.next()) {
+ ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
+ if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
+ TLRPC.Message message = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data, data.readInt32());
+ message.id = cursor.intValue(1);
+ message.dialog_id = uid;
+ if ((int)uid == 0) {
+ message.random_id = cursor.longValue(2);
+ }
+ res.messages.add(message);
+ fromUser.add(message.from_id);
+ }
+ MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
+ }
+ cursor.dispose();
+
+ StringBuilder usersToLoad = new StringBuilder();
+ for (int uid : fromUser) {
+ if (!loadedUsers.contains(uid)) {
+ if (usersToLoad.length() != 0) {
+ usersToLoad.append(",");
+ }
+ usersToLoad.append(uid);
+ loadedUsers.add(uid);
+ }
+ }
+ if (usersToLoad.length() != 0) {
+ MessagesStorage.getInstance().getUsersInternal(usersToLoad.toString(), res.users);
+ }
+ } catch (Exception e) {
+ res.messages.clear();
+ res.chats.clear();
+ res.users.clear();
+ FileLog.e("tmessages", e);
+ } finally {
+ processLoadedMedia(res, uid, offset, count, max_id, type, true, classGuid);
+ }
+ }
+ });
+ }
+
+ private static void putMediaDatabaseInternal(final long uid, final int type, final ArrayList messages) {
+ try {
+ MessagesStorage.getInstance().getDatabase().beginTransaction();
+ SQLitePreparedStatement state2 = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)");
+ for (TLRPC.Message message : messages) {
+ if (canAddMessageToMedia(message)) {
+ state2.requery();
+ ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(message.getObjectSize());
+ message.serializeToStream(data);
+ state2.bindInteger(1, message.id);
+ state2.bindLong(2, uid);
+ state2.bindInteger(3, message.date);
+ state2.bindInteger(4, type);
+ state2.bindByteBuffer(5, data.buffer);
+ state2.step();
+ MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
+ }
+ }
+ state2.dispose();
+ MessagesStorage.getInstance().getDatabase().commitTransaction();
+ } catch (Exception e) {
+ FileLog.e("tmessages", e);
+ }
+ }
+
+ private static void putMediaDatabase(final long uid, final int type, final ArrayList messages) {
+ MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ putMediaDatabaseInternal(uid, type, messages);
+ }
+ });
+ }
+}
diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ConnectionsManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/ConnectionsManager.java
index b3c50750..bc3dd299 100644
--- a/TMessagesProj/src/main/java/org/telegram/messenger/ConnectionsManager.java
+++ b/TMessagesProj/src/main/java/org/telegram/messenger/ConnectionsManager.java
@@ -14,6 +14,7 @@ import android.content.pm.PackageInfo;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
+import android.os.PowerManager;
import android.util.Base64;
import org.telegram.android.AndroidUtilities;
@@ -81,6 +82,8 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
private volatile long nextCallToken = 1;
+ private PowerManager.WakeLock wakeLock = null;
+
private static volatile ConnectionsManager Instance = null;
public static ConnectionsManager getInstance() {
ConnectionsManager localInstance = Instance;
@@ -213,6 +216,14 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
}
Utilities.stageQueue.postRunnable(stageRunnable, 1000);
+
+ try {
+ PowerManager pm = (PowerManager)ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE);
+ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "lock");
+ wakeLock.setReferenceCounted(false);
+ } catch (Exception e) {
+ FileLog.e("tmessages", e);
+ }
}
public int getConnectionState() {
@@ -456,7 +467,7 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
} else {
Datacenter datacenter = new Datacenter();
datacenter.datacenterId = 1;
- datacenter.addAddressAndPort("173.240.5.253", 443);
+ datacenter.addAddressAndPort("149.154.175.10", 443);
datacenters.put(datacenter.datacenterId, datacenter);
datacenter = new Datacenter();
@@ -566,25 +577,33 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
- while (requestQueue.size() != 0) {
- RPCRequest request = requestQueue.get(0);
- requestQueue.remove(0);
+ for (int a = 0; a < requestQueue.size(); a++) {
+ RPCRequest request = requestQueue.get(a);
+ if ((request.flags & RPCRequest.RPCRequestClassWithoutLogin) != 0) {
+ continue;
+ }
+ requestQueue.remove(a);
if (request.completionBlock != null) {
TLRPC.TL_error implicitError = new TLRPC.TL_error();
implicitError.code = -1000;
implicitError.text = "";
request.completionBlock.run(null, implicitError);
}
+ a--;
}
- while (runningRequests.size() != 0) {
- RPCRequest request = runningRequests.get(0);
- runningRequests.remove(0);
+ for (int a = 0; a < runningRequests.size(); a++) {
+ RPCRequest request = runningRequests.get(a);
+ if ((request.flags & RPCRequest.RPCRequestClassWithoutLogin) != 0) {
+ continue;
+ }
+ runningRequests.remove(a);
if (request.completionBlock != null) {
TLRPC.TL_error implicitError = new TLRPC.TL_error();
implicitError.code = -1000;
implicitError.text = "";
request.completionBlock.run(null, implicitError);
}
+ a--;
}
pingIdToDate.clear();
quickAckIdToRequestIds.clear();
@@ -2140,6 +2159,15 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
} else {
if (resultContainer.result instanceof TLRPC.updates_Difference) {
pushMessagesReceived = true;
+ AndroidUtilities.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ if (wakeLock.isHeld()) {
+ FileLog.e("tmessages", "release wakelock");
+ wakeLock.release();
+ }
+ }
+ });
}
if (request.rawRequest instanceof TLRPC.TL_auth_checkPassword) {
UserConfig.setWaitingForPasswordEnter(false);
@@ -2337,9 +2365,25 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
if (paused) {
pushMessagesReceived = false;
}
+ AndroidUtilities.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ FileLog.e("tmessages", "acquire wakelock");
+ wakeLock.acquire(20000);
+ }
+ });
resumeNetworkInternal();
} else {
pushMessagesReceived = true;
+ AndroidUtilities.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ if (wakeLock.isHeld()) {
+ FileLog.e("tmessages", "release wakelock");
+ wakeLock.release();
+ }
+ }
+ });
MessagesController.getInstance().processUpdates((TLRPC.Updates) message, false);
}
} else {
@@ -2444,7 +2488,25 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
}
});
} else if ((connection.transportRequestClass & RPCRequest.RPCRequestClassPush) != 0) {
- FileLog.e("tmessages", "call connection closed");
+ FileLog.e("tmessages", "push connection closed");
+ if (BuildVars.DEBUG_VERSION) {
+ try {
+ ConnectivityManager cm = (ConnectivityManager)ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo[] networkInfos = cm.getAllNetworkInfo();
+ for (int a = 0; a < 2; a++) {
+ if (a >= networkInfos.length) {
+ break;
+ }
+ NetworkInfo info = networkInfos[a];
+ FileLog.e("tmessages", "Network: " + info.getTypeName() + " status: " + info.getState() + " info: " + info.getExtraInfo() + " object: " + info.getDetailedState() + " other: " + info);
+ }
+ if (networkInfos.length == 0) {
+ FileLog.e("tmessages", "no network available");
+ }
+ } catch (Exception e) {
+ FileLog.e("tmessages", "NETWORK STATE GET ERROR", e);
+ }
+ }
sendingPushPing = false;
lastPushPingTime = System.currentTimeMillis() - 60000 * 3 + 4000;
}
@@ -2456,7 +2518,10 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
if (datacenter.authKey != null) {
if ((connection.transportRequestClass & RPCRequest.RPCRequestClassPush) != 0) {
sendingPushPing = false;
- lastPushPingTime = System.currentTimeMillis() - 60000 * 3 + 4000;
+ //lastPushPingTime = System.currentTimeMillis() - 60000 * 3 + 4000; //TODO check this
+ //FileLog.e("tmessages", "schedule push ping in 4 seconds");
+ lastPushPingTime = System.currentTimeMillis();
+ generatePing(datacenter, true);
} else {
if (paused && lastPauseTime != 0) {
lastPauseTime = System.currentTimeMillis();
@@ -2541,6 +2606,7 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
} else {
if (datacenter.authKeyId == 0 || keyId != datacenter.authKeyId) {
FileLog.e("tmessages", "Error: invalid auth key id " + connection);
+ datacenter.switchTo443Port();
connection.suspendConnection(true);
connection.connect();
return;
@@ -2581,6 +2647,7 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
if (!Utilities.arraysEquals(messageKey, 0, realMessageKeyFull, realMessageKeyFull.length - 16)) {
FileLog.e("tmessages", "***** Error: invalid message key");
+ datacenter.switchTo443Port();
connection.suspendConnection(true);
connection.connect();
return;
diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Datacenter.java b/TMessagesProj/src/main/java/org/telegram/messenger/Datacenter.java
index b37088d3..61c63d2a 100644
--- a/TMessagesProj/src/main/java/org/telegram/messenger/Datacenter.java
+++ b/TMessagesProj/src/main/java/org/telegram/messenger/Datacenter.java
@@ -115,6 +115,16 @@ public class Datacenter {
readCurrentAddressAndPortNum();
}
+ public void switchTo443Port() {
+ for (int a = 0; a < addresses.size(); a++) {
+ if (ports.get(addresses.get(a)) == 443) {
+ currentAddressNum = a;
+ currentPortNum = 0;
+ break;
+ }
+ }
+ }
+
public String getCurrentAddress() {
if (addresses.isEmpty()) {
return null;
diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java
index c3200a7f..462098cc 100644
--- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java
+++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java
@@ -55,7 +55,7 @@ public class FileLoadOperation {
private boolean isForceRequest = false;
public static interface FileLoadOperationDelegate {
- public abstract void didFinishLoadingFile(FileLoadOperation operation, File finalFile, File tempFile);
+ public abstract void didFinishLoadingFile(FileLoadOperation operation, File finalFile);
public abstract void didFailedLoadingFile(FileLoadOperation operation, int state);
public abstract void didChangedLoadProgress(FileLoadOperation operation, float progress);
}
@@ -341,9 +341,11 @@ public class FileLoadOperation {
cacheIvTemp.delete();
}
if (cacheFileTemp != null) {
- cacheFileTemp.renameTo(cacheFileFinal);
+ if (!cacheFileTemp.renameTo(cacheFileFinal)) {
+ cacheFileFinal = cacheFileTemp;
+ }
}
- delegate.didFinishLoadingFile(FileLoadOperation.this, cacheFileFinal, cacheFileTemp);
+ delegate.didFinishLoadingFile(FileLoadOperation.this, cacheFileFinal);
}
private void processRequestResult(RequestInfo requestInfo, TLRPC.TL_error error) {
@@ -373,7 +375,8 @@ public class FileLoadOperation {
fiv.seek(0);
fiv.write(iv);
}
- downloadedBytes += requestInfo.response.bytes.limit();
+ int currentBytesSize = requestInfo.response.bytes.limit();
+ downloadedBytes += currentBytesSize;
if (totalBytesCount > 0 && state == stateDownloading) {
delegate.didChangedLoadProgress(FileLoadOperation.this, Math.min(1.0f, (float)downloadedBytes / (float)totalBytesCount));
}
@@ -390,10 +393,14 @@ public class FileLoadOperation {
}
}
- if (totalBytesCount != downloadedBytes && downloadedBytes % downloadChunkSize == 0 || totalBytesCount > 0 && totalBytesCount > downloadedBytes) {
- startDownloadRequest();
- } else {
+ if (currentBytesSize != downloadChunkSize) {
onFinishLoadingFile();
+ } else {
+ if (totalBytesCount != downloadedBytes && downloadedBytes % downloadChunkSize == 0 || totalBytesCount > 0 && totalBytesCount > downloadedBytes) {
+ startDownloadRequest();
+ } else {
+ onFinishLoadingFile();
+ }
}
} catch (Exception e) {
cleanup();
diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java
index 48f5dda0..b0e1ac4e 100644
--- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java
+++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java
@@ -23,7 +23,7 @@ public class FileLoader {
public abstract void fileUploadProgressChanged(String location, float progress, boolean isEncrypted);
public abstract void fileDidUploaded(String location, TLRPC.InputFile inputFile, TLRPC.InputEncryptedFile inputEncryptedFile);
public abstract void fileDidFailedUpload(String location, boolean isEncrypted);
- public abstract void fileDidLoaded(String location, File finalFile, File tempFile);
+ public abstract void fileDidLoaded(String location, File finalFile, int type);
public abstract void fileDidFailedLoad(String location, int state);
public abstract void fileLoadProgressChanged(String location, float progress);
}
@@ -395,53 +395,50 @@ public class FileLoader {
File tempDir = getDirectory(MEDIA_DIR_CACHE);
File storeDir = tempDir;
+ int type = MEDIA_DIR_CACHE;
if (video != null) {
operation = new FileLoadOperation(video);
- if (!cacheOnly) {
- storeDir = getDirectory(MEDIA_DIR_VIDEO);
- }
+ type = MEDIA_DIR_VIDEO;
} else if (location != null) {
operation = new FileLoadOperation(location, locationSize);
- if (!cacheOnly) {
- storeDir = getDirectory(MEDIA_DIR_IMAGE);
- }
+ type = MEDIA_DIR_IMAGE;
} else if (document != null) {
operation = new FileLoadOperation(document);
- if (!cacheOnly) {
- storeDir = getDirectory(MEDIA_DIR_DOCUMENT);
- }
+ type = MEDIA_DIR_DOCUMENT;
} else if (audio != null) {
operation = new FileLoadOperation(audio);
- if (!cacheOnly) {
- storeDir = getDirectory(MEDIA_DIR_AUDIO);
- }
+ type = MEDIA_DIR_AUDIO;
+ }
+ if (!cacheOnly) {
+ storeDir = getDirectory(type);
}
operation.setPaths(storeDir, tempDir);
- final String arg1 = fileName;
+ final String finalFileName = fileName;
+ final int finalType = type;
loadOperationPaths.put(fileName, operation);
operation.setDelegate(new FileLoadOperation.FileLoadOperationDelegate() {
@Override
- public void didFinishLoadingFile(FileLoadOperation operation, File finalFile, File tempFile) {
+ public void didFinishLoadingFile(FileLoadOperation operation, File finalFile) {
if (delegate != null) {
- delegate.fileDidLoaded(arg1, finalFile, tempFile);
+ delegate.fileDidLoaded(finalFileName, finalFile, finalType);
}
- checkDownloadQueue(audio, location, arg1);
+ checkDownloadQueue(audio, location, finalFileName);
}
@Override
public void didFailedLoadingFile(FileLoadOperation operation, int canceled) {
- checkDownloadQueue(audio, location, arg1);
+ checkDownloadQueue(audio, location, finalFileName);
if (delegate != null) {
- delegate.fileDidFailedLoad(arg1, canceled);
+ delegate.fileDidFailedLoad(finalFileName, canceled);
}
}
@Override
public void didChangedLoadProgress(FileLoadOperation operation, float progress) {
if (delegate != null) {
- delegate.fileLoadProgressChanged(arg1, progress);
+ delegate.fileLoadProgressChanged(finalFileName, progress);
}
}
});
@@ -641,7 +638,7 @@ public class FileLoader {
continue;
}
int currentSide = obj.w >= obj.h ? obj.w : obj.h;
- if (closestObject == null || closestObject instanceof TLRPC.TL_photoCachedSize || currentSide <= side && lastSide < currentSide) {
+ if (closestObject == null || obj instanceof TLRPC.TL_photoCachedSize || currentSide <= side && lastSide < currentSide) {
closestObject = obj;
lastSide = currentSide;
}
diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java
index ceee714f..3d01798f 100644
--- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java
+++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java
@@ -160,6 +160,26 @@ public class FileLog {
}
}
+ public static void w(final String tag, final String message) {
+ if (!BuildVars.DEBUG_VERSION) {
+ return;
+ }
+ Log.w(tag, message);
+ if (getInstance().streamWriter != null) {
+ getInstance().logQueue.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " W/" + tag + ": " + message + "\n");
+ getInstance().streamWriter.flush();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ }
+
public static void cleanupLogs() {
ArrayList uris = new ArrayList();
File sdCard = ApplicationLoader.applicationContext.getExternalFilesDir(null);
diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/TcpConnection.java b/TMessagesProj/src/main/java/org/telegram/messenger/TcpConnection.java
index a2afb811..406c0513 100644
--- a/TMessagesProj/src/main/java/org/telegram/messenger/TcpConnection.java
+++ b/TMessagesProj/src/main/java/org/telegram/messenger/TcpConnection.java
@@ -299,6 +299,9 @@ public class TcpConnection extends ConnectionContext {
if (canReuse) {
BuffersStorage.getInstance().reuseFreeBuffer(buff);
}
+ if (BuildConfig.DEBUG) {
+ FileLog.e("tmessages", TcpConnection.this + " disconnected, don't send data");
+ }
return;
}
diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java
index 629942b4..188fdf76 100644
--- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java
+++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java
@@ -48,6 +48,7 @@ public class ActionBar extends FrameLayout {
private boolean allowOverlayTitle;
private CharSequence lastTitle;
private boolean showingOverlayTitle;
+ private boolean castShadows = true;
protected boolean isSearchFieldVisible;
protected int itemsBackgroundResourceId;
@@ -485,6 +486,14 @@ public class ActionBar extends FrameLayout {
}
}
+ public void setCastShadows(boolean value) {
+ castShadows = value;
+ }
+
+ public boolean getCastShadows() {
+ return castShadows;
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java
index c8c6658d..bf295a53 100644
--- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java
+++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java
@@ -68,8 +68,10 @@ public class ActionBarLayout extends FrameLayout {
continue;
}
if (view instanceof ActionBar && view.getVisibility() == VISIBLE) {
- actionBarHeight = view.getMeasuredHeight();
- wasActionBar = true;
+ if (((ActionBar) view).getCastShadows()) {
+ actionBarHeight = view.getMeasuredHeight();
+ wasActionBar = true;
+ }
break;
}
}
@@ -597,7 +599,7 @@ public class ActionBarLayout extends FrameLayout {
if (useAlphaAnimations && fragmentsStack.size() == 1) {
presentFragmentInternalRemoveOld(removeLast, currentFragment);
- ArrayList