5980 lines
264 KiB
ObjectPascal
5980 lines
264 KiB
ObjectPascal
/// SQLite3 Database engine direct access
|
|
// - this unit is a part of the freeware Synopse mORMot framework,
|
|
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
|
|
unit SynSQLite3;
|
|
|
|
{
|
|
This file is part of Synopse mORMot framework.
|
|
|
|
Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez
|
|
Synopse Informatique - https://synopse.info
|
|
|
|
*** BEGIN LICENSE BLOCK *****
|
|
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
The contents of this file are subject to the Mozilla Public License Version
|
|
1.1 (the "License"); you may not use this file except in compliance with
|
|
the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
for the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is Synopse mORMot framework.
|
|
|
|
The Initial Developer of the Original Code is Arnaud Bouchez.
|
|
|
|
Portions created by the Initial Developer are Copyright (C) 2022
|
|
the Initial Developer. All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
- Alfred Glaenzer (alf)
|
|
- Eric Grange
|
|
- Vaclav
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
in which case the provisions of the GPL or the LGPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of either the GPL or the LGPL, and not to allow others to
|
|
use your version of this file under the terms of the MPL, indicate your
|
|
decision by deleting the provisions above and replace them with the notice
|
|
and other provisions required by the GPL or the LGPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
|
|
SQLite3 3.38.2 database engine
|
|
********************************
|
|
|
|
Brand new SQLite3 library to be used with Delphi/FPC
|
|
- FLEXIBLE: in process, local or remote access (JSON RESTFUL HTTP server)
|
|
- STANDARD: full UTF-8 and Unicode, SQLite3 engine (enhanced but not hacked)
|
|
- SECURE: tested, multi-thread oriented, atomic commit, encryption ready
|
|
- SIMPLE: staticaly linked into a single Delphi unit (via SynSQLite3Static)
|
|
or standard external dll - via TSQLite3LibraryDynamic
|
|
- LIGHT: use native classes, not TDataSet nor TDataSource
|
|
- SMART: queries share a JSON-based memory cache for immediate response
|
|
- FAST: tuned pascal and i386 assembler code with use of FastMM4/SynScaleMM
|
|
- FREE: full source code provided, with permissive licence
|
|
|
|
- unitary tested with provided regression tests
|
|
- includes RTREE extension for doing very fast range queries
|
|
- can include FTS3 full text search engine (MATCH operator) after sqlite3.c
|
|
recompile (by default, FTS3 is not compiled, saving more than 50KB of code)
|
|
- uses only newest API (sqlite3_prepare_v2) and follow SQLite3 official documentation
|
|
- uses purely UTF-8 encoded strings: Ansi/Unicode conversion routines included,
|
|
Delphi 2009 ready (but Unicode works very well with older Delphi versions)
|
|
- optional on the fly fast encryption of the data on disk
|
|
- use an optional and efficient caching mechanism (TSynCache based) for the
|
|
most used SELECT statements, using our TSQLTableJSON as fast data source
|
|
- record retrieval from its ID is speed up via SQL statement preparation
|
|
- uses ISO 8601:2004 format to properly handle date/time values in TEXT field
|
|
- can be easily updated from official SQLite3 source code (see comments in
|
|
the source code of this unit)
|
|
- compiled without thread mutex: the caller has to be thread-safe aware
|
|
(this is faster on most configuration, since mutex has to be acquired once):
|
|
low level sqlite3_*() functions are not thread-safe, as TSQLRequest and
|
|
TSQLBlobStream which just wrap them; but TSQLDataBase is thread-safe, as
|
|
mORMot's TSQLTableDB/TSQLRestServerDB/TSQLRestClientDB which use TSQLDataBase
|
|
- compiled with SQLITE_OMIT_SHARED_CACHE define
|
|
- compatible with our LVCL 'Very LIGHT VCL routines' framework
|
|
for building light but fast GUI servers software
|
|
|
|
}
|
|
|
|
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
|
|
|
|
interface
|
|
|
|
uses
|
|
{$ifdef MSWINDOWS}
|
|
Windows,
|
|
{$ifdef FPC}
|
|
dynlibs,
|
|
{$endif}
|
|
{$else}
|
|
{$ifdef KYLIX3}
|
|
LibC,
|
|
SyncObjs,
|
|
SynKylix,
|
|
{$endif}
|
|
{$ifdef FPC}
|
|
{$ifdef Linux}
|
|
SynFPCLinux,
|
|
{$ifdef BSDNOTDARWIN}
|
|
dl,
|
|
{$else}
|
|
DynLibs,
|
|
{$endif}
|
|
{$endif}
|
|
{$endif}
|
|
{$endif}
|
|
SysUtils,
|
|
Classes,
|
|
{$ifndef LVCL}
|
|
Contnrs,
|
|
{$endif}
|
|
SynCommons,
|
|
SynTable,
|
|
SynLog;
|
|
|
|
|
|
{ ************ direct access to sqlite3.c / sqlite3.obj consts and functions }
|
|
|
|
{$ifdef BSD}
|
|
{$linklib c}
|
|
{$linklib pthread}
|
|
{$endif}
|
|
|
|
{$ifdef FPC}
|
|
{$packrecords C}
|
|
{$packenum 4}
|
|
{$endif}
|
|
|
|
type
|
|
/// internaly store the SQLite3 database handle
|
|
TSQLite3DB = type PtrUInt;
|
|
|
|
/// internaly store the SQLite3 statement handle
|
|
// - This object is variously known as a "prepared statement" or a "compiled
|
|
// SQL statement" or simply as a "statement".
|
|
// - Create the object using sqlite3.prepare_v2() or a related function.
|
|
// - Bind values to host parameters using the sqlite3.bind_*() interfaces.
|
|
// - Run the SQL by calling sqlite3.step() one or more times.
|
|
// - Reset the statement using sqlite3.reset() then go back to "Bind" step.
|
|
// Do this zero or more times.
|
|
// - Destroy the object using sqlite3.finalize().
|
|
TSQLite3Statement = type PtrUInt;
|
|
|
|
/// internaly store the SQLite3 blob handle
|
|
TSQLite3Blob = type PtrUInt;
|
|
|
|
/// internaly store a SQLite3 Dynamically Typed Value Object
|
|
// - SQLite uses the sqlite3.value object to represent all values that
|
|
// can be stored in a database table, which are mapped to this TSQLite3Value type
|
|
// - SQLite uses dynamic typing for the values it stores
|
|
// - Values stored in sqlite3.value objects can be integers, floating point
|
|
// values, strings, BLOBs, or NULL
|
|
TSQLite3Value = type PtrUInt;
|
|
|
|
/// internal store a SQLite3 Function Context Object
|
|
// - The context in which an SQL function executes is stored in an sqlite3.context
|
|
// object, which is mapped to this TSQLite3FunctionContext type
|
|
// - A pointer to an sqlite3.context object is always first parameter to
|
|
// application-defined SQL functions, i.e. a TSQLFunctionFunc prototype
|
|
TSQLite3FunctionContext = type PtrUInt;
|
|
|
|
/// internaly store a SQLite3 Backup process handle
|
|
TSQLite3Backup = type PtrUInt;
|
|
|
|
/// internaly store of SQLite3 values, as used by TSQLFunctionFunc
|
|
TSQLite3ValueArray = array[0..63] of TSQLite3Value;
|
|
|
|
const
|
|
{$ifdef MSWINDOWS}
|
|
{$ifdef CPU64}
|
|
// see https://synopse.info/files/SQLite3-64.7z
|
|
SQLITE_LIBRARY_DEFAULT_NAME = 'sqlite3-64.dll';
|
|
{$else}
|
|
SQLITE_LIBRARY_DEFAULT_NAME = 'sqlite3.dll';
|
|
{$endif}
|
|
{$else}
|
|
{$ifdef Linux}
|
|
{$ifdef Android}
|
|
SQLITE_LIBRARY_DEFAULT_NAME = 'libsqlite.so';
|
|
{$else}
|
|
{$ifdef Darwin}
|
|
SQLITE_LIBRARY_DEFAULT_NAME = 'libsqlite3.dylib';
|
|
{$else}
|
|
SQLITE_LIBRARY_DEFAULT_NAME = 'libsqlite3.so.0';
|
|
{$endif}
|
|
{$endif}
|
|
{$else}
|
|
SQLITE_LIBRARY_DEFAULT_NAME = 'libsqlite3.so';
|
|
{$endif}
|
|
{$endif}
|
|
|
|
/// internal SQLite3 type as Integer
|
|
SQLITE_INTEGER = 1;
|
|
/// internal SQLite3 type as Floating point value
|
|
SQLITE_FLOAT = 2;
|
|
/// internal SQLite3 type as Text
|
|
SQLITE_TEXT = 3;
|
|
/// internal SQLite3 type as Blob
|
|
SQLITE_BLOB = 4;
|
|
/// internal SQLite3 type as NULL
|
|
SQLITE_NULL = 5;
|
|
|
|
/// text is UTF-8 encoded
|
|
SQLITE_UTF8 = 1;
|
|
/// text is UTF-16 LE encoded
|
|
SQLITE_UTF16LE = 2;
|
|
/// text is UTF-16 BE encoded
|
|
SQLITE_UTF16BE = 3;
|
|
/// text is UTF-16 encoded, using the system native byte order
|
|
SQLITE_UTF16 = 4;
|
|
/// sqlite3.create_function don't care about text encoding
|
|
SQLITE_ANY = 5;
|
|
/// used by sqlite3.create_collation() only
|
|
SQLITE_UTF16_ALIGNED = 8;
|
|
|
|
|
|
/// sqlite_exec() return code: no error occured
|
|
SQLITE_OK = 0;
|
|
/// sqlite_exec() return code: SQL error or missing database - legacy generic code
|
|
SQLITE_ERROR = 1;
|
|
/// sqlite_exec() return code: An internal logic error in SQLite
|
|
SQLITE_INTERNAL = 2;
|
|
/// sqlite_exec() return code: Access permission denied
|
|
SQLITE_PERM = 3;
|
|
/// sqlite_exec() return code: Callback routine requested an abort
|
|
SQLITE_ABORT = 4;
|
|
/// sqlite_exec() return code: The database file is locked
|
|
SQLITE_BUSY = 5;
|
|
/// sqlite_exec() return code: A table in the database is locked
|
|
SQLITE_LOCKED = 6;
|
|
/// sqlite_exec() return code: A malloc() failed
|
|
SQLITE_NOMEM = 7;
|
|
/// sqlite_exec() return code: Attempt to write a readonly database
|
|
SQLITE_READONLY = 8;
|
|
/// sqlite_exec() return code: Operation terminated by sqlite3.interrupt()
|
|
SQLITE_INTERRUPT = 9;
|
|
/// sqlite_exec() return code: Some kind of disk I/O error occurred
|
|
SQLITE_IOERR = 10;
|
|
/// sqlite_exec() return code: The database disk image is malformed
|
|
SQLITE_CORRUPT = 11;
|
|
/// sqlite_exec() return code: (Internal Only) Table or record not found
|
|
SQLITE_NOTFOUND = 12;
|
|
/// sqlite_exec() return code: Insertion failed because database is full
|
|
SQLITE_FULL = 13;
|
|
/// sqlite_exec() return code: Unable to open the database file
|
|
SQLITE_CANTOPEN = 14;
|
|
/// sqlite_exec() return code: (Internal Only) Database lock protocol error
|
|
SQLITE_PROTOCOL = 15;
|
|
/// sqlite_exec() return code: Database is empty
|
|
SQLITE_EMPTY = 16;
|
|
/// sqlite_exec() return code: The database schema changed, and unable to be recompiled
|
|
SQLITE_SCHEMA = 17;
|
|
/// sqlite_exec() return code: Too much data for one row of a table
|
|
SQLITE_TOOBIG = 18;
|
|
/// sqlite_exec() return code: Abort due to contraint violation
|
|
SQLITE_CONSTRAINT = 19;
|
|
/// sqlite_exec() return code: Data type mismatch
|
|
SQLITE_MISMATCH = 20;
|
|
/// sqlite_exec() return code: Library used incorrectly
|
|
SQLITE_MISUSE = 21;
|
|
/// sqlite_exec() return code: Uses OS features not supported on host
|
|
SQLITE_NOLFS = 22;
|
|
/// sqlite_exec() return code: Authorization denied
|
|
SQLITE_AUTH = 23;
|
|
/// sqlite_exec() return code: Auxiliary database format error
|
|
SQLITE_FORMAT = 24;
|
|
/// sqlite_exec() return code: 2nd parameter to sqlite3.bind out of range
|
|
SQLITE_RANGE = 25;
|
|
/// sqlite_exec() return code: File opened that is not a database file
|
|
SQLITE_NOTADB = 26;
|
|
|
|
/// sqlite3.step() return code: another result row is ready
|
|
SQLITE_ROW = 100;
|
|
/// sqlite3.step() return code: has finished executing
|
|
SQLITE_DONE = 101;
|
|
|
|
/// possible error codes for sqlite_exec() and sqlite3.step()
|
|
// - as verified by sqlite3_check()
|
|
SQLITE_ERRORS = [SQLITE_ERROR..SQLITE_ROW-1];
|
|
|
|
/// The database is opened in read-only mode
|
|
// - if the database does not already exist, an error is returned
|
|
// - Ok for sqlite3.open_v2()
|
|
SQLITE_OPEN_READONLY = $00000001;
|
|
/// The database is opened for reading and writing if possible, or reading
|
|
// only if the file is write protected by the operating system
|
|
// - In either case the database must already exist, otherwise an error is
|
|
// returned
|
|
// - Ok for sqlite3.open_v2()
|
|
SQLITE_OPEN_READWRITE = $00000002;
|
|
/// In conjunction with SQLITE_OPEN_READWRITE, optionally create the database
|
|
// file if it does not exist
|
|
// - The database is opened for reading and writing if possible, or reading
|
|
// only if the file is write protected by the operating system
|
|
// - In either case the database must already exist, otherwise an error is returned
|
|
SQLITE_OPEN_CREATE = $00000004;
|
|
/// URI filename interpretation is enabled if the SQLITE_OPEN_URI flag is set
|
|
// in the fourth argument to sqlite3.open_v2(), or if it has been enabled
|
|
// globally using the SQLITE_CONFIG_URI option with the sqlite3.config() method
|
|
// or by the SQLITE_USE_URI compile-time option.
|
|
// - As of SQLite version 3.7.7, URI filename interpretation is turned off by
|
|
// default, but future releases of SQLite might enable URI filename
|
|
// interpretation by default
|
|
// - Ok for sqlite3.open_v2(), in conjuction with SQLITE_OPEN_READONLY,
|
|
// SQLITE_OPEN_READWRITE, (SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE)
|
|
SQLITE_OPEN_URI = $00000040; // Ok for sqlite3_open_v2()
|
|
/// If the SQLITE_OPEN_NOMUTEX flag is set, then the database will remain in
|
|
// memory
|
|
// - Ok for sqlite3.open_v2(), in conjuction with SQLITE_OPEN_READONLY,
|
|
// SQLITE_OPEN_READWRITE, (SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE)
|
|
SQLITE_OPEN_MEMORY = $00000080; // Ok for sqlite3_open_v2()
|
|
/// If the SQLITE_OPEN_NOMUTEX flag is set, then the database connection opens
|
|
// in the multi-thread threading mode as long as the single-thread mode has
|
|
// not been set at compile-time or start-time
|
|
// - Ok for sqlite3.open_v2(), in conjuction with SQLITE_OPEN_READONLY,
|
|
// SQLITE_OPEN_READWRITE, (SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE)
|
|
SQLITE_OPEN_NOMUTEX = $00008000; // Ok for sqlite3_open_v2()
|
|
/// If the SQLITE_OPEN_FULLMUTEX flag is set then the database connection opens
|
|
// in the serialized threading mode unless single-thread was previously selected
|
|
// at compile-time or start-time
|
|
// - Ok for sqlite3.open_v2(), in conjuction with SQLITE_OPEN_READONLY,
|
|
// SQLITE_OPEN_READWRITE, (SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE)
|
|
SQLITE_OPEN_FULLMUTEX = $00010000; // Ok for sqlite3_open_v2()
|
|
/// The SQLITE_OPEN_SHAREDCACHE flag causes the database connection to be
|
|
// eligible to use shared cache mode, regardless of whether or not shared
|
|
// cache is enabled using sqlite3.enable_shared_cache()
|
|
// - Ok for sqlite3.open_v2(), in conjuction with SQLITE_OPEN_READONLY,
|
|
// SQLITE_OPEN_READWRITE, (SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE)
|
|
SQLITE_OPEN_SHAREDCACHE = $00020000; // Ok for sqlite3_open_v2()
|
|
/// The SQLITE_OPEN_PRIVATECACHE flag causes the database connection to not
|
|
// participate in shared cache mode even if it is enabled
|
|
// - Ok for sqlite3.open_v2(), in conjuction with SQLITE_OPEN_READONLY,
|
|
// SQLITE_OPEN_READWRITE, (SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE)
|
|
SQLITE_OPEN_PRIVATECACHE = $00040000;
|
|
|
|
{
|
|
SQLITE_OPEN_DELETEONCLOSE = $00000008; // VFS only
|
|
SQLITE_OPEN_EXCLUSIVE = $00000010; // VFS only
|
|
SQLITE_OPEN_AUTOPROXY = $00000020; // VFS only
|
|
SQLITE_OPEN_MAIN_DB = $00000100; // VFS only
|
|
SQLITE_OPEN_TEMP_DB = $00000200; // VFS only
|
|
SQLITE_OPEN_TRANSIENT_DB = $00000400; // VFS only
|
|
SQLITE_OPEN_MAIN_JOURNAL = $00000800; // VFS only
|
|
SQLITE_OPEN_TEMP_JOURNAL = $00001000; // VFS only
|
|
SQLITE_OPEN_SUBJOURNAL = $00002000; // VFS only
|
|
SQLITE_OPEN_MASTER_JOURNAL = $00004000; // VFS only
|
|
SQLITE_OPEN_WAL = $00080000; // VFS only
|
|
}
|
|
|
|
/// DestroyPtr set to SQLITE_STATIC if data is constant and will never change
|
|
// - SQLite assumes that the text or BLOB result is in constant space and
|
|
// does not copy the content of the parameter nor call a destructor on the
|
|
// content when it has finished using that result
|
|
SQLITE_STATIC = pointer(0);
|
|
|
|
/// DestroyPtr set to SQLITE_TRANSIENT for SQLite3 to make a private copy of
|
|
// the data into space obtained from from sqlite3.malloc() before it returns
|
|
// - this is the default behavior in our framework
|
|
// - note that we discovered that under Win64, sqlite3.result_text() expects
|
|
// SQLITE_TRANSIENT_VIRTUALTABLE=pointer(integer(-1)) and not pointer(-1)
|
|
SQLITE_TRANSIENT = pointer(-1);
|
|
|
|
/// DestroyPtr set to SQLITE_TRANSIENT_VIRTUALTABLE for setting results to
|
|
// SQlite3 virtual tables columns
|
|
// - due to a bug of the SQlite3 engine under Win64
|
|
SQLITE_TRANSIENT_VIRTUALTABLE = pointer(integer(-1));
|
|
|
|
/// pseudo database file name used to create an in-memory database
|
|
// - an SQLite database is normally stored in a single ordinary disk file -
|
|
// however, in certain circumstances, the database might be stored in memory,
|
|
// if you pass SQLITE_MEMORY_DATABASE_NAME to TSQLDatabase.Create() instead of
|
|
// a real disk file name
|
|
// - this instance will cease to exist as soon as the database connection
|
|
// is closed, i.e. when calling TSQLDatabase.Free
|
|
// - every ':memory:' database is distinct from every other - so, creating two
|
|
// TSQLDatabase instances each with the filename SQLITE_MEMORY_DATABASE_NAME
|
|
// will create two independent in-memory databases
|
|
SQLITE_MEMORY_DATABASE_NAME = ':memory:';
|
|
|
|
SQLITE_CONFIG_SINGLETHREAD = 1;
|
|
SQLITE_CONFIG_MULTITHREAD = 2;
|
|
SQLITE_CONFIG_SERIALIZED = 3;
|
|
SQLITE_CONFIG_MALLOC = 4;
|
|
SQLITE_CONFIG_GETMALLOC = 5;
|
|
SQLITE_CONFIG_SCRATCH = 6;
|
|
SQLITE_CONFIG_PAGECACHE = 7;
|
|
SQLITE_CONFIG_HEAP = 8;
|
|
SQLITE_CONFIG_MEMSTATUS = 9;
|
|
SQLITE_CONFIG_MUTEX = 10;
|
|
SQLITE_CONFIG_GETMUTEX = 11;
|
|
SQLITE_CONFIG_LOOKASIDE = 13;
|
|
SQLITE_CONFIG_PCACHE = 14;
|
|
SQLITE_CONFIG_GETPCACHE = 15;
|
|
SQLITE_CONFIG_LOG = 16;
|
|
SQLITE_CONFIG_URI = 17;
|
|
SQLITE_CONFIG_PCACHE2 = 18;
|
|
SQLITE_CONFIG_GETPCACHE2 = 19;
|
|
SQLITE_CONFIG_COVERING_INDEX_SCAN = 20;
|
|
SQLITE_CONFIG_SQLLOG = 21;
|
|
SQLITE_CONFIG_MMAP_SIZE = 22;
|
|
SQLITE_CONFIG_WIN32_HEAPSIZE = 23;
|
|
|
|
SQLITE_DBCONFIG_LOOKASIDE = 1001;
|
|
SQLITE_DBCONFIG_ENABLE_FKEY = 1002;
|
|
SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003;
|
|
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004;
|
|
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005;
|
|
|
|
type
|
|
/// type for a custom destructor for the text or BLOB content
|
|
// - set to @sqlite3InternalFree if a Value must be released via Freemem()
|
|
// - set to @sqlite3InternalFreeObject if a Value must be released via
|
|
// TObject(p).Free
|
|
TSQLDestroyPtr = procedure(p: pointer); cdecl;
|
|
|
|
/// SQLite3 collation (i.e. sort and comparison) function prototype
|
|
// - this function MUST use s1Len and s2Len parameters during the comparison:
|
|
// s1 and s2 are not zero-terminated
|
|
// - used by sqlite3.create_collation low-level function
|
|
TSQLCollateFunc = function(CollateParam: pointer; s1Len: integer; s1: pointer;
|
|
s2Len: integer; s2: pointer) : integer; cdecl;
|
|
|
|
/// SQLite3 user function or aggregate callback prototype
|
|
// - argc is the number of supplied parameters, which are available in argv[]
|
|
// (you can call ErrorWrongNumberOfArgs(Context) in case of unexpected number)
|
|
// - use sqlite3.value_*(argv[*]) functions to retrieve a parameter value
|
|
// - then set the result using sqlite3.result_*(Context,*) functions
|
|
TSQLFunctionFunc = procedure(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
|
|
/// SQLite3 user final aggregate callback prototype
|
|
TSQLFunctionFinal = procedure(Context: TSQLite3FunctionContext); cdecl;
|
|
|
|
/// SQLite3 callback prototype to handle SQLITE_BUSY errors
|
|
// - The first argument to the busy handler is a copy of the user 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 this locking event.
|
|
// - If the busy callback returns 0, then no additional attempts are made to
|
|
// access the database and SQLITE_BUSY or SQLITE_IOERR_BLOCKED is returned.
|
|
// - If the callback returns non-zero, then another attempt is made to open
|
|
// the database for reading and the cycle repeats.
|
|
TSQLBusyHandler = function(user: pointer; count: integer): integer;
|
|
cdecl;
|
|
|
|
PFTSMatchInfo = ^TFTSMatchInfo;
|
|
/// map the matchinfo function returned BLOB value
|
|
// - i.e. the default 'pcx' layout, for both FTS3 and FTS4
|
|
// - see http://www.sqlite.org/fts3.html#matchinfo
|
|
// - used for the FTS3/FTS4 ranking of results by TSQLRest.FTSMatch method
|
|
// and the internal RANK() function as proposed in
|
|
// http://www.sqlite.org/fts3.html#appendix_a
|
|
TFTSMatchInfo = packed record
|
|
nPhrase: integer;
|
|
nCol: integer;
|
|
hits: array[1..9] of record
|
|
this_row: integer;
|
|
all_rows: integer;
|
|
docs_with_hits: integer;
|
|
end;
|
|
end;
|
|
|
|
PSQLite3Module = ^TSQLite3Module;
|
|
PSQLite3VTab = ^TSQLite3VTab;
|
|
PSQLite3VTabCursor = ^TSQLite3VTabCursor;
|
|
|
|
/// records WHERE clause constraints of the form "column OP expr"
|
|
// - Where "column" is a column in the virtual table, OP is an operator like
|
|
// "=" or "<", and EXPR is an arbitrary expression
|
|
// - So, for example, if the WHERE clause contained a term like this:
|
|
// $ a = 5
|
|
// Then one of the constraints would be on the "a" column with operator "="
|
|
// and an expression of "5"
|
|
// - For example, if the WHERE clause contained something like this:
|
|
// $ x BETWEEN 10 AND 100 AND 999>y
|
|
// The query optimizer might translate this into three separate constraints:
|
|
// ! x >= 10
|
|
// ! x <= 100
|
|
// ! y < 999
|
|
TSQLite3IndexConstraint = record
|
|
/// Column on left-hand side of constraint
|
|
// - The first column of the virtual table is column 0
|
|
// - The ROWID of the virtual table is column -1
|
|
// - Hidden columns are counted when determining the column index.
|
|
iColumn: integer;
|
|
/// Constraint operator
|
|
// - OP is =, <, <=, >, or >= using one of the SQLITE_INDEX_CONSTRAINT_* values
|
|
op: byte;
|
|
/// True if this constraint is usable
|
|
// - The aConstraint[] array contains information about all constraints that
|
|
// apply to the virtual table. But some of the constraints might not be usable
|
|
// because of the way tables are ordered in a join. The xBestIndex method
|
|
// must therefore only consider constraints that have a usable flag which is
|
|
// true, and just ignore contraints with usable set to false
|
|
usable: bytebool;
|
|
/// Used internally - xBestIndex() should ignore this field
|
|
iTermOffset: integer;
|
|
end;
|
|
PSQLite3IndexConstraintArray = ^TSQLite3IndexConstraintArray;
|
|
TSQLite3IndexConstraintArray = array[0..MaxInt div SizeOf(TSQLite3IndexConstraint)-1] of TSQLite3IndexConstraint;
|
|
|
|
/// ORDER BY clause, one item per column
|
|
TSQLite3IndexOrderBy = record
|
|
/// Column number
|
|
// - The first column of the virtual table is column 0
|
|
// - The ROWID of the virtual table is column -1
|
|
// - Hidden columns are counted when determining the column index.
|
|
iColumn: integer;
|
|
/// True for DESC. False for ASC.
|
|
desc: bytebool;
|
|
end;
|
|
PSQLite3IndexOrderByArray = ^TSQLite3IndexOrderByArray;
|
|
TSQLite3IndexOrderByArray = array[0..MaxInt div SizeOf(TSQLite3IndexOrderBy)-1] of TSQLite3IndexOrderBy;
|
|
|
|
/// define what information is to be passed to xFilter() for a given WHERE
|
|
// clause constraint of the form "column OP expr"
|
|
TSQLite3IndexConstraintUsage = record
|
|
/// If argvIndex>0 then the right-hand side of the corresponding
|
|
// aConstraint[] is evaluated and becomes the argvIndex-th entry in argv
|
|
// - Exactly one entry should be set to 1, another to 2, another to 3, and
|
|
// so forth up to as many or as few as the xBestIndex() method wants.
|
|
// - The EXPR of the corresponding constraints will then be passed in as
|
|
// the argv[] parameters to xFilter()
|
|
// - For example, if the aConstraint[3].argvIndex is set to 1, then when
|
|
// xFilter() is called, the argv[0] passed to xFilter will have the EXPR
|
|
// value of the aConstraint[3] constraint.
|
|
argvIndex: Integer;
|
|
/// If omit is true, then the constraint is assumed to be fully handled
|
|
// by the virtual table and is not checked again by SQLite
|
|
// - By default, the SQLite core double checks all constraints on each
|
|
// row of the virtual table that it receives. If such a check is redundant,
|
|
// xBestFilter() method can suppress that double-check by setting this field
|
|
omit: bytebool;
|
|
end;
|
|
PSQLite3IndexConstraintUsageArray = ^TSQLite3IndexConstraintUsageArray;
|
|
TSQLite3IndexConstraintUsageArray = array[0..MaxInt div SizeOf(TSQLite3IndexConstraintUsage) - 1] of TSQLite3IndexConstraintUsage;
|
|
|
|
/// Structure used as part of the virtual table interface to pass information
|
|
// into and receive the reply from the xBestIndex() method of a virtual table module
|
|
// - Outputs fields will be passed as parameter to the xFilter() method, and
|
|
// will be initialized to zero by SQLite
|
|
// - For instance, xBestIndex() method fills the idxNum and idxStr fields with
|
|
// information that communicates an indexing strategy to the xFilter method.
|
|
// The information in idxNum and idxStr is arbitrary as far as the SQLite core
|
|
// is concerned. The SQLite core just copies the information through to the
|
|
// xFilter() method. Any desired meaning can be assigned to idxNum and idxStr
|
|
// as long as xBestIndex() and xFilter() agree on what that meaning is.
|
|
// Use the SetInfo() method of this object in order to make a temporary copy
|
|
// of any needed data.
|
|
TSQLite3IndexInfo = record
|
|
/// input: Number of entries in aConstraint array
|
|
nConstraint: integer;
|
|
/// input: List of WHERE clause constraints of the form "column OP expr"
|
|
aConstraint: PSQLite3IndexConstraintArray;
|
|
/// input: Number of terms in the aOrderBy array
|
|
nOrderBy: integer;
|
|
/// input: List of ORDER BY clause, one per column
|
|
aOrderBy: PSQLite3IndexOrderByArray;
|
|
/// output: filled by xBestIndex() method with information about what
|
|
// parameters to pass to xFilter() method
|
|
// - has the same number of items than the aConstraint[] array
|
|
// - should set the aConstraintUsage[].argvIndex to have the corresponding
|
|
// argument in xFilter() argc/argv[] expression list
|
|
aConstraintUsage: PSQLite3IndexConstraintUsageArray;
|
|
/// output: Number used to identify the index
|
|
idxNum: integer;
|
|
/// output: String, possibly obtained from sqlite3.malloc()
|
|
// - may contain any variable-length data or class/record content, as
|
|
// necessary
|
|
idxStr: PAnsiChar;
|
|
/// output: Free idxStr using sqlite3.free() if true (=1)
|
|
needToFreeIdxStr: integer;
|
|
/// output: True (=1) if output is already ordered
|
|
// - i.e. if the virtual table will output rows in the order specified
|
|
// by the ORDER BY clause
|
|
// - if False (=0), will indicate to the SQLite core that it will need to
|
|
// do a separate sorting pass over the data after it comes out
|
|
// of the virtual table
|
|
orderByConsumed: integer;
|
|
/// output: Estimated cost of using this index
|
|
// - Should be set to the estimated number of disk access operations
|
|
// required to execute this query against the virtual table
|
|
// - The SQLite core will often call xBestIndex() multiple times with
|
|
// different constraints, obtain multiple cost estimates, then choose the
|
|
// query plan that gives the lowest estimate
|
|
estimatedCost: Double;
|
|
/// output: Estimated number of rows returned (since 3.8.2)
|
|
// - may be set to an estimate of the number of rows returned by the
|
|
// proposed query plan. If this value is not explicitly set, the default
|
|
// estimate of 25 rows is used
|
|
estimatedRows: Int64;
|
|
/// output: Mask of SQLITE_INDEX_SCAN_* flags (since 3.9.0)
|
|
// - may be set to SQLITE_INDEX_SCAN_UNIQUE to indicate that the virtual
|
|
// table will return only zero or one rows given the input constraints.
|
|
// Additional bits of the idxFlags field might be understood in later
|
|
// versions of SQLite
|
|
idxFlags: Integer;
|
|
/// input: Mask of columns used by statement (since 3.10.0)
|
|
// - indicates which fields of the virtual table are actually used by the
|
|
// statement being prepared. If the lowest bit of colUsed is set, that means
|
|
// that the first column is used. The second lowest bit corresponds to the
|
|
// second column. And so forth. If the most significant bit of colUsed is
|
|
// set, that means that one or more columns other than the first 63 columns
|
|
// are used.
|
|
// - If column usage information is needed by the xFilter method, then the
|
|
// required bits must be encoded into either the idxNum or idxStr output fields
|
|
colUsed: UInt64;
|
|
end;
|
|
|
|
/// Virtual Table Instance Object
|
|
// - Every virtual table module implementation uses a subclass of this object
|
|
// to describe a particular instance of the virtual table.
|
|
// - Each subclass will be tailored to the specific needs of the module
|
|
// implementation. The purpose of this superclass is to define certain fields
|
|
// that are common to all module implementations. This structure therefore
|
|
// contains a pInstance field, which will be used to store a class instance
|
|
// handling the virtual table as a pure Delphi class: the TSQLVirtualTableModule
|
|
// class will use it internaly
|
|
TSQLite3VTab = record
|
|
/// The module for this virtual table
|
|
pModule: PSQLite3Module;
|
|
/// no longer used
|
|
nRef: integer;
|
|
/// Error message from sqlite3.mprintf()
|
|
// - Virtual tables methods can set an error message by assigning a string
|
|
// obtained from sqlite3.mprintf() to zErrMsg.
|
|
// - The method should take care that any prior string is freed by a call
|
|
// to sqlite3.free() prior to assigning a new string to zErrMsg.
|
|
// - After the error message is delivered up to the client application,
|
|
// the string will be automatically freed by sqlite3.free() and the zErrMsg
|
|
// field will be zeroed.
|
|
zErrMsg: PUTF8Char;
|
|
/// this will be used to store a Delphi class instance handling the Virtual Table
|
|
pInstance: TObject;
|
|
end;
|
|
|
|
/// Virtual Table Cursor Object
|
|
// - Every virtual table module implementation uses a subclass of the following
|
|
// structure to describe cursors that point into the virtual table and are
|
|
// used to loop through the virtual table.
|
|
// - Cursors are created using the xOpen method of the module and are destroyed
|
|
// by the xClose method. Cursors are used by the xFilter, xNext, xEof, xColumn,
|
|
// and xRowid methods of the module.
|
|
// - Each module implementation will define the content of a cursor structure
|
|
// to suit its own needs.
|
|
// - This superclass exists in order to define fields of the cursor that are
|
|
// common to all implementationsThis structure therefore contains a pInstance
|
|
// field, which will be used to store a class instance handling the virtual
|
|
// table as a pure Delphi class: the TSQLVirtualTableModule class will use
|
|
// it internaly
|
|
TSQLite3VTabCursor = record
|
|
/// Virtual table of this cursor
|
|
pVtab: PSQLite3VTab;
|
|
/// this will be used to store a Delphi class instance handling the cursor
|
|
pInstance: TObject;
|
|
end;
|
|
|
|
/// defines a module object used to implement a virtual table.
|
|
// - Think of a module as a class from which one can construct multiple virtual
|
|
// tables having similar properties. For example, one might have a module that
|
|
// provides read-only access to comma-separated-value (CSV) files on disk.
|
|
// That one module can then be used to create several virtual tables where each
|
|
// virtual table refers to a different CSV file.
|
|
// - The module structure contains methods that are invoked by SQLite to perform
|
|
// various actions on the virtual table such as creating new instances of a
|
|
// virtual table or destroying old ones, reading and writing data, searching
|
|
// for and deleting, updating, or inserting rows.
|
|
TSQLite3Module = record
|
|
/// defines the particular edition of the module table structure
|
|
// - Currently, handled iVersion is 2, but in future releases of SQLite the
|
|
// module structure definition might be extended with additional methods and
|
|
// in that case the iVersion value will be increased
|
|
iVersion: integer;
|
|
/// called to create a new instance of a virtual table in response to a
|
|
// CREATE VIRTUAL TABLE statement
|
|
// - The job of this method is to construct the new virtual table object (an
|
|
// PSQLite3VTab object) and return a pointer to it in ppVTab
|
|
// - The DB parameter is a pointer to the SQLite database connection that is
|
|
// executing the CREATE VIRTUAL TABLE statement
|
|
// - The pAux argument is the copy of the client data pointer that was the
|
|
// fourth argument to the sqlite3.create_module_v2() call that registered
|
|
// the virtual table module
|
|
// - The argv parameter is an array of argc pointers to null terminated strings
|
|
// - The first string, argv[0], is the name of the module being invoked. The
|
|
// module name is the name provided as the second argument to sqlite3.create_module()
|
|
// and as the argument to the USING clause of the CREATE VIRTUAL TABLE
|
|
// statement that is running.
|
|
// - The second, argv[1], is the name of the database in which the new virtual
|
|
// table is being created. The database name is "main" for the primary
|
|
// database, or "temp" for TEMP database, or the name given at the end of
|
|
// the ATTACH statement for attached databases.
|
|
// - The third element of the array, argv[2], is the name of the new virtual
|
|
// table, as specified following the TABLE keyword in the CREATE VIRTUAL
|
|
// TABLE statement
|
|
// - If present, the fourth and subsequent strings in the argv[] array report
|
|
// the arguments to the module name in the CREATE VIRTUAL TABLE statement
|
|
// - As part of the task of creating a new PSQLite3VTab structure, this method
|
|
// must invoke sqlite3.declare_vtab() to tell the SQLite core about the
|
|
// columns and datatypes in the virtual table
|
|
xCreate: function(DB: TSQLite3DB; pAux: Pointer;
|
|
argc: Integer; const argv: PPUTF8CharArray;
|
|
var ppVTab: PSQLite3VTab; var pzErr: PUTF8Char): Integer; cdecl;
|
|
/// xConnect is called to establish a new connection to an existing virtual table,
|
|
// whereas xCreate is called to create a new virtual table from scratch
|
|
// - It has the same parameters and constructs a new PSQLite3VTab structure
|
|
// - xCreate and xConnect methods are only different when the virtual table
|
|
// has some kind of backing store that must be initialized the first time the
|
|
// virtual table is created. The xCreate method creates and initializes the
|
|
// backing store. The xConnect method just connects to an existing backing store.
|
|
xConnect: function(DB: TSQLite3DB; pAux: Pointer;
|
|
argc: Integer; const argv: PPUTF8CharArray;
|
|
var ppVTab: PSQLite3VTab; var pzErr: PUTF8Char): Integer; cdecl;
|
|
/// Used to determine the best way to access the virtual table
|
|
// - The pInfo parameter is used for input and output parameters
|
|
// - The SQLite core calls the xBestIndex() method when it is compiling a query
|
|
// that involves a virtual table. In other words, SQLite calls this method when
|
|
// it is running sqlite3.prepare() or the equivalent.
|
|
// - By calling this method, the SQLite core is saying to the virtual table
|
|
// that it needs to access some subset of the rows in the virtual table and
|
|
// it wants to know the most efficient way to do that access. The xBestIndex
|
|
// method replies with information that the SQLite core can then use to
|
|
// conduct an efficient search of the virtual table, via the xFilter() method.
|
|
// - While compiling a single SQL query, the SQLite core might call xBestIndex
|
|
// multiple times with different settings in pInfo. The SQLite
|
|
// core will then select the combination that appears to give the best performance.
|
|
// - The information in the pInfo structure is ephemeral and may be overwritten
|
|
// or deallocated as soon as the xBestIndex() method returns. If the
|
|
// xBestIndex() method needs to remember any part of the pInfo structure,
|
|
// it should make a copy. Care must be taken to store the copy in a place
|
|
// where it will be deallocated, such as in the idxStr field with
|
|
// needToFreeIdxStr set to 1.
|
|
xBestIndex: function(var pVTab: TSQLite3VTab; var pInfo: TSQLite3IndexInfo): Integer;
|
|
cdecl;
|
|
/// Releases a connection to a virtual table
|
|
// - Only the pVTab object is destroyed. The virtual table is not destroyed and
|
|
// any backing store associated with the virtual table persists. This method
|
|
// undoes the work of xConnect.
|
|
xDisconnect: function(pVTab: PSQLite3VTab): Integer; cdecl;
|
|
/// Releases a connection to a virtual table, just like the xDisconnect method,
|
|
// and it also destroys the underlying table implementation.
|
|
// - This method undoes the work of xCreate
|
|
// - The xDisconnect method is called whenever a database connection that uses
|
|
// a virtual table is closed. The xDestroy method is only called when a
|
|
// DROP TABLE statement is executed against the virtual table.
|
|
xDestroy: function(pVTab: PSQLite3VTab): Integer; cdecl;
|
|
/// Creates a new cursor used for accessing (read and/or writing) a virtual table
|
|
// - A successful invocation of this method will allocate the memory for the
|
|
// TPSQLite3VTabCursor (or a subclass), initialize the new object, and
|
|
// make ppCursor point to the new object. The successful call then returns SQLITE_OK.
|
|
// - For every successful call to this method, the SQLite core will later
|
|
// invoke the xClose method to destroy the allocated cursor.
|
|
// - The xOpen method need not initialize the pVtab field of the ppCursor structure.
|
|
// The SQLite core will take care of that chore automatically.
|
|
// - A virtual table implementation must be able to support an arbitrary number
|
|
// of simultaneously open cursors.
|
|
// - When initially opened, the cursor is in an undefined state. The SQLite core
|
|
// will invoke the xFilter method on the cursor prior to any attempt to
|
|
// position or read from the cursor.
|
|
xOpen: function(var pVTab: TSQLite3VTab; var ppCursor: PSQLite3VTabCursor): Integer;
|
|
cdecl;
|
|
/// Closes a cursor previously opened by xOpen
|
|
// - The SQLite core will always call xClose once for each cursor opened using xOpen.
|
|
// - This method must release all resources allocated by the corresponding xOpen call.
|
|
// - The routine will not be called again even if it returns an error. The
|
|
// SQLite core will not use the pVtabCursor again after it has been closed.
|
|
xClose: function(pVtabCursor: PSQLite3VTabCursor): Integer; cdecl;
|
|
/// Begins a search of a virtual table
|
|
// - The first argument is a cursor opened by xOpen.
|
|
// - The next two arguments define a particular search index previously chosen
|
|
// by xBestIndex(). The specific meanings of idxNum and idxStr are unimportant
|
|
// as long as xFilter() and xBestIndex() agree on what that meaning is.
|
|
// - The xBestIndex() function may have requested the values of certain
|
|
// expressions using the aConstraintUsage[].argvIndex values of its pInfo
|
|
// structure. Those values are passed to xFilter() using the argc and argv
|
|
// parameters.
|
|
// - If the virtual table contains one or more rows that match the search criteria,
|
|
// then the cursor must be left point at the first row. Subsequent calls to
|
|
// xEof must return false (zero). If there are no rows match, then the cursor
|
|
// must be left in a state that will cause the xEof to return true (non-zero).
|
|
// The SQLite engine will use the xColumn and xRowid methods to access that row content.
|
|
// The xNext method will be used to advance to the next row.
|
|
// - This method must return SQLITE_OK if successful, or an sqlite error code
|
|
// if an error occurs.
|
|
xFilter: function(var pVtabCursor: TSQLite3VTabCursor; idxNum: Integer; const idxStr: PAnsiChar;
|
|
argc: Integer; var argv: TSQLite3ValueArray): Integer; cdecl;
|
|
/// Advances a virtual table cursor to the next row of a result set initiated by xFilter
|
|
// - If the cursor is already pointing at the last row when this routine is called,
|
|
// then the cursor no longer points to valid data and a subsequent call to the
|
|
// xEof method must return true (non-zero).
|
|
// - If the cursor is successfully advanced to another row of content, then
|
|
// subsequent calls to xEof must return false (zero).
|
|
// - This method must return SQLITE_OK if successful, or an sqlite error code
|
|
// if an error occurs.
|
|
xNext: function(var pVtabCursor: TSQLite3VTabCursor): Integer; cdecl;
|
|
/// Checks if cursor reached end of rows
|
|
// - Must return false (zero) if the specified cursor currently points to a
|
|
// valid row of data, or true (non-zero) otherwise
|
|
xEof: function(var pVtabCursor: TSQLite3VTabCursor): Integer; cdecl;
|
|
/// The SQLite core invokes this method in order to find the value for the
|
|
// N-th column of the current row
|
|
// - N is zero-based so the first column is numbered 0.
|
|
// - The xColumn method may return its result back to SQLite using one of the
|
|
// standard sqlite3.result_*() functions with the specified sContext
|
|
// - If the xColumn method implementation calls none of the sqlite3.result_*()
|
|
// functions, then the value of the column defaults to an SQL NULL.
|
|
// - The xColumn method must return SQLITE_OK on success.
|
|
// - To raise an error, the xColumn method should use one of the result_text()
|
|
// methods to set the error message text, then return an appropriate error code.
|
|
xColumn: function(var pVtabCursor: TSQLite3VTabCursor; sContext: TSQLite3FunctionContext;
|
|
N: Integer): Integer; cdecl;
|
|
/// Should fill pRowid with the rowid of row that the virtual table cursor
|
|
// pVtabCursor is currently pointing at
|
|
xRowid: function(var pVtabCursor: TSQLite3VTabCursor; var pRowid: Int64): Integer;
|
|
cdecl;
|
|
/// Makes a change to a virtual table content (insert/delete/update)
|
|
// - The nArg parameter specifies the number of entries in the ppArg[] array
|
|
// - The value of nArg will be 1 for a pure delete operation or N+2 for an
|
|
// insert or replace or update where N is the number of columns in the table
|
|
// (including any hidden columns)
|
|
// - The ppArg[0] parameter is the rowid of a row in the virtual table to be deleted.
|
|
// If ppArg[0] is an SQL NULL, then no deletion occurs
|
|
// - The ppArg[1] parameter is the rowid of a new row to be inserted into the
|
|
// virtual table. If ppArg[1] is an SQL NULL, then the implementation must
|
|
// choose a rowid for the newly inserted row. Subsequent ppArg[] entries
|
|
// contain values of the columns of the virtual table, in the order that
|
|
// the columns were declared. The number of columns will match the table
|
|
// declaration that the xConnect or xCreate method made using the
|
|
// sqlite3.declare_vtab() call. All hidden columns are included.
|
|
// - When doing an insert without a rowid (nArg>1, ppArg[1] is an SQL NULL),
|
|
// the implementation must set pRowid to the rowid of the newly inserted row;
|
|
// this will become the value returned by the sqlite3.last_insert_rowid()
|
|
// function. Setting this value in all the other cases is a harmless no-op;
|
|
// the SQLite engine ignores the pRowid return value if nArg=1 or ppArg[1]
|
|
// is not an SQL NULL.
|
|
// - Each call to xUpdate() will fall into one of cases shown below. Note
|
|
// that references to ppArg[i] mean the SQL value held within the ppArg[i]
|
|
// object, not the ppArg[i] object itself:
|
|
// $ nArg = 1
|
|
// The single row with rowid equal to ppArg[0] is deleted. No insert occurs.
|
|
// $ nArg > 1
|
|
// $ ppArg[0] = NULL
|
|
// A new row is inserted with a rowid ppArg[1] and column values in ppArg[2]
|
|
// and following. If ppArg[1] is an SQL NULL, the a new unique rowid is
|
|
// generated automatically.
|
|
// $ nArg > 1
|
|
// $ ppArg[0] <> NULL
|
|
// $ ppArg[0] = ppArg[1]
|
|
// The row with rowid ppArg[0] is updated with new values in ppArg[2] and
|
|
// following parameters.
|
|
// $ nArg > 1
|
|
// $ ppArg[0] <> NULL
|
|
// $ ppArg[0] <> ppArg[1]
|
|
// The row with rowid ppArg[0] is updated with rowid ppArg[1] and new values
|
|
// in ppArg[2] and following parameters. This will occur when an SQL statement
|
|
// updates a rowid, as in the statement:
|
|
// $ UPDATE table SET rowid=rowid+1 WHERE ...;
|
|
// - The xUpdate() method must return SQLITE_OK if and only if it is successful.
|
|
// If a failure occurs, the xUpdate() must return an appropriate error code.
|
|
// On a failure, the pVTab.zErrMsg element may optionally be replaced with
|
|
// a custom error message text.
|
|
// - If the xUpdate() method violates some constraint of the virtual table
|
|
// (including, but not limited to, attempting to store a value of the
|
|
// wrong datatype, attempting to store a value that is too large or too small,
|
|
// or attempting to change a read-only value) then the xUpdate() must fail
|
|
// with an appropriate error code.
|
|
// - There might be one or more TSQLite3VTabCursor objects open and in use on
|
|
// the virtual table instance and perhaps even on the row of the virtual
|
|
// table when the xUpdate() method is invoked. The implementation of xUpdate()
|
|
// must be prepared for attempts to delete or modify rows of the table out
|
|
// from other existing cursors. If the virtual table cannot accommodate such
|
|
// changes, the xUpdate() method must return an error code.
|
|
xUpdate: function(var pVTab: TSQLite3VTab; nArg: Integer;
|
|
var ppArg: TSQLite3ValueArray; var pRowid: Int64): Integer; cdecl;
|
|
/// Begins a transaction on a virtual table
|
|
// - This method is always followed by one call to either the xCommit or
|
|
// xRollback method.
|
|
// - Virtual table transactions do not nest, so the xBegin method will not be
|
|
// invoked more than once on a single virtual table without an intervening
|
|
// call to either xCommit or xRollback. For nested transactions, use
|
|
// xSavepoint, xRelease and xRollBackTo methods.
|
|
// - Multiple calls to other methods can and likely will occur in between the
|
|
// xBegin and the corresponding xCommit or xRollback.
|
|
xBegin: function(var pVTab: TSQLite3VTab): Integer; cdecl;
|
|
/// Signals the start of a two-phase commit on a virtual table
|
|
// - This method is only invoked after call to the xBegin method and prior
|
|
// to an xCommit or xRollback.
|
|
// - In order to implement two-phase commit, the xSync method on all virtual
|
|
// tables is invoked prior to invoking the xCommit method on any virtual table.
|
|
// - If any of the xSync methods fail, the entire transaction is rolled back.
|
|
xSync: function(var pVTab: TSQLite3VTab): Integer; cdecl;
|
|
/// Causes a virtual table transaction to commit
|
|
xCommit: function(var pVTab: TSQLite3VTab): Integer; cdecl;
|
|
/// Causes a virtual table transaction to rollback
|
|
xRollback: function(var pVTab: TSQLite3VTab): Integer; cdecl;
|
|
/// Called during sqlite3.prepare() to give the virtual table implementation
|
|
// an opportunity to overload SQL functions
|
|
// - When a function uses a column from a virtual table as its first argument,
|
|
// this method is called to see if the virtual table would like to overload
|
|
// the function. The first three parameters are inputs: the virtual table,
|
|
// the number of arguments to the function, and the name of the function.
|
|
// If no overloading is desired, this method returns 0. To overload the
|
|
// function, this method writes the new function implementation into pxFunc
|
|
// and writes user data into ppArg and returns 1.
|
|
// - Note that infix functions (LIKE, GLOB, REGEXP, and MATCH) reverse the
|
|
// order of their arguments. So "like(A,B)" is equivalent to "B like A".
|
|
// For the form "B like A" the B term is considered the first argument to the
|
|
// function. But for "like(A,B)" the A term is considered the first argument.
|
|
// - The function pointer returned by this routine must be valid for the
|
|
// lifetime of the pVTab object given in the first parameter.
|
|
xFindFunction: function(var pVTab: TSQLite3VTab; nArg: Integer; const zName: PAnsiChar;
|
|
var pxFunc: TSQLFunctionFunc; var ppArg: Pointer): Integer; cdecl;
|
|
/// Provides notification that the virtual table implementation that the
|
|
// virtual table will be given a new name
|
|
// - If this method returns SQLITE_OK then SQLite renames the table.
|
|
// - If this method returns an error code then the renaming is prevented.
|
|
xRename: function(var pVTab: TSQLite3VTab; const zNew: PAnsiChar): Integer;
|
|
cdecl;
|
|
/// Starts a new transaction with the virtual table
|
|
// - SAVEPOINTs are a method of creating transactions, similar to BEGIN and
|
|
// COMMIT, except that the SAVEPOINT and RELEASE commands are named and
|
|
// may be nested. See @http://www.sqlite.org/lang_savepoint.html
|
|
// - iSavepoint parameter indicates the unique name of the SAVEPOINT
|
|
xSavepoint: function(var pVTab: TSQLite3VTab; iSavepoint: integer): Integer;
|
|
cdecl;
|
|
/// Merges a transaction into its parent transaction, so that the specified
|
|
// transaction and its parent become the same transaction
|
|
// - Causes all savepoints back to and including the most recent savepoint
|
|
// with a matching identifier to be removed from the transaction stack
|
|
// - Some people view RELEASE as the equivalent of COMMIT for a SAVEPOINT.
|
|
// This is an acceptable point of view as long as one remembers that the
|
|
// changes committed by an inner transaction might later be undone by a
|
|
// rollback in an outer transaction.
|
|
// - iSavepoint parameter indicates the unique name of the SAVEPOINT
|
|
xRelease: function(var pVTab: TSQLite3VTab; iSavepoint: integer): Integer;
|
|
cdecl;
|
|
/// Reverts the state of the virtual table content back to what it was just
|
|
// after the corresponding SAVEPOINT
|
|
// - iSavepoint parameter indicates the unique name of the SAVEPOINT
|
|
xRollbackTo: function(var pVTab: TSQLite3VTab; iSavepoint: integer): Integer;
|
|
cdecl;
|
|
end;
|
|
|
|
/// Compile-Time Authorization Callback prototype
|
|
// - The authorizer callback is invoked as SQL statements are being compiled by
|
|
// sqlite3.prepare2() e.g.
|
|
// - The authorizer callback should return SQLITE_OK to allow the action,
|
|
// SQLITE_IGNORE to disallow the specific action but allow the SQL statement
|
|
// to continue to be compiled, or SQLITE_DENY to cause the entire SQL statement
|
|
// to be rejected with an error.
|
|
// - If the authorizer callback returns any value other than SQLITE_IGNORE,
|
|
// SQLITE_OK, or SQLITE_DENY then the sqlite3.prepare_v2() or equivalent call
|
|
// that triggered the authorizer will fail with an error message.
|
|
// - The first pUserData parameter to the authorizer callback is a copy of the
|
|
// third parameter to the sqlite3.set_authorizer() interface
|
|
// - The second parameter to the callback is an integer action code that
|
|
// specifies the particular action to be authorized:
|
|
// - The third through sixth parameters to the callback are zero-terminated
|
|
// strings that contain additional details about the action to be authorized.
|
|
// - Here is a list of handled code constant, and their associated zTab / zCol
|
|
// parameters:
|
|
// ! const zTab zCol
|
|
// $ SQLITE_CREATE_INDEX Index Name Table Name
|
|
// $ SQLITE_CREATE_TABLE Table Name nil
|
|
// $ SQLITE_CREATE_TEMP_INDEX Index Name Table Name
|
|
// $ SQLITE_CREATE_TEMP_TABLE Table Name nil
|
|
// $ SQLITE_CREATE_TEMP_TRIGGER Trigger Name Table Name
|
|
// $ SQLITE_CREATE_TEMP_VIEW View Name nil
|
|
// $ SQLITE_CREATE_TRIGGER Trigger Name Table Name
|
|
// $ SQLITE_CREATE_VIEW View Name nil
|
|
// $ SQLITE_DELETE Table Name nil
|
|
// $ SQLITE_DROP_INDEX Index Name Table Name
|
|
// $ SQLITE_DROP_TABLE Table Name nil
|
|
// $ SQLITE_DROP_TEMP_INDEX Index Name Table Name
|
|
// $ SQLITE_DROP_TEMP_TABLE Table Name nil
|
|
// $ SQLITE_DROP_TEMP_TRIGGER Trigger Name Table Name
|
|
// $ SQLITE_DROP_TEMP_VIEW View Name nil
|
|
// $ SQLITE_DROP_TRIGGER Trigger Name Table Name
|
|
// $ SQLITE_DROP_VIEW View Name nil
|
|
// $ SQLITE_INSERT Table Name nil
|
|
// $ SQLITE_PRAGMA Pragma Name 1st arg or nil
|
|
// $ SQLITE_READ Table Name Column Name
|
|
// $ SQLITE_SELECT nil nil
|
|
// $ SQLITE_TRANSACTION Operation nil
|
|
// $ SQLITE_UPDATE Table Name Column Name
|
|
// $ SQLITE_ATTACH Filename nil
|
|
// $ SQLITE_DETACH Database Name nil
|
|
// $ SQLITE_ALTER_TABLE Database Name Table Name
|
|
// $ SQLITE_REINDEX Index Name nil
|
|
// $ SQLITE_ANALYZE Table Name nil
|
|
// $ SQLITE_CREATE_VTABLE Table Name Module Name
|
|
// $ SQLITE_DROP_VTABLE Table Name Module Name
|
|
// $ SQLITE_FUNCTION nil Function Name
|
|
// $ SQLITE_SAVEPOINT Operation Savepoint Name
|
|
// - The 5th parameter to the authorizer callback is the name of the database
|
|
// ('main', 'temp', etc.) if applicable.
|
|
// - The 6th parameter to the authorizer callback is the name of the inner-most
|
|
// trigger or view that is responsible for the access attempt or nil if this
|
|
// access attempt is directly from top-level SQL code.
|
|
TSQLAuthorizerCallback = function(pUserData: Pointer; code: Integer;
|
|
const zTab, zCol, zDb, zAuthContext: PAnsiChar): Integer; cdecl;
|
|
|
|
/// Callback function invoked when a row is updated, inserted or deleted,
|
|
// after sqlite3.update_hook() registration
|
|
// - The first pUpdateArg argument is a copy of the third argument to
|
|
// sqlite3.update_hook().
|
|
// - The second op argument is one of SQLITE_INSERT, SQLITE_DELETE, or SQLITE_UPDATE,
|
|
// depending on the operation that caused the callback to be invoked.
|
|
// - The third and fourth zDB / zTbl arguments contain pointers to the database
|
|
// and table name containing the affected row.
|
|
// - The final iRowID parameter is the rowid of the row. In the case of an update,
|
|
// this is the rowid after the update takes place.
|
|
// - The update hook implementation must not do anything that will modify the
|
|
// database connection that invoked the update hook. Any actions to modify the
|
|
// database connection must be deferred until after the completion of the
|
|
// sqlite3.step() call that triggered the update hook. Note that
|
|
// sqlite3.prepare_v2() and sqlite3.step() both modify their database
|
|
// connections for the meaning of "modify" in this paragraph.
|
|
TSQLUpdateCallback = procedure(pUpdateArg: Pointer; op: Integer;
|
|
const zDb, zTbl: PUTF8Char; iRowID: Int64); cdecl;
|
|
|
|
/// Commit And Rollback Notification Callback function after
|
|
// sqlite3.commit_hook() or sqlite3.rollback_hook() registration
|
|
// - The callback implementation must not do anything that will modify the
|
|
// database connection that invoked the callback. Any actions to modify the
|
|
// database connection must be deferred until after the completion of the
|
|
// sqlite3.step() call that triggered the commit or rollback hook in the
|
|
// first place. Note that sqlite3.prepare_v2() and sqlite3.step() both modify
|
|
// their database connections for the meaning of "modify" in this paragraph.
|
|
// - When the commit hook callback routine returns zero, the COMMIT operation
|
|
// is allowed to continue normally. If the commit hook returns non-zero, then
|
|
// the COMMIT is converted into a ROLLBACK. The rollback hook is invoked on
|
|
// a rollback that results from a commit hook returning non-zero, just as
|
|
// it would be with any other rollback.
|
|
// - For the purposes of this API, a transaction is said to have been rolled
|
|
// back if an explicit "ROLLBACK" statement is executed, or an error or
|
|
// constraint causes an implicit rollback to occur. The rollback callback
|
|
// is not invoked if a transaction is automatically rolled back because the
|
|
// database connection is closed.
|
|
TSQLCommitCallback = function(pArg: Pointer): Integer; cdecl;
|
|
|
|
|
|
/// events monitored by sqlite3.trace_v2() tracing logic
|
|
// - stmStmt callback is invoked when a prepared statement first begins
|
|
// running and possibly at other times during the execution of the prepared
|
|
// statement, such as at the start of each trigger subprogram. The P argument
|
|
// is a pointer to the prepared statement. The X argument is a pointer to a
|
|
// string which is the unexpanded SQL text of the prepared statement or an
|
|
// SQL comment that indicates the invocation of a trigger.
|
|
// - stmProfile callback provides approximately the same information as was
|
|
// provided by the deprecated sqlite3.profile() callback. The P argument is
|
|
// a pointer to the prepared statement and the X argument points to a 64-bit
|
|
// integer which is the estimated of the number of nanosecond that the
|
|
// prepared statement took to run. The stmProfile callback is invoked when
|
|
// the statement finishes.
|
|
// - stmRow callback is invoked whenever a prepared statement generates
|
|
// a single row of result. The P argument is a pointer to the prepared
|
|
// statement and the X argument is unused.
|
|
// - stmClose callback is invoked when a database connection closes. The
|
|
// P argument is a pointer to the database connection object and the X
|
|
// argument is unused.
|
|
TSQLTraceMask = set of (stmStmt, stmProfile, stmRow, stmClose);
|
|
|
|
/// Callback function registered by sqlite3.trace_v2()
|
|
// - the Trace argument has one of the TSQLTraceMask items set, to indicate
|
|
// why the callback was invoked
|
|
// - UserData argument is a copy of the context pointer, as provided at
|
|
// sqlite3.trace_v2() call
|
|
// - P and X arguments are pointers whose meanings depend on Trace content:
|
|
// see TSQLTraceMask for the various use cases
|
|
TSQLTraceCallback = procedure(Trace: TSQLTraceMask; UserData,P,X: pointer);
|
|
cdecl;
|
|
|
|
/// Callback function registered by sqlite3.profile()
|
|
// - this procedure will be invoked as each SQL statement finishes
|
|
// - warning: sqlite3.profile() function is considered experimental and is
|
|
// subject to change in future versions of SQLite
|
|
TSQLProfileCallback = procedure(ProfileArg: Pointer; Profile: PUTF8Char;
|
|
ProfileNanoSeconds: Int64); cdecl;
|
|
|
|
/// defines the interface between SQLite and low-level memory allocation routines
|
|
// - as used by sqlite3.config(SQLITE_CONFIG_MALLOC,pMemMethods);
|
|
TSQLite3MemMethods = record
|
|
/// Memory allocation function
|
|
xMalloc: function(size: integer): pointer; cdecl;
|
|
/// Free a prior allocation
|
|
xFree: procedure(ptr: pointer); cdecl;
|
|
/// Resize an allocation
|
|
xRealloc: function(ptr: pointer; size: integer): pointer; cdecl;
|
|
/// Return the size of an allocation
|
|
xSize: function(ptr: pointer): integer; cdecl;
|
|
/// Round up request size to allocation size
|
|
xRoundup: function(size: integer): integer; cdecl;
|
|
/// Initialize the memory allocator
|
|
xInit: function(appData: pointer): integer; cdecl;
|
|
/// Deinitialize the memory allocator
|
|
xShutdown: procedure(appData: pointer); cdecl;
|
|
/// Argument to xInit() and xShutdown()
|
|
pAppData: pointer;
|
|
end;
|
|
|
|
{$M+}
|
|
/// wrapper around all SQLite3 library API calls
|
|
// - abstract class allowing direct binding of static sqlite3.obj
|
|
// (TSQLite3LibrayStatic) or with an external library (TSQLite3LibraryDynamic)
|
|
// - a global sqlite3: TSQLite3Library will be defined in this unit, so
|
|
// you should call sqlite3.open() instead of sqlite3_open() for instance
|
|
// - if your project refers to SynSQLite3Static unit, it will initialize a
|
|
// TSQLite3LibrayStatic instance
|
|
TSQLite3Library = class
|
|
protected
|
|
fUseInternalMM: boolean;
|
|
fVersionNumber: cardinal;
|
|
fVersionText: RawUTF8;
|
|
function GetVersion: RawUTF8;
|
|
public
|
|
/// initialize the SQLite3 database code
|
|
// - automaticaly called by the initialization block of this unit
|
|
// - so sqlite3.c is compiled with SQLITE_OMIT_AUTOINIT defined
|
|
initialize: function: integer; cdecl;
|
|
|
|
/// shutdown the SQLite3 database core
|
|
// - automaticaly called by the finalization block of this unit
|
|
shutdown: function: integer; cdecl;
|
|
|
|
/// Open a SQLite3 database filename, creating a DB handle
|
|
// - filename must be UTF-8 encoded (filenames containing international
|
|
// characters must be converted to UTF-8 prior to passing them)
|
|
// - allocate a sqlite3 object, and return its handle in DB
|
|
// - return SQLITE_OK on success
|
|
// - an error code (see SQLITE_* const) is returned otherwise - sqlite3.errmsg()
|
|
// can be used to obtain an English language description of the error
|
|
// - Whatever or not an error occurs when it is opened, resources associated with
|
|
// the database connection handle should be released by passing it to
|
|
// sqlite3.close() when it is no longer required
|
|
open: function(filename: PUTF8Char; var DB: TSQLite3DB): integer; cdecl;
|
|
|
|
/// Open a SQLite3 database filename, creating a DB handle
|
|
// - sqlite3.open_v2() interface works like sqlite3.open() except that it
|
|
// accepts two additional parameters for additional control over the new
|
|
// database connection.
|
|
// - flags parameter to sqlite3.open_v2() can take one of SQLITE_OPEN_READONLY,
|
|
// SQLITE_OPEN_READWRITE or (SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE)
|
|
// values, optionally combined with the SQLITE_OPEN_NOMUTEX,
|
|
// SQLITE_OPEN_FULLMUTEX, SQLITE_OPEN_SHAREDCACHE, SQLITE_OPEN_PRIVATECACHE,
|
|
// and/or SQLITE_OPEN_URI flags
|
|
// - If the flags parameter is not one of the combinations shown above optionally
|
|
// combined with other SQLITE_OPEN_* bits then the behavior is undefined.
|
|
// - The fourth parameter is the name of the sqlite3_vfs object that defines
|
|
// the operating system interface that the new database connection should use.
|
|
// If the fourth parameter is a nil pointer then the default sqlite3_vfs
|
|
// object is used
|
|
open_v2: function(filename: PUTF8Char; var DB: TSQLite3DB; flags: integer;
|
|
zVfszVfs: PUTF8Char): integer; cdecl;
|
|
|
|
/// specify the encryption key on a newly opened database connection
|
|
// - Assigned(key)=false if encryption is not available for this .dll
|
|
// - SynSQLite3Static will use its own internal encryption format
|
|
// - key/keylen may be a JSON-serialized TSynSignerParams object, or will use
|
|
// AES-OFB-128 after SHAKE_128 with rounds=1000 and a fixed salt on plain password text
|
|
key: function(DB: TSQLite3DB; key: pointer; keyLen: Integer): integer; cdecl;
|
|
|
|
/// change the encryption key on a database connection that is already opened
|
|
// - can also decrypt a previously encrypted database (so that it is accessible
|
|
// from any version of SQLite) by specifying a nil key
|
|
// - Assigned(rekey)=false if encryption is not available, i.e. if
|
|
// NOSQLITE3STATIC is defined
|
|
// - also see ChangeSQLEncryptTablePassWord() procedure
|
|
rekey: function(DB: TSQLite3DB; key: pointer; keyLen: Integer): integer; cdecl;
|
|
|
|
/// Destructor for the sqlite3 object, which handle is DB
|
|
// - Applications should finalize all prepared statements and close all BLOB handles
|
|
// associated with the sqlite3 object prior to attempting to close the object
|
|
// (sqlite3.next_stmt() interface can be used for this task)
|
|
// - if invoked while a transaction is open, the transaction is automatically rolled back
|
|
// - SynSQLite3Static will use its own internal function for handling properly
|
|
// its own encryption format
|
|
close: function(DB: TSQLite3DB): integer; cdecl;
|
|
|
|
/// Return the version of the SQLite database engine, in ascii format
|
|
// - currently returns '3.38.2', when used with our SynSQLite3Static unit
|
|
// - if an external SQLite3 library is used, version may vary
|
|
// - you may use the VersionText property (or Version for full details) instead
|
|
libversion: function: PUTF8Char; cdecl;
|
|
|
|
/// Returns English-language text that describes an error,
|
|
// using UTF-8 encoding (which, with English text, is the same as Ansi).
|
|
// - Memory to hold the error message string is managed internally.
|
|
// The application does not need to worry about freeing the result.
|
|
// However, the error string might be overwritten or deallocated by
|
|
// subsequent calls to other SQLite interface functions.
|
|
errmsg: function(DB: TSQLite3DB): PUTF8Char; cdecl;
|
|
|
|
/// returns the numeric result code or extended result code for the most
|
|
// recent failed sqlite3 API call associated with a database connection
|
|
extended_errcode: function(DB: TSQLite3DB): integer; cdecl;
|
|
|
|
/// add SQL functions or aggregates or to redefine the behavior of existing
|
|
// SQL functions or aggregates
|
|
// - The first parameter is the database connection to which the SQL function is
|
|
// to be added. If an application uses more than one database connection then
|
|
// application-defined SQL functions must be added to each database connection
|
|
// separately.
|
|
// - The second parameter is the name of the SQL function to be created or redefined.
|
|
// The length of the name is limited to 255 bytes in a UTF-8 representation,
|
|
// exclusive of the zero-terminator. Note that the name length limit is in
|
|
// UTF-8 bytes, not characters nor UTF-16 bytes. Any attempt to create a
|
|
// function with a longer name will result in SQLITE_MISUSE being returned.
|
|
// - The third parameter (nArg) is the number of arguments that the SQL
|
|
// function or aggregate takes. If this parameter is -1, then the SQL
|
|
// function or aggregate may take any number of arguments between 0 and the
|
|
// SQLITE_LIMIT_FUNCTION_ARG current limit. If the third parameter is less
|
|
// than -1 or greater than 127 then the behavior is undefined.
|
|
// - The fourth parameter, eTextRep, specifies what text encoding this SQL
|
|
// function prefers for its parameters. Every SQL function implementation must
|
|
// be able to work with UTF-8, UTF-16le, or UTF-16be. But some implementations
|
|
// may be more efficient with one encoding than another. When multiple
|
|
// implementations of the same function are available, SQLite will pick the one
|
|
// that involves the least amount of data conversion. If there is only a single
|
|
// implementation which does not care what text encoding is used, then the
|
|
// fourth argument should be SQLITE_ANY.
|
|
// - The fifth parameter, pApp, is an arbitrary pointer. The implementation
|
|
// of the function can gain access to this pointer using sqlite3.user_data().
|
|
// - The seventh, eighth and ninth parameters, xFunc, xStep and xFinal, are
|
|
// pointers to C-language functions that implement the SQL function or aggregate.
|
|
// A scalar SQL function requires an implementation of the xFunc callback only;
|
|
// nil pointers must be passed as the xStep and xFinal parameters. An aggregate
|
|
// SQL function requires an implementation of xStep and xFinal and nil pointer
|
|
// must be passed for xFunc. To delete an existing SQL function or aggregate,
|
|
// pass nil pointers for all three function callbacks.
|
|
// - It is permitted to register multiple implementations of the same functions
|
|
// with the same name but with either differing numbers of arguments or
|
|
// differing preferred text encodings. SQLite will use the implementation
|
|
// that most closely matches the way in which the SQL function is used.
|
|
create_function: function(DB: TSQLite3DB; FunctionName: PUTF8Char;
|
|
nArg, eTextRep: integer; pApp: pointer; xFunc, xStep: TSQLFunctionFunc;
|
|
xFinal: TSQLFunctionFinal): Integer; cdecl;
|
|
|
|
/// add SQL functions or aggregates or to redefine the behavior of existing
|
|
// SQL functions or aggregates, including destruction
|
|
// - if the additinal xDestroy parameter is not nil, then it is invoked when
|
|
// the function is deleted, either by being overloaded or when the database
|
|
// connection closes.
|
|
// - When the destructure callback of the tenth parameter is invoked, it is
|
|
// passed a single argument which is a copy of the pointer which was the fifth
|
|
// parameter to sqlite3.create_function_v2().
|
|
// - this function is not available in older revisions - e.g. 3.6.*
|
|
create_function_v2: function(DB: TSQLite3DB; FunctionName: PUTF8Char;
|
|
nArg, eTextRep: integer; pApp: pointer; xFunc, xStep: TSQLFunctionFunc;
|
|
xFinal: TSQLFunctionFinal; xDestroy: TSQLDestroyPtr): Integer; cdecl;
|
|
|
|
/// add SQL functions or aggregates or to redefine the behavior of existing
|
|
// SQL functions or aggregates, including extra callback functions needed
|
|
// by aggregate window functions
|
|
// - see https://www.sqlite.org/windowfunctions.html#aggregate_window_functions
|
|
// - sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue
|
|
// and xInverse) passed to this function are pointers to callbacks that
|
|
// implement the new aggregate window function. xStep and xFinal must both
|
|
// be non-nil. xValue and xInverse may either both be nil, in which case a
|
|
// regular aggregate function is created, or must both be non-nil, in which
|
|
// case the new function may be used as either an aggregate or aggregate
|
|
// window function
|
|
// - this function is not available in older revisions, i.e. before 3.25.2
|
|
create_window_function: function(DB: TSQLite3DB; FunctionName: PUTF8Char;
|
|
nArg, eTextRep: integer; pApp: pointer; xStep: TSQLFunctionFunc;
|
|
xFinal, xValue: TSQLFunctionFinal; xInverse: TSQLFunctionFunc;
|
|
xDestroy: TSQLDestroyPtr): Integer; cdecl;
|
|
|
|
/// Define New Collating Sequences
|
|
// - add new collation sequences to the database connection specified
|
|
// - collation name is to be used in CREATE TABLE t1 (a COLLATE CollationName);
|
|
// or in SELECT * FROM t1 ORDER BY c COLLATE CollationName;
|
|
// - StringEncoding is either SQLITE_UTF8 either SQLITE_UTF16
|
|
// - TSQLDataBase.Create add WIN32CASE, WIN32NOCASE and ISO8601 collations
|
|
create_collation: function(DB: TSQLite3DB; CollationName: PUTF8Char;
|
|
StringEncoding: integer; CollateParam: pointer; cmp: TSQLCollateFunc): integer; cdecl;
|
|
|
|
/// Returns the rowid of the most recent successful INSERT into the database
|
|
last_insert_rowid: function(DB: TSQLite3DB): Int64; cdecl;
|
|
|
|
/// Set A Busy Timeout
|
|
// - This routine sets a busy handler that sleeps for a specified amount of time
|
|
// when a table is locked. The handler will sleep multiple times until at least
|
|
// "ms" milliseconds of sleeping have accumulated. After at least "ms" milliseconds
|
|
// of sleeping, the handler returns 0 which causes sqlite3.step() to return
|
|
// SQLITE_BUSY or SQLITE_IOERR_BLOCKED.
|
|
// - Calling this routine with an argument less than or equal to zero turns off
|
|
// all busy handlers.
|
|
// - There can only be a single busy handler for a particular database connection
|
|
// any given moment. If another busy handler was defined (using
|
|
// sqlite3.busy_handler()) prior to calling this routine, that other busy handler
|
|
// is cleared.
|
|
busy_timeout: function(DB: TSQLite3DB; Milliseconds: integer): integer; cdecl;
|
|
|
|
/// Register A Callback To Handle SQLITE_BUSY Errors
|
|
// - This routine sets a callback function that might be invoked whenever an
|
|
// attempt is made to open a database table that another thread or process has locked.
|
|
// - If the busy callback is nil, then SQLITE_BUSY or SQLITE_IOERR_BLOCKED is
|
|
// returned immediately upon encountering the lock. If the busy callback is not
|
|
// nil, then the callback might be invoked with two arguments.
|
|
// - The default busy callback is nil.
|
|
busy_handler: function(DB: TSQLite3DB;
|
|
CallbackPtr: TSQLBusyHandler; user: Pointer): integer; cdecl;
|
|
|
|
/// Compile a SQL query into byte-code
|
|
// - SQL must contains an UTF8-encoded null-terminated string query
|
|
// - SQL_bytes contains -1 (to stop at the null char) or the number of bytes in
|
|
// the input string, including the null terminator
|
|
// - return SQLITE_OK on success or an error code - see SQLITE_* and sqlite3.errmsg()
|
|
// - S will contain an handle of the resulting statement (an opaque sqlite3.stmt
|
|
// object) on success, or will 0 on error - the calling procedure is responsible
|
|
// for deleting the compiled SQL statement using sqlite3.finalize() after it has
|
|
// finished with it
|
|
// - in this "v2" interface, the prepared statement that is returned contains a
|
|
// copy of the original SQL text
|
|
// - this routine only compiles the first statement in SQL, so SQLtail is left pointing
|
|
// to what remains uncompiled
|
|
prepare_v2: function(DB: TSQLite3DB; SQL: PUTF8Char; SQL_bytes: integer;
|
|
var S: TSQLite3Statement; var SQLtail: PUTF8Char): integer; cdecl;
|
|
|
|
/// Delete a previously prepared statement
|
|
// - return SQLITE_OK on success or an error code - see SQLITE_* and sqlite3.errmsg()
|
|
// - this routine can be called at any point during the execution of the prepared
|
|
// statement. If the virtual machine has not completed execution when this routine
|
|
// is called, that is like encountering an error or an interrupt. Incomplete updates
|
|
// may be rolled back and transactions canceled, depending on the circumstances,
|
|
// and the error code returned will be SQLITE_ABORT
|
|
finalize: function(S: TSQLite3Statement): integer; cdecl;
|
|
|
|
/// Find the next prepared statement
|
|
// - this interface returns a handle to the next prepared statement after S,
|
|
// associated with the database connection DB.
|
|
// - if S is 0 then this interface returns a pointer to the first prepared
|
|
// statement associated with the database connection DB.
|
|
// - if no prepared statement satisfies the conditions of this routine, it returns 0
|
|
next_stmt: function(DB: TSQLite3DB; S: TSQLite3Statement): TSQLite3Statement; cdecl;
|
|
|
|
/// Reset a prepared statement object back to its initial state, ready to be re-Prepared
|
|
// - if the most recent call to sqlite3.step(S) returned SQLITE_ROW or SQLITE_DONE,
|
|
// or if sqlite3.step(S) has never before been called with S, then sqlite3.reset(S)
|
|
// returns SQLITE_OK.
|
|
// - return an appropriate error code if the most recent call to sqlite3.step(S) failed
|
|
// - any SQL statement variables that had values bound to them using the sqlite3.bind_*()
|
|
// API retain their values. Use sqlite3.clear_bindings() to reset the bindings.
|
|
reset: function(S: TSQLite3Statement): integer; cdecl;
|
|
|
|
/// returns true (non-zero) if and only if the prepared statement X
|
|
// makes no direct changes to the content of the database file
|
|
// - Transaction control statements such as BEGIN, COMMIT, ROLLBACK, SAVEPOINT,
|
|
// and RELEASE cause sqlite3.stmt_readonly() to return true, since the statements
|
|
// themselves do not actually modify the database but rather they control the
|
|
// timing of when other statements modify the database. The ATTACH and DETACH
|
|
// statements also cause sqlite3.stmt_readonly() to return true since, while
|
|
// those statements change the configuration of a database connection, they
|
|
// do not make changes to the content of the database files on disk.
|
|
stmt_readonly: function(S: TSQLite3Statement): integer; cdecl;
|
|
|
|
/// Evaluate An SQL Statement, returning a result status:
|
|
// - SQLITE_BUSY means that the database engine was unable to acquire the database
|
|
// locks it needs to do its job. If the statement is a COMMIT or occurs outside of
|
|
// an explicit transaction, then you can retry the statement. If the statement
|
|
// is not a COMMIT and occurs within a explicit transaction then you should
|
|
// rollback the transaction before continuing.
|
|
// - SQLITE_DONE means that the statement has finished executing successfully.
|
|
// sqlite3.step() should not be called again on this virtual machine without
|
|
// first calling sqlite3.reset() to reset the virtual machine state back.
|
|
// - SQLITE_ROW is returned each time a new row of data is ready for processing by
|
|
// the caller. The values may be accessed using the column access functions below.
|
|
// sqlite3.step() has to be called again to retrieve the next row of data.
|
|
// - SQLITE_MISUSE means that the this routine was called inappropriately. Perhaps
|
|
// it was called on a prepared statement that has already been finalized or on
|
|
// one that had previously returned SQLITE_ERROR or SQLITE_DONE. Or it could be
|
|
// the case that the same database connection is being used by two or more threads
|
|
// at the same moment in time.
|
|
// - SQLITE_SCHEMA means that the database schema changes, and the SQL statement
|
|
// has been recompiled and run again, but the schame changed in a way that makes
|
|
// the statement no longer valid, as a fatal error.
|
|
// - another specific error code is returned on fatal error
|
|
step: function(S: TSQLite3Statement): integer; cdecl;
|
|
|
|
/// get the number of columns in the result set for the statement
|
|
column_count: function(S: TSQLite3Statement): integer; cdecl;
|
|
|
|
/// datatype code for the initial data type of a result column
|
|
// - returned value is one of SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT,
|
|
// SQLITE_BLOB or SQLITE_NULL
|
|
// - S is the SQL statement, after sqlite3.step(S) returned SQLITE_ROW
|
|
// - Col is the column number, indexed from 0 to sqlite3.column_count(S)-1
|
|
// - must be called before any sqlite3.column_*() statement, which may result in
|
|
// an implicit type conversion: in this case, value is undefined
|
|
column_type: function(S: TSQLite3Statement; Col: integer): integer; cdecl;
|
|
|
|
/// returns a zero-terminated UTF-8 string containing the declared datatype
|
|
// of a result column
|
|
column_decltype: function(S: TSQLite3Statement; Col: integer): PAnsiChar; cdecl;
|
|
|
|
/// returns the name of a result column as a zero-terminated UTF-8 string
|
|
column_name: function(S: TSQLite3Statement; Col: integer): PUTF8Char; cdecl;
|
|
|
|
/// number of bytes for a BLOB or UTF-8 string result
|
|
// - S is the SQL statement, after sqlite3.step(S) returned SQLITE_ROW
|
|
// - Col is the column number, indexed from 0 to sqlite3.column_count(S)-1
|
|
// - an implicit conversion into UTF-8 text is made for a numeric value or
|
|
// UTF-16 column: you must call sqlite3.column_text() or sqlite3.column_blob()
|
|
// before calling sqlite3.column_bytes() to perform the conversion itself
|
|
column_bytes: function(S: TSQLite3Statement; Col: integer): integer; cdecl;
|
|
|
|
/// get the value handle of the Col column in the current row of prepared statement S
|
|
// - this handle represent a sqlite3.value object
|
|
// - this handle can then be accessed with any sqlite3.value_*() function below
|
|
column_value: function(S: TSQLite3Statement; Col: integer): TSQLite3Value; cdecl;
|
|
|
|
/// converts the Col column in the current row prepared statement S
|
|
// into a floating point value and returns a copy of that value
|
|
// - NULL is converted into 0.0
|
|
// - INTEGER is converted into corresponding floating point value
|
|
// - TEXT or BLOB is converted from all correct ASCII numbers with 0.0 as default
|
|
column_double: function(S: TSQLite3Statement; Col: integer): double; cdecl;
|
|
|
|
/// converts the Col column in the current row prepared statement S
|
|
// into a 32 bit integer value and returns a copy of that value
|
|
// - NULL is converted into 0
|
|
// - FLOAT is truncated into corresponding integer value
|
|
// - TEXT or BLOB is converted from all correct ASCII numbers with 0 as default
|
|
column_int: function(S: TSQLite3Statement; Col: integer): integer; cdecl;
|
|
|
|
/// converts the Col column in the current row prepared statement S
|
|
// into a 64 bit integer value and returns a copy of that value
|
|
// - NULL is converted into 0
|
|
// - FLOAT is truncated into corresponding integer value
|
|
// - TEXT or BLOB is converted from all correct ASCII numbers with 0 as default
|
|
column_int64: function(S: TSQLite3Statement; Col: integer): int64; cdecl;
|
|
|
|
/// converts the Col column in the current row prepared statement S
|
|
// into a zero-terminated UTF-8 string and returns a pointer to that string
|
|
// - NULL is converted into nil
|
|
// - INTEGER or FLOAT are converted into ASCII rendering of the numerical value
|
|
// - TEXT is returned directly (with UTF-16 -> UTF-8 encoding if necessary)
|
|
// - BLOB add a zero terminator if needed
|
|
column_text: function(S: TSQLite3Statement; Col: integer): PUTF8Char; cdecl;
|
|
|
|
/// converts the Col column in the current row prepared statement S
|
|
// into a zero-terminated UTF-16 string and returns a pointer to that string
|
|
// - NULL is converted into nil
|
|
// - INTEGER or FLOAT are converted into ASCII rendering of the numerical value
|
|
// - TEXT is returned directly (with UTF-8 -> UTF-16 encoding if necessary)
|
|
// - BLOB add a zero terminator if needed
|
|
column_text16: function(S: TSQLite3Statement; Col: integer): PWideChar; cdecl;
|
|
|
|
/// converts the Col column in the current row of prepared statement S
|
|
// into a BLOB and then returns a pointer to the converted value
|
|
// - NULL is converted into nil
|
|
// - INTEGER or FLOAT are converted into ASCII rendering of the numerical value
|
|
// - TEXT and BLOB are returned directly
|
|
column_blob: function(S: TSQLite3Statement; Col: integer): PAnsiChar; cdecl;
|
|
|
|
|
|
/// datatype code for a sqlite3.value object, specified by its handle
|
|
// - returned value is one of SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT,
|
|
// SQLITE_BLOB or SQLITE_NULL
|
|
// - must be called before any sqlite3.value_*() statement, which may result in
|
|
// an implicit type conversion: in this case, value is undefined
|
|
value_type: function(Value: TSQLite3Value): integer; cdecl;
|
|
|
|
/// attempts to apply numeric affinity to the value
|
|
// - This means that an attempt is made to convert the value to an integer or
|
|
// floating point. If such a conversion is possible without loss of information
|
|
// (in other words, if the value is a string that looks like a number) then the
|
|
// conversion is performed. Otherwise no conversion occurs. The datatype after
|
|
// conversion is returned.
|
|
// - returned value is one of SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT,
|
|
// SQLITE_BLOB or SQLITE_NULL
|
|
value_numeric_type: function(Value: TSQLite3Value): integer; cdecl;
|
|
|
|
/// number of bytes for a sqlite3.value object, specified by its handle
|
|
// - used after a call to sqlite3.value_text() or sqlite3.value_blob()
|
|
// to determine buffer size (in bytes)
|
|
value_bytes: function(Value: TSQLite3Value): integer; cdecl;
|
|
|
|
/// converts a sqlite3.value object, specified by its handle,
|
|
// into a floating point value and returns a copy of that value
|
|
value_double: function(Value: TSQLite3Value): double; cdecl;
|
|
|
|
/// converts a sqlite3.value object, specified by its handle,
|
|
// into an integer value and returns a copy of that value
|
|
value_int64: function(Value: TSQLite3Value): Int64; cdecl;
|
|
|
|
/// converts a sqlite3.value object, specified by its handle,
|
|
// into an UTF-8 encoded string, and returns a copy of that value
|
|
value_text: function(Value: TSQLite3Value): PUTF8Char; cdecl;
|
|
|
|
/// converts a sqlite3.value object, specified by its handle,
|
|
// into a blob memory, and returns a copy of that value
|
|
value_blob: function(Value: TSQLite3Value): pointer; cdecl;
|
|
|
|
|
|
/// sets the return value of the application-defined function to be NULL
|
|
result_null: procedure(Context: TSQLite3FunctionContext); cdecl;
|
|
|
|
/// sets the return value of the application-defined function to be the 64-bit
|
|
// signed integer value given in the 2nd argument
|
|
result_int64: procedure(Context: TSQLite3FunctionContext; Value: Int64); cdecl;
|
|
|
|
/// sets the result from an application-defined function to be a floating point
|
|
// value specified by its 2nd argument
|
|
result_double: procedure(Context: TSQLite3FunctionContext; Value: double); cdecl;
|
|
|
|
/// sets the result from an application-defined function to be the BLOB
|
|
// - content is pointed to by the Value and which is Value_bytes bytes long
|
|
// - set DestroyPtr as SQLITE_STATIC (nil) for static binding
|
|
// - set DestroyPtr to SQLITE_TRANSIENT (-1) for SQLite to make its own private
|
|
// copy of the data (this is the prefered way in our Framework)
|
|
// - set DestroyPtr to @sqlite3InternalFree if Value must be released via Freemem()
|
|
// or to @sqlite3InternalFreeObject if Value must be released via a Free method
|
|
result_blob: procedure(Context: TSQLite3FunctionContext; Value: Pointer;
|
|
Value_bytes: Integer=0; DestroyPtr: TSQLDestroyPtr=SQLITE_TRANSIENT); cdecl;
|
|
|
|
/// sets the return value of the application-defined function to be a text string
|
|
// which is represented as UTF-8
|
|
// - if Value_bytes is negative, then SQLite takes result text from the Value
|
|
// parameter through the first zero character
|
|
// - if Value_bytes is non-negative, then as many bytes (NOT characters: this
|
|
// parameter must include the #0 terminator) of the text pointed to by the
|
|
// Value parameter are taken as the application-defined function result
|
|
// - set DestroyPtr as SQLITE_STATIC (nil) for static binding
|
|
// - set DestroyPtr to SQLITE_TRANSIENT (-1) for SQLite to make its own private
|
|
// copy of the data (this is the prefered way in our Framework)
|
|
// - set DestroyPtr to @sqlite3InternalFree if Value must be released via Freemem()
|
|
// or to @sqlite3InternalFreeObject if Value must be released via a Free method
|
|
result_text: procedure(Context: TSQLite3FunctionContext; Value: PUTF8Char;
|
|
Value_bytes: Integer=-1; DestroyPtr: TSQLDestroyPtr=SQLITE_TRANSIENT); cdecl;
|
|
|
|
/// sets the result of the application-defined function to be a copy the unprotected
|
|
// sqlite3.value object specified by the 2nd parameter
|
|
// - The sqlite3.result_value() interface makes a copy of the sqlite3.value so
|
|
// that the sqlite3.value specified in the parameter may change or be deallocated
|
|
// after sqlite3.result_value() returns without harm
|
|
result_value: procedure(Context: TSQLite3FunctionContext; Value: TSQLite3Value); cdecl;
|
|
|
|
/// cause the implemented SQL function to throw an exception
|
|
// - SQLite interprets the error message string from sqlite3.result_error() as UTF-8
|
|
// - if MsgLen is negative, Msg must be #0 ended, or MsgLen must tell the numnber of
|
|
// characters in the Msg UTF-8 buffer
|
|
result_error: procedure(Context: TSQLite3FunctionContext; Msg: PUTF8Char; MsgLen: integer=-1); cdecl;
|
|
|
|
/// returns a copy of the pointer that was the pUserData parameter (the 5th
|
|
// parameter) of the sqlite3.create_function() routine that originally
|
|
// registered the application defined function
|
|
// - This routine must be called from the same thread in which the
|
|
// application-defined function is running
|
|
user_data: function(Context: TSQLite3FunctionContext): pointer; cdecl;
|
|
|
|
/// returns a copy of the pointer to the database connection (the 1st parameter)
|
|
// of the sqlite3.create_function() routine that originally registered the
|
|
// application defined function
|
|
context_db_handle: function(Context: TSQLite3FunctionContext): TSQLite3DB; cdecl;
|
|
|
|
/// Implementations of aggregate SQL functions use this routine to allocate
|
|
// memory for storing their state.
|
|
// - The first time the sqlite3.aggregate_context(C,N) routine is called for a
|
|
// particular aggregate function, SQLite allocates N of memory, zeroes out that
|
|
// memory, and returns a pointer to the new memory. On second and subsequent calls
|
|
// to sqlite3.aggregate_context() for the same aggregate function instance, the
|
|
// same buffer is returned. sqlite3.aggregate_context() is normally called once
|
|
// for each invocation of the xStep callback and then one last time when the
|
|
// xFinal callback is invoked. When no rows match an aggregate query, the xStep()
|
|
// callback of the aggregate function implementation is never called and xFinal()
|
|
// is called exactly once. In those cases, sqlite3.aggregate_context() might be
|
|
// called for the first time from within xFinal().
|
|
// - The sqlite3.aggregate_context(C,N) routine returns a nil pointer if N is
|
|
// less than or equal to zero or if a memory allocate error occurs.
|
|
// - The amount of space allocated by sqlite3.aggregate_context(C,N) is
|
|
// determined by the N parameter on first successful call. Changing the value
|
|
// of N in subsequent call to sqlite3.aggregate_context() within the same
|
|
// aggregate function instance will not resize the memory allocation.
|
|
// - SQLite automatically frees the memory allocated by sqlite3.aggregate_context()
|
|
// when the aggregate query concludes.
|
|
aggregate_context: function(Context: TSQLite3FunctionContext;
|
|
nBytes: integer): pointer; cdecl;
|
|
|
|
/// Bind a Text Value to a parameter of a prepared statement
|
|
// - return SQLITE_OK on success or an error code - see SQLITE_* and sqlite3.errmsg()
|
|
// - S is a statement prepared by a previous call to sqlite3.prepare_v2()
|
|
// - Param is the index of the SQL parameter to be set. The leftmost SQL parameter
|
|
// has an index of 1.
|
|
// - Text must contains an UTF8-encoded null-terminated string query
|
|
// - Text_bytes contains -1 (to stop at the null char) or the number of chars
|
|
// in the input string, excluding the null terminator
|
|
// - set DestroyPtr as SQLITE_STATIC (nil) for static binding
|
|
// - set DestroyPtr to SQLITE_TRANSIENT (-1) for SQLite to make its own private
|
|
// copy of the data (this is the prefered way in our Framework)
|
|
// - set DestroyPtr to @sqlite3InternalFree if Value must be released via Freemem()
|
|
bind_text: function(S: TSQLite3Statement; Param: integer;
|
|
Text: PUTF8Char; Text_bytes: integer=-1;
|
|
DestroyPtr: TSQLDestroyPtr=SQLITE_TRANSIENT): integer; cdecl;
|
|
// note that the official SQLite3 documentation could lead into misunderstanding:
|
|
// Text_bytes must EXCLUDE the null terminator, otherwise a #0 is appended to
|
|
// all column values
|
|
|
|
/// Bind a Blob Value to a parameter of a prepared statement
|
|
// - return SQLITE_OK on success or an error code - see SQLITE_* and sqlite3.errmsg()
|
|
// - S is a statement prepared by a previous call to sqlite3.prepare_v2()
|
|
// - Param is the index of the SQL parameter to be set (leftmost=1)
|
|
// - Buf must point to a memory buffer of Buf_bytes bytes
|
|
// - Buf_bytes contains the number of bytes in Buf
|
|
// - set DestroyPtr as SQLITE_STATIC (nil) for static binding
|
|
// - set DestroyPtr to SQLITE_TRANSIENT (-1) for SQLite to make its own private
|
|
// copy of the data (this is the prefered way in our Framework)
|
|
// - set DestroyPtr to @sqlite3InternalFree if Value must be released via Freemem()
|
|
bind_blob: function(S: TSQLite3Statement; Param: integer; Buf: pointer; Buf_bytes: integer;
|
|
DestroyPtr: TSQLDestroyPtr=SQLITE_TRANSIENT): integer; cdecl;
|
|
|
|
/// bind a ZeroBlob buffer to a parameter
|
|
// - uses a fixed amount of memory (just an integer to hold its size) while
|
|
// it is being processed. Zeroblobs are intended to serve as placeholders
|
|
// for BLOBs whose content is later written using incremental BLOB I/O routines.
|
|
// - a negative value for the Size parameter results in a zero-length BLOB
|
|
// - the leftmost SQL parameter has an index of 1, but ?NNN may override it
|
|
bind_zeroblob: function(S: TSQLite3Statement; Param: integer; Size: integer): integer; cdecl;
|
|
|
|
/// Bind a floating point Value to a parameter of a prepared statement
|
|
// - return SQLITE_OK on success or an error code - see SQLITE_* and sqlite3.errmsg()
|
|
// - S is a statement prepared by a previous call to sqlite3.prepare_v2()
|
|
// - Param is the index of the SQL parameter to be set (leftmost=1)
|
|
// - Value is the floating point number to bind
|
|
bind_double: function(S: TSQLite3Statement; Param: integer; Value: double): integer; cdecl;
|
|
|
|
/// Bind a 32 bits Integer Value to a parameter of a prepared statement
|
|
// - return SQLITE_OK on success or an error code - see SQLITE_* and sqlite3.errmsg()
|
|
// - S is a statement prepared by a previous call to sqlite3.prepare_v2()
|
|
// - Param is the index of the SQL parameter to be set (leftmost=1)
|
|
// - Value is the 32 bits Integer to bind
|
|
bind_int: function(S: TSQLite3Statement; Param: integer; Value: integer): integer; cdecl;
|
|
|
|
/// Bind a 64 bits Integer Value to a parameter of a prepared statement
|
|
// - return SQLITE_OK on success or an error code - see SQLITE_* and sqlite3.errmsg()
|
|
// - S is a statement prepared by a previous call to sqlite3.prepare_v2()
|
|
// - Param is the index of the SQL parameter to be set (leftmost=1)
|
|
// - Value is the 64 bits Integer to bind
|
|
bind_int64: function(S: TSQLite3Statement; Param: integer; Value: Int64): integer; cdecl;
|
|
|
|
/// Bind a NULL Value to a parameter of a prepared statement
|
|
// - return SQLITE_OK on success or an error code - see SQLITE_* and sqlite3.errmsg()
|
|
// - S is a statement prepared by a previous call to sqlite3.prepare_v2()
|
|
// - Param is the index of the SQL parameter to be set (leftmost=1)
|
|
bind_null: function(S: TSQLite3Statement; Param: integer): integer; cdecl;
|
|
|
|
/// Reset All Bindings On A Prepared Statement
|
|
clear_bindings: function(S: TSQLite3Statement): integer; cdecl;
|
|
|
|
/// Number Of SQL Parameters for a prepared statement
|
|
// - returns the index of the largest (rightmost) parameter. For all forms
|
|
// except ?NNN, this will correspond to the number of unique parameters.
|
|
// - If parameters of the ?NNN type are used, there may be gaps in the list.
|
|
bind_parameter_count: function(S: TSQLite3Statement): integer; cdecl;
|
|
|
|
/// Open a BLOB For Incremental I/O
|
|
// - returns a BLOB handle for row RowID, column ColumnName, table TableName
|
|
// in database DBName; in other words, the same BLOB that would be selected by:
|
|
// ! SELECT ColumnName FROM DBName.TableName WHERE rowid = RowID;
|
|
blob_open: function(DB: TSQLite3DB; DBName, TableName, ColumnName: PUTF8Char;
|
|
RowID: Int64; Flags: Integer; var Blob: TSQLite3Blob): Integer; cdecl;
|
|
|
|
/// Move a BLOB Handle to a New Row
|
|
// - will point to a different row of the same database table
|
|
// - this is faster than closing the existing handle and opening a new one
|
|
blob_reopen: function(Blob: TSQLite3Blob; RowID: Int64): Integer; cdecl;
|
|
|
|
/// Close A BLOB Handle
|
|
blob_close: function(Blob: TSQLite3Blob): Integer; cdecl;
|
|
|
|
/// Read Data From a BLOB Incrementally
|
|
blob_read: function(Blob: TSQLite3Blob; const Data; Count, Offset: Integer): Integer; cdecl;
|
|
|
|
/// Write Data To a BLOB Incrementally
|
|
blob_write: function(Blob: TSQLite3Blob; const Data; Count, Offset: Integer): Integer; cdecl;
|
|
|
|
/// Return The Size Of An Open BLOB
|
|
blob_bytes: function(Blob: TSQLite3Blob): Integer; cdecl;
|
|
|
|
/// Used to register a new virtual table module name
|
|
// - The module name is registered on the database connection specified by the
|
|
// first DB parameter.
|
|
// - The name of the module is given by the second parameter.
|
|
// - The third parameter is a pointer to the implementation of the virtual table
|
|
// module.
|
|
// - The fourth parameter is an arbitrary client data pointer that is passed
|
|
// through into the xCreate and xConnect methods of the virtual table module
|
|
// when a new virtual table is be being created or reinitialized.
|
|
// - The fifth parameter can be used to specify a custom destructor for the
|
|
// pClientData buffer. SQLite will invoke the destructor function (if it is
|
|
// not nil) when SQLite no longer needs the pClientData pointer. The
|
|
// destructor will also be invoked if call to sqlite3.create_module_v2() fails.
|
|
create_module_v2: function(DB: TSQLite3DB; const zName: PAnsiChar;
|
|
var p: TSQLite3Module; pClientData: Pointer; xDestroy: TSQLDestroyPtr): Integer; cdecl;
|
|
|
|
/// Declare the Schema of a virtual table
|
|
// - The xCreate() and xConnect() methods of a virtual table module call this
|
|
// interface to declare the format (the names and datatypes of the columns) of
|
|
// the virtual tables they implement. The string can be deallocated and/or reused
|
|
// as soon as the sqlite3.declare_vtab() routine returns.
|
|
// - If a column datatype contains the special keyword "HIDDEN" (in any
|
|
// combination of upper and lower case letters) then that keyword it is omitted
|
|
// from the column datatype name and the column is marked as a hidden column
|
|
// internally. A hidden column differs from a normal column in three respects:
|
|
// 1. Hidden columns are not listed in the dataset returned by "PRAGMA table_info",
|
|
// 2. Hidden columns are not included in the expansion of a "*" expression in
|
|
// the result set of a SELECT, and 3. Hidden columns are not included in the
|
|
// implicit column-list used by an INSERT statement that lacks an explicit
|
|
// column-list.
|
|
declare_vtab: function(DB: TSQLite3DB; const zSQL: PAnsiChar): Integer; cdecl;
|
|
|
|
/// Registers an authorizer callback to a specified DB connection
|
|
// - Only a single authorizer can be in place on a database connection at a time
|
|
// - Each call to sqlite3.set_authorizer overrides the previous call
|
|
// - Disable the authorizer by installing a nil callback
|
|
// - The authorizer is disabled by default
|
|
set_authorizer: function(DB: TSQLite3DB; xAuth: TSQLAuthorizerCallback;
|
|
pUserData: Pointer): Integer; cdecl;
|
|
|
|
/// Register Data Change Notification Callbacks
|
|
// - The sqlite3.update_hook() interface registers a callback function with
|
|
// the database connection identified by the first argument to be invoked
|
|
// whenever a row is updated, inserted or deleted.
|
|
// - Any callback set by a previous call to this function for the same
|
|
// database connection is overridden.
|
|
// - sqlite3.update_hook(D,C,P) function returns the P argument from the
|
|
// previous call on the same database connection D, or nil for the first
|
|
// call on database connection D.
|
|
// - The update hook is not invoked when internal system tables are modified
|
|
// (i.e. sqlite_master and sqlite_sequence).
|
|
// - In the current implementation, the update hook is not invoked when
|
|
// duplication rows are deleted because of an ON CONFLICT REPLACE clause.
|
|
// Nor is the update hook invoked when rows are deleted using the truncate
|
|
// optimization. The exceptions defined in this paragraph might change in
|
|
// a future release of SQLite.
|
|
// - Note that you should also trace COMMIT and ROLLBACK commands (calling
|
|
// sqlite3.commit_hook() and sqlite3.rollback_hook() functions) if you want to
|
|
// ensure that the notified update was not canceled by a later Rollback.
|
|
update_hook: function(DB: TSQLite3DB; xCallback: TSQLUpdateCallback;
|
|
pArg: pointer): pointer; cdecl;
|
|
|
|
/// Register Commit Notification Callbacks
|
|
// - The sqlite3.commit_hook() interface registers a callback function to be
|
|
// invoked whenever a transaction is committed.
|
|
// - Any callback set by a previous call to sqlite3.commit_hook() for the same
|
|
// database connection is overridden.
|
|
// - Registering a nil function disables the Commit callback.
|
|
// - The sqlite3.commit_hook(DB,C,P) function returns the P argument from the
|
|
// previous call of the same function on the same database connection DB, or nil
|
|
// for the first call for each function on DB.
|
|
commit_hook: function(DB: TSQLite3DB; xCallback: TSQLCommitCallback;
|
|
pArg: Pointer): Pointer; cdecl;
|
|
|
|
// Register Rollback Notification Callbacks
|
|
// - The sqlite3.rollback_hook() interface registers a callback function to be
|
|
// invoked whenever a transaction is rolled back.
|
|
// - Any callback set by a previous call to sqlite3.rollback_hook() for the same
|
|
// database connection is overridden.
|
|
// - Registering a nil function disables the Rollback callback.
|
|
// - The sqlite3.rollback_hook(D,C,P) function returns the P argument from the
|
|
// previous call of the same function on the same database connection D, or nil
|
|
// for the first call for each function on D.
|
|
rollback_hook: function(DB: TSQLite3DB; xCallback: TSQLCommitCallback;
|
|
pArg: Pointer): Pointer; cdecl;
|
|
|
|
/// 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.
|
|
// - If a separate thread makes changes on the same database connection while
|
|
// sqlite3.changes() is running then the value returned is unpredictable and not
|
|
// meaningful.
|
|
changes: function(DB: TSQLite3DB): Integer; cdecl;
|
|
|
|
/// 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 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()).
|
|
// - If a separate thread makes changes on the same database connection while
|
|
// sqlite3.total_changes() is running then the value returned is unpredictable
|
|
// and not meaningful.
|
|
total_changes: function(DB: TSQLite3DB): Integer; cdecl;
|
|
|
|
/// Returns a pointer to a block of memory at least N bytes in length
|
|
// - should call native malloc() function, i.e. GetMem() in this unit
|
|
malloc: function(n: Integer): Pointer; cdecl;
|
|
|
|
/// Attempts to resize a prior memory allocation
|
|
// - should call native realloc() function, i.e. ReallocMem() in this unit
|
|
realloc: function(pOld: Pointer; n: Integer): Pointer; cdecl;
|
|
|
|
/// Releases memory previously returned by sqlite3.malloc() or sqlite3.realloc()
|
|
// - should call native free() function, i.e. FreeMem() in this unit
|
|
// - renamed free_ in order not to override TObject.Free method
|
|
free_: procedure(p: Pointer); cdecl;
|
|
|
|
/// Returns the number of bytes of memory currently outstanding (malloced but not freed)
|
|
memory_used: function: Int64; cdecl;
|
|
|
|
/// Returns the maximum value of sqlite3.memory_used() since the high-water mark
|
|
// was last reset
|
|
memory_highwater: function(resetFlag: Integer): Int64; cdecl;
|
|
|
|
/// Register callback function that can be used for tracing the execution of
|
|
// SQL statements
|
|
// - registers a trace callback function Callback against database connection
|
|
// DB, using property mask TSQLTraceMask and context pointer UserData
|
|
// - if the Callback parameter is nil or if the TSQLTraceMask mask is zero,
|
|
// then tracing is disabled
|
|
// - parameters of the Callback functions depend of the TSQLTraceMask involved
|
|
trace_v2: function(DB: TSQLite3DB; Mask: TSQLTraceMask; Callback: TSQLTraceCallback;
|
|
UserData: Pointer): Pointer; cdecl;
|
|
|
|
/// Allows the size of various constructs to be limited on a connection
|
|
// by connection basis
|
|
// - The first parameter is the database connection whose limit is to be
|
|
// set or queried
|
|
// - The second parameter is one of the limit categories that define a
|
|
// class of constructs to be size limited - see TSQLLimitCategory enumerate
|
|
// - The third parameter is the new limit for that construct. If the new
|
|
// limit is a negative number, the limit is unchanged.
|
|
// - Regardless of whether or not the limit was changed, the sqlite3.limit()
|
|
// interface returns the prior value of the limit. Hence, to find the current
|
|
// value of a limit without changing it, simply invoke this interface with
|
|
// the third parameter set to -1.
|
|
limit: function(DB: TSQLite3DB; id,newValue: integer): integer; cdecl;
|
|
|
|
/// Initialize a backup process of a given SQLite3 database instance
|
|
// - The DestDB and DestDatabaseName arguments are the database connection
|
|
// associated with the destination database and the database name,
|
|
// respectively. The database name is "main" for the main database,
|
|
// "temp" for the temporary database, or the name specified after
|
|
// the AS keyword in an ATTACH statement for an attached database.
|
|
// - The SourceDB and SourceDatabaseName arguments identify the database
|
|
// connection and database name of the source database, respectively.
|
|
// - The source and destination database connections (parameters SourceDB and
|
|
// DestDB) must be different or else function will fail with an error.
|
|
// - If an error occurs within backup_init(), then nil is returned and an
|
|
// error code and error message are stored in the destination database
|
|
// connection DestDB. The error code and message for the failed call to
|
|
// backup_init() can be retrieved using the errcode() or errmsg() functions.
|
|
// - A successful call to backup_init() returns a pointer to an TSQLite3Backup
|
|
// object. The TSQLite3Backup object may be used with the backup_step() and
|
|
// backup_finish() functions to perform the specified backup operation.
|
|
backup_init: function(DestDB: TSQLite3DB; DestDatabaseName: PUTF8Char;
|
|
SourceDB: TSQLite3DB; SourceDatabaseName: PUTF8Char): TSQLite3Backup; cdecl;
|
|
|
|
/// perform a backup step to transfer the data between the two databases
|
|
// - backup_step() will copy up to nPages pages between the source and
|
|
// destination databases specified by TSQLite3Backup object Backup.
|
|
// - If nPages is negative, all remaining source pages are copied.
|
|
// - If backup_step() successfully copies nPages pages and there are still more
|
|
// pages to be copied, then the function returns SQLITE_OK.
|
|
// - If backup_step() successfully finishes copying all pages from source to
|
|
// destination, then it returns SQLITE_DONE.
|
|
// - If an error occurs while running backup_step(), an error code is returned.
|
|
// - As well as SQLITE_OK and SQLITE_DONE, a call to backup_step() may return
|
|
// SQLITE_READONLY, SQLITE_NOMEM, SQLITE_BUSY, SQLITE_LOCKED, or an
|
|
// SQLITE_IOERR_XXX extended error code. The function might return
|
|
// SQLITE_READONLY if the destination database was opened read-only, or is
|
|
// using WAL journaling and the destination and source page sizes differ, or
|
|
// the destination database is an in-memory database and the destination and
|
|
// source page sizes differ. SQLITE_BUSY indicates that the file-system lock
|
|
// did not succeed: in this case the call to backup_step() can be retried later.
|
|
// If the source database connection is being used to write to the source
|
|
// database when backup_step() is called, then SQLITE_LOCKED is returned
|
|
// immediately. Again, in this case the call to backup_step() can be retried
|
|
// later on. If SQLITE_IOERR_XXX, SQLITE_NOMEM, or SQLITE_READONLY is returned,
|
|
// then there is no point in retrying the call to backup_step(). These errors
|
|
// are considered fatal. The application must accept that the backup operation
|
|
// has failed and pass the backup operation handle to the backup_finish() to
|
|
// release associated resources.
|
|
// - The first call to sqlite3_backup_step() obtains an exclusive lock on the
|
|
// destination file. The exclusive lock is not released until either
|
|
// backup_finish() is called or the backup operation is complete and
|
|
// backup_step() returns SQLITE_DONE. Every call to backup_step() obtains a
|
|
// shared lock on the source database that lasts for the duration of the
|
|
// backup_step() call.
|
|
// - Because the source database is not locked between calls to backup_step(),
|
|
// the source database may be modified mid-way through the backup process.
|
|
// If the source database is modified by an external process or via a database
|
|
// connection other than the one being used by the backup operation, then the
|
|
// backup will be automatically restarted by the next call to backup_step().
|
|
// If the source database is modified by the using the same database connection
|
|
// as is used by the backup operation (which is the case in the SynSQLite3 and
|
|
// mORMotSQLite3 units), then the backup database is automatically updated at
|
|
// the same time, so you won't loose any data.
|
|
backup_step: function(Backup: TSQLite3Backup; nPages: integer): integer; cdecl;
|
|
/// finalize a Backup process on a given database
|
|
// - When backup_step() has returned SQLITE_DONE, or when the application
|
|
// wishes to abandon the backup operation, the application should destroy the
|
|
// TSQLite3Backup by passing it to backup_finish().
|
|
// - The backup_finish() interfaces releases all resources associated with the
|
|
// TSQLite3Backup object. If backup_step() has not yet returned SQLITE_DONE,
|
|
// then any active write-transaction on the destination database is rolled back.
|
|
// - The TSQLite3Backup object is invalid and may not be used following a call
|
|
// to backup_finish().
|
|
// - The value returned by backup_finish is SQLITE_OK if no backup_step() errors
|
|
// occurred, regardless or whether or not backup_step() completed. If an
|
|
// out-of-memory condition or IO error occurred during any prior backup_step()
|
|
// call on the same TSQLite3Backup object, then backup_finish() returns the
|
|
// corresponding error code.
|
|
// - A return of SQLITE_BUSY or SQLITE_LOCKED from backup_step() is not a
|
|
// permanent error and does not affect the return value of backup_finish().
|
|
backup_finish: function(Backup: TSQLite3Backup): integer; cdecl;
|
|
|
|
/// returns the number of pages still to be backed up for a given Backup
|
|
// - The values returned by this function is only updated by backup_step().
|
|
// If the source database is modified during a backup operation, then the
|
|
// value is not updated to account for any extra pages that need to be
|
|
// updated or the size of the source database file changing.
|
|
backup_remaining: function(Backup: TSQLite3Backup): integer; cdecl;
|
|
|
|
/// returns the the total number of pages in the source database file
|
|
// for a given Backup process
|
|
// - The values returned by this function is only updated by backup_step().
|
|
// If the source database is modified during a backup operation, then the
|
|
// value is not updated to account for any extra pages that need to be
|
|
// updated or the size of the source database file changing.
|
|
backup_pagecount: function(Backup: TSQLite3Backup): integer; cdecl;
|
|
|
|
/// serialize a database
|
|
// - returns a pointer to memory that is a serialization of the Schema
|
|
// database on database connection DB
|
|
// - if Size is not nil, then the size of the database in bytes is written into Size^
|
|
// - for an ordinary on-disk database file, the serialization is just a copy
|
|
// of the disk file; for an in-memory database or a "TEMP" database, the
|
|
// serialization is the same sequence of bytes which would be written to disk
|
|
// if that database where backed up to disk
|
|
// - caller is responsible for freeing the returned value (using free_)
|
|
// to avoid a memory leak
|
|
serialize: function(DB: TSQLite3DB; Schema: PUTF8Char; Size: PInt64;
|
|
Flags: integer): pointer; cdecl;
|
|
|
|
/// deserialize a database
|
|
// - causes the database connection DB to disconnect from database Schema
|
|
// and then reopen Schema as an in-memory database based on the serialization
|
|
// contained in Data; the serialized database Data is DBSize bytes in size
|
|
// - BufSize is the size of the buffer Data, which might be larger than DBSize
|
|
deserialize: function(DB: TSQLite3DB; Schema: PUTF8Char; Data: pointer;
|
|
DBSize, BufSize: Int64; Flags: integer): pointer; cdecl;
|
|
|
|
/// sets and/or queries the soft limit on the amount of heap memory
|
|
// that may be allocated by SQLite
|
|
// - SQLite strives to keep heap memory utilization below the soft heap limit
|
|
// by reducing the number of pages held in the page cache as heap memory usages
|
|
// approaches the limit. The soft heap limit is "soft" because even though
|
|
// SQLite strives to stay below the limit, it will exceed the limit rather
|
|
// than generate an SQLITE_NOMEM error. In other words, the soft heap limit
|
|
// is advisory only
|
|
// - The return value from soft_heap_limit64() is the size of the soft heap
|
|
// limit prior to the call, or negative in the case of an error. If the
|
|
// argument N is negative then no change is made to the soft heap limit.
|
|
// Hence, the current size of the soft heap limit can be determined by
|
|
// invoking soft_heap_limit64() with a negative argument
|
|
// - This function is useful when you have many SQLite databases open at
|
|
// the same time, as the cache-size setting is per-database (connection),
|
|
// while this limit is global for the process, so this allows to limit the
|
|
// total cache size
|
|
soft_heap_limit64: function(N: Int64): Int64; cdecl;
|
|
|
|
/// used to make global configuration changes to current database
|
|
config: function(operation: integer): integer;
|
|
{$ifndef DELPHI5OROLDER}cdecl varargs;{$endif}
|
|
|
|
/// used to make global configuration changes to current database connection
|
|
db_config: function(DestDB: TSQLite3DB; operation: integer): integer;
|
|
{$ifndef DELPHI5OROLDER}cdecl varargs;{$endif}
|
|
|
|
/// initialize the internal version numbers
|
|
constructor Create; virtual;
|
|
/// will change the SQLite3 configuration to use Delphi/FPC memory manager
|
|
// - this will reduce memory fragmentation, and enhance speed, especially
|
|
// under multi-process activity
|
|
// - this method should be called before sqlite3.initialize()
|
|
procedure ForceToUseSharedMemoryManager; virtual;
|
|
/// returns the current version number as a plain integer
|
|
// - equals e.g. 3008003001 for '3.8.3.1'
|
|
property VersionNumber: cardinal read fVersionNumber;
|
|
/// returns the current version number as a text
|
|
// - equals e.g. '3.8.3.1'
|
|
// - use the Version property for the full information about this instance
|
|
property VersionText: RawUTF8 read fVersionText;
|
|
published
|
|
/// will return the class name and SQLite3 version number
|
|
// - if self (e.g. global sqlite3) is nil, will return ''
|
|
property Version: RawUTF8 read GetVersion;
|
|
end;
|
|
{$M-}
|
|
|
|
/// allow access to an exernal SQLite3 library engine
|
|
// - you can e.g. replace the main sqlite3 engine with any external library:
|
|
// ! FreeAndNil(sqlite3); // release any previous instance (e.g. static)
|
|
// ! sqlite3 := TSQLite3LibraryDynamic.Create;
|
|
TSQLite3LibraryDynamic = class(TSQLite3Library)
|
|
protected
|
|
{$ifdef FPC}
|
|
fHandle: TLibHandle;
|
|
{$else}
|
|
fHandle: THandle;
|
|
{$endif}
|
|
fLibraryName: TFileName;
|
|
public
|
|
/// initialize the specified external library
|
|
// - raise an ESQLite3Exception on error
|
|
constructor Create(const LibraryName: TFileName=SQLITE_LIBRARY_DEFAULT_NAME); reintroduce;
|
|
/// unload the external library
|
|
destructor Destroy; override;
|
|
published
|
|
property LibraryName: TFileName read fLibraryName;
|
|
end;
|
|
|
|
|
|
/// an internal function which calls Freemem(p)
|
|
// - can be used to free some PUTF8Char pointer allocated by Delphi Getmem()
|
|
procedure sqlite3InternalFree(p: pointer); cdecl;
|
|
|
|
/// an internal function which calls TObject(p).Free
|
|
// - can be used to free some Delphi class instance
|
|
procedure sqlite3InternalFreeObject(p: pointer); cdecl;
|
|
|
|
/// an internal function which calls RawByteString(p) := ''
|
|
// - can be used to free some Delphi class instance
|
|
// - use a local tmp: pointer variable to prepare the reference count, e.g.
|
|
// ! tmp := nil;
|
|
// ! RawUTF8(tmp) := Text; // fast COW assignment
|
|
// ! sqlite3.result_text(Context,tmp,length(Text)+1,sqlite3InternalFreeRawByteString);
|
|
procedure sqlite3InternalFreeRawByteString(p: pointer); cdecl;
|
|
|
|
/// wrapper around sqlite3.result_error() to be called if wrong number of arguments
|
|
procedure ErrorWrongNumberOfArgs(Context: TSQLite3FunctionContext);
|
|
|
|
/// wrapper around sqlite3.result_error() validating the expected number of arguments
|
|
function CheckNumberOfArgs(Context: TSQLite3FunctionContext; expected,sent: integer): boolean;
|
|
|
|
/// create a TSQLite3Module.pzErr UTF-8 text buffer according to the given
|
|
// Delphi exception
|
|
procedure ExceptionToSqlite3Err(E: Exception; var pzErr: PUTF8Char);
|
|
|
|
/// set a TSQLVar into a SQlite3 result context
|
|
// - will call the corresponding sqlite3.result_*() function and return true,
|
|
// or will return false if the TSQLVar type is not handled
|
|
function SQLVarToSQlite3Context(const Res: TSQLVar; Context: TSQLite3FunctionContext): boolean;
|
|
|
|
/// set a UTF-8 string into a SQlite3 result context
|
|
// - this function will use copy-on-write assignment of Text, with no memory
|
|
// allocation, then let sqlite3InternalFreeRawByteString release its reference count
|
|
procedure RawUTF8ToSQlite3Context(const Text: RawUTF8; Context: TSQLite3FunctionContext;
|
|
VoidTextAsNull: boolean);
|
|
|
|
{$ifndef NOVARIANTS}
|
|
|
|
/// set a variant value into a SQlite3 result context
|
|
// - will call the corresponding sqlite3.result_*() function, using
|
|
// SQLVarToSQlite3Context() after a call to VariantToSQLVar()
|
|
procedure VariantToSQlite3Context(const Value: Variant; Context: TSQLite3FunctionContext);
|
|
|
|
/// set a JSON value into a SQlite3 result context
|
|
// - a JSON object or array would be returned at plain TEXT, or other simple
|
|
// JSON text or number would be returned as the corresponding SQLite3 value
|
|
procedure JsonToSQlite3Context(json: PUTF8Char; Context: TSQLite3FunctionContext);
|
|
|
|
{$endif}
|
|
|
|
/// set a SQLite3 value into a TSQLVar
|
|
// - will call the corresponding sqlite3.value_*() function to retrieve the
|
|
// data with the less overhead (e.g. memory allocation or copy) as possible
|
|
procedure SQlite3ValueToSQLVar(Value: TSQLite3Value; var Res: TSQLVar);
|
|
|
|
|
|
|
|
const
|
|
SQLITE_INDEX_CONSTRAINT_EQ = 2;
|
|
SQLITE_INDEX_CONSTRAINT_GT = 4;
|
|
SQLITE_INDEX_CONSTRAINT_LE = 8;
|
|
SQLITE_INDEX_CONSTRAINT_LT = 16;
|
|
SQLITE_INDEX_CONSTRAINT_GE = 32;
|
|
SQLITE_INDEX_CONSTRAINT_MATCH = 64;
|
|
SQLITE_INDEX_CONSTRAINT_LIKE = 65;
|
|
SQLITE_INDEX_CONSTRAINT_GLOB = 66;
|
|
SQLITE_INDEX_CONSTRAINT_REGEXP = 67;
|
|
|
|
SQLITE_DENY = 1;
|
|
SQLITE_IGNORE = 2;
|
|
|
|
SQLITE_CREATE_INDEX = 1;
|
|
SQLITE_CREATE_TABLE = 2;
|
|
SQLITE_CREATE_TEMP_INDEX = 3;
|
|
SQLITE_CREATE_TEMP_TABLE = 4;
|
|
SQLITE_CREATE_TEMP_TRIGGER = 5;
|
|
SQLITE_CREATE_TEMP_VIEW = 6;
|
|
SQLITE_CREATE_TRIGGER = 7;
|
|
SQLITE_CREATE_VIEW = 8;
|
|
SQLITE_DELETE = 9;
|
|
SQLITE_DROP_INDEX = 10;
|
|
SQLITE_DROP_TABLE = 11;
|
|
SQLITE_DROP_TEMP_INDEX = 12;
|
|
SQLITE_DROP_TEMP_TABLE = 13;
|
|
SQLITE_DROP_TEMP_TRIGGER = 14;
|
|
SQLITE_DROP_TEMP_VIEW = 15;
|
|
SQLITE_DROP_TRIGGER = 16;
|
|
SQLITE_DROP_VIEW = 17;
|
|
SQLITE_INSERT = 18;
|
|
SQLITE_PRAGMA = 19;
|
|
SQLITE_READ = 20;
|
|
SQLITE_SELECT = 21;
|
|
SQLITE_TRANSACTION = 22;
|
|
SQLITE_UPDATE = 23;
|
|
SQLITE_ATTACH = 24;
|
|
SQLITE_DETACH = 25;
|
|
SQLITE_ALTER_TABLE = 26;
|
|
SQLITE_REINDEX = 27;
|
|
SQLITE_ANALYZE = 28;
|
|
SQLITE_CREATE_VTABLE = 29;
|
|
SQLITE_DROP_VTABLE = 30;
|
|
SQLITE_FUNCTION = 31;
|
|
SQLITE_SAVEPOINT = 32;
|
|
SQLITE_COPY = 0;
|
|
|
|
|
|
/// SQL statement to get all tables names in the current database file
|
|
// (taken from official SQLite3 documentation)
|
|
SQL_GET_TABLE_NAMES =
|
|
'SELECT name FROM sqlite_master WHERE type=''table'' AND name NOT LIKE ''sqlite_%'';';
|
|
|
|
type
|
|
/// the main possible return codes, including error codes
|
|
TSQLite3ErrorCode = (
|
|
secUnknown,
|
|
secOK,secERROR,secINTERNAL,secPERM,secABORT,secBUSY,secLOCKED,secNOMEM,
|
|
secREADONLY,secINTERRUPT,secIOERR,secCORRUPT,secNOTFOUND,secFULL,secCANTOPEN,
|
|
secPROTOCOL,secEMPTY,secSCHEMA,secTOOBIG,secCONSTRAINT,secMISMATCH,secMISUSE,
|
|
secNOLFS,secAUTH,secFORMAT,secRANGE,secNOTADB, secROW,secDONE);
|
|
|
|
/// custom SQLite3 dedicated Exception type
|
|
ESQLite3Exception = class(ESynException)
|
|
protected
|
|
fErrorCode: integer;
|
|
fSQLite3ErrorCode: TSQLite3ErrorCode;
|
|
public
|
|
/// the DB which raised this exception
|
|
DB: TSQLite3DB;
|
|
/// create the exception, getting the message from DB
|
|
constructor Create(aDB: TSQLite3DB; aErrorCode: integer; const aSQL: RawUTF8); reintroduce; overload;
|
|
published
|
|
/// the corresponding error code, e.g. 21 (for SQLITE_MISUSE)
|
|
property ErrorCode: integer read fErrorCode;
|
|
/// the corresponding error code, e.g. secMISUSE
|
|
property SQLite3ErrorCode: TSQLite3ErrorCode read fSQLite3ErrorCode;
|
|
end;
|
|
|
|
/// convert a SQLite3 result code into a TSQLite3ErrorCode item
|
|
function sqlite3_resultToErrorCode(aResult: integer): TSQLite3ErrorCode;
|
|
|
|
/// convert a SQLite3 result code into the corresponding SQLite constant name
|
|
// - e.g. sqlite3_resultToErrorText(SQLITE_OK)='SQLITE_OK'
|
|
function sqlite3_resultToErrorText(aResult: integer): RawUTF8;
|
|
|
|
/// convert a TSQLite3ErrorCode item into the corresponding SQLite constant name
|
|
// - e.g. ErrorCodeToText(secOK)='SQLITE_OK'
|
|
function ErrorCodeToText(err: TSQLite3ErrorCode): RawUTF8;
|
|
|
|
/// test the result state of a sqlite3.*() function
|
|
// - raise a ESQLite3Exception if the result state is within SQLITE_ERRORS
|
|
// - return the result state otherwise (SQLITE_OK,SQLITE_ROW,SQLITE_DONE e.g.)
|
|
function sqlite3_check(DB: TSQLite3DB; aResult: integer; const SQL: RawUTF8=''): integer;
|
|
|
|
var
|
|
/// global access to linked SQLite3 library API calls
|
|
// - you should call sqlite3.open() instead of sqlite3_open() for instance
|
|
// - points either to the statically linked sqlite3.obj, or to an external
|
|
// library (e.g. sqlite3.dll under Windows)
|
|
// - your project should use EITHER SynSQLite3Static unit OR create a
|
|
// TSQLite3LibraryDynamic instance:
|
|
// ! FreeAndNil(sqlite3); // release any previous instance
|
|
// ! sqlite3 := TSQLite3LibraryDynamic.Create;
|
|
// - caller should free the sqlite3 instance only with
|
|
// ! FreeAndNil(sqlite3);
|
|
// to avoid issues with the automatic freeing in finalization section
|
|
sqlite3: TSQLite3Library;
|
|
|
|
|
|
|
|
{ ************ objects for high-level access SQLite3 database engine }
|
|
|
|
type
|
|
/// available file-level write access wait mode of the SQLite3 engine
|
|
// - when synchronous is smFull (which is the default setting), the SQLite
|
|
// database engine will use the xSync method of the VFS to ensure that all
|
|
// content is safely written to the disk surface prior to continuing. This
|
|
// ensures that an operating system crash or power failure will not corrupt
|
|
// the database. FULL synchronous is very safe, but it is also slower.
|
|
// - when synchronous is smNormal, the SQLite database engine will still
|
|
// sync at the most critical moments, but less often than in FULL mode. There
|
|
// is a very small (though non-zero) chance that a power failure at just the
|
|
// wrong time could corrupt the database in NORMAL mode. But in practice,
|
|
// you are more likely to suffer a catastrophic disk failure or some other
|
|
// unrecoverable hardware fault.
|
|
// - when synchronous is smOff, SQLite continues without syncing as soon as
|
|
// it has handed data off to the operating system. If the application running
|
|
// SQLite crashes, the data will be safe, but the database might become
|
|
// corrupted if the operating system crashes or the computer loses power
|
|
// before that data has been written to the disk surface. On the other hand,
|
|
// some operations are as much as 50 or more times faster with synchronous OFF.
|
|
TSQLSynchronousMode = (smOff, smNormal, smFull);
|
|
|
|
/// available file-level database connection locking-mode
|
|
// - lmNormal locking-mode (the default unless overridden at compile-time using
|
|
// SQLITE_DEFAULT_LOCKING_MODE), a database connection unlocks the database
|
|
// file at the conclusion of each read or write transaction.
|
|
// - when the locking-mode is set to lmExclusive, the database connection
|
|
// never releases file-locks. The first time the database is read in
|
|
// lmExclusive mode, a shared lock is obtained and held. The first time the
|
|
// database is written, an exclusive lock is obtained and held. Database locks
|
|
// obtained by a connection in lmExclusive mode may be released either by
|
|
// closing the database connection, or by setting the locking-mode back to
|
|
// lmNormal using this pragma and then accessing the database file (for read
|
|
// or write). Simply setting the locking-mode to lmNormal is not enough - locks
|
|
// are not released until the next time the database file is accessed.
|
|
// - lmExclusive gives much better write performance, and could be used when
|
|
// needed, in case of a heavy loaded mORMot server
|
|
TSQLLockingMode = (lmNormal, lmExclusive);
|
|
|
|
/// available Run-Time limit categories
|
|
// - as expected by sqlite3.limit() function and TSQLDatabase.Limit property
|
|
// - lcLength The maximum size of any string or BLOB or table row, in bytes.
|
|
// - lcSQLLength The maximum length of an SQL statement, in bytes.
|
|
// - lcColumn The maximum number of columns in a table definition or in the
|
|
// result set of a SELECT or the maximum number of columns in an index or in
|
|
// an ORDER BY or GROUP BY clause.
|
|
// - lcExprDepth The maximum depth of the parse tree on any expression.
|
|
// - lcCompoundSelect The maximum number of terms in a compound SELECT statement.
|
|
// - lcVDBEop The maximum number of instructions in a virtual machine program
|
|
// used to implement an SQL statement. This limit is not currently enforced,
|
|
// though that might be added in some future release of SQLite.
|
|
// - lcFunctionArg The maximum number of arguments on a function.
|
|
// - lcAttached The maximum number of attached databases.
|
|
// - lcLikePatternLength The maximum length of the pattern argument to the
|
|
// LIKE or GLOB operators.
|
|
// - lcVariableNumber The maximum number of parameters in an SQL statement.
|
|
// - lcTriggerDepth The maximum depth of recursion for triggers.
|
|
TSQLLimitCategory = (lcLength, lcSQLLength, lcColumn, lcExprDepth,
|
|
lcCompoundSelect, lcVDBEop, lcFunctionArg, lcAttached, lcLikePatternLength,
|
|
lcVariableNumber, lcTriggerDepth);
|
|
|
|
{$M+}
|
|
TSQLDatabase = class;
|
|
{$M-}
|
|
|
|
TSQLBlobStream = class;
|
|
|
|
PSQLRequest = ^TSQLRequest;
|
|
|
|
/// wrapper to a SQLite3 request
|
|
// - defined as a record, so that it may be allocated on the stack
|
|
// - do not forget to call the Close method to release the request resources
|
|
{$ifdef UNICODE}
|
|
TSQLRequest = record
|
|
{$else}
|
|
TSQLRequest = object
|
|
{$endif}
|
|
private
|
|
fDB: TSQLite3DB;
|
|
fRequest: TSQLite3Statement;
|
|
fNextSQL: PUTF8Char;
|
|
fFieldCount: integer;
|
|
function GetReadOnly: Boolean;
|
|
function GetParamCount: integer;
|
|
|
|
// 1. general request process
|
|
public
|
|
/// Prepare a UTF-8 encoded SQL statement
|
|
// - compile the SQL into byte-code
|
|
// - parameters ? ?NNN :VV @VV $VV can be bound with Bind*() functions below
|
|
// - raise an ESQLite3Exception on any error, unless NoExcept is TRUE
|
|
function Prepare(DB: TSQLite3DB; const SQL: RawUTF8; NoExcept: boolean=false): integer;
|
|
/// Prepare a WinAnsi SQL statement
|
|
// - behave the same as Prepare()
|
|
function PrepareAnsi(DB: TSQLite3DB; const SQL: WinAnsiString): integer;
|
|
/// Prepare the next SQL command initialized in previous Prepare()
|
|
// - raise an ESQLite3Exception on any error
|
|
function PrepareNext: integer;
|
|
/// Evaluate An SQL Statement, returning the sqlite3.step() result status:
|
|
// - return SQLITE_ROW on success, with data ready to be retrieved via the
|
|
// Field*() methods
|
|
// - return SQLITE_DONE if the SQL commands were executed
|
|
// - raise an ESQLite3Exception on any error
|
|
function Step: integer;
|
|
/// Reset A Prepared Statement Object
|
|
// - reset a prepared statement object back to its initial state,
|
|
// ready to be re-executed.
|
|
// - any SQL statement variables that had values bound to them using the Bind*()
|
|
// function below retain their values. Use BindReset() to reset the bindings
|
|
// - return SQLITE_OK on success, or the previous Step error code
|
|
function Reset: integer;
|
|
/// Execute all SQL statements already prepared by a call to Prepare()
|
|
// - the statement is closed
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure ExecuteAll; overload;
|
|
/// Execute all SQL statements in the aSQL UTF-8 encoded string
|
|
// - internaly call Prepare() then Step then PrepareNext until end of aSQL
|
|
// - Close is always called internaly
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure ExecuteAll(aDB: TSQLite3DB; const aSQL: RawUTF8); overload;
|
|
/// Execute one SQL statement already prepared by a call to Prepare()
|
|
// - the statement is closed
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure Execute; overload;
|
|
/// Execute one SQL statement in the aSQL UTF-8 encoded string
|
|
// - Execute the first statement in aSQL: call Prepare() then Step once
|
|
// - Close is always called internaly
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure Execute(aDB: TSQLite3DB; const aSQL: RawUTF8); overload;
|
|
/// Execute one SQL statement in the aSQL UTF-8 encoded string
|
|
// - Execute the first statement in aSQL: call Prepare() then Step once
|
|
// - Close is always called internaly
|
|
// - returns TRUE on success, and raise no ESQLite3Exception on error, but returns FALSE
|
|
function ExecuteNoException(aDB: TSQLite3DB; const aSQL: RawUTF8): boolean;
|
|
/// Execute a SQL statement which return integers from the aSQL UTF-8 encoded string
|
|
// - Execute the first statement in aSQL
|
|
// - this statement must get (at least) one field/column result of INTEGER
|
|
// - return result as a dynamic array of Int64 in ID
|
|
// - return count of row in integer function result (may be < length(ID))
|
|
// - raise an ESQLite3Exception on any error
|
|
function Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var ID: TInt64DynArray): integer; overload;
|
|
/// Execute a SQL statement which return one integer from the aSQL UTF-8 encoded string
|
|
// - Execute the first statement in aSQL
|
|
// - this statement must get (at least) one field/column result of INTEGER
|
|
// - return result as an unique Int64 in ID
|
|
// - raise an ESQLite3Exception on any error }
|
|
procedure Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out ID: Int64); overload;
|
|
/// Execute a SQL statement which return one TEXT value from the aSQL UTF-8 encoded string
|
|
// - Execute the first statement in aSQL
|
|
// - this statement must get (at least) one field/column result of TEXT
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out Value: RawUTF8); overload;
|
|
/// Execute a SQL statement which return TEXT from the aSQL UTF-8 encoded string
|
|
// - Execute the first statement in aSQL
|
|
// - this statement must get (at least) one field/column result of TEXT
|
|
// - return result as a dynamic array of RawUTF8 in ID
|
|
// - return count of row in integer function result (may be < length(ID))
|
|
// - raise an ESQLite3Exception on any error
|
|
function Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var Values: TRawUTF8DynArray): integer; overload;
|
|
/// Execute one SQL statement which return the results in JSON format
|
|
// - JSON format is more compact than XML and well supported
|
|
// - Execute the first statement in aSQL
|
|
// - if SQL is '', the statement should have been prepared, reset and bound if necessary
|
|
// - raise an ESQLite3Exception on any error
|
|
// - JSON data is added to TStream, with UTF-8 encoding
|
|
// - if Expand is true, JSON data is an array of objects, for direct use
|
|
// with any Ajax or .NET client:
|
|
// & [ {"col1":val11,"col2":"val12"},{"col1":val21,... ]
|
|
// - if Expand is false, JSON data is serialized (used in TSQLTableJSON)
|
|
// & { "FieldCount":1,"Values":["col1","col2",val11,"val12",val21,..] }
|
|
// - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"'
|
|
// format and contains true BLOB data (no conversion into TEXT, as with
|
|
// TSQLTableDB) - so will work for sftBlob, sftBlobDynArray and sftBlobRecord
|
|
// - returns the number of data rows added to JSON (excluding the headers)
|
|
function Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; JSON: TStream;
|
|
Expand: boolean=false): PtrInt; overload;
|
|
/// Execute one SQL statement which return the results in JSON format
|
|
// - use internaly Execute() above with a TRawByteStringStream, and return a string
|
|
// - BLOB field value is saved as Base64, e.g. '"\uFFF0base64encodedbinary"'
|
|
// - returns the number of data rows added to JSON (excluding the headers)
|
|
// in the integer variable mapped by aResultCount (if any)
|
|
// - if any error occurs, the ESQLite3Exception is handled and '' is returned
|
|
function ExecuteJSON(aDB: TSQLite3DB; const aSQL: RawUTF8; Expand: boolean=false;
|
|
aResultCount: PPtrInt=nil): RawUTF8;
|
|
/// Execute all SQL statements in the aSQL UTF-8 encoded string, results will
|
|
// be written as ANSI text in OutFile
|
|
procedure ExecuteDebug(aDB: TSQLite3DB; const aSQL: RawUTF8; var OutFile: Text);
|
|
/// close the Request handle
|
|
// - call it even if an ESQLite3Exception has been raised
|
|
procedure Close;
|
|
|
|
/// read-only access to the Request (SQLite3 statement) handle
|
|
property Request: TSQLite3Statement read fRequest;
|
|
/// read-only access to the SQLite3 database handle
|
|
property RequestDB: TSQLite3DB read fDB;
|
|
/// returns true if the current prepared statement makes no direct changes
|
|
// to the content of the database file
|
|
// - Transaction control statements such as BEGIN, COMMIT, ROLLBACK, SAVEPOINT,
|
|
// and RELEASE cause this property to return true, since the statements
|
|
// themselves do not actually modify the database but rather they control the
|
|
// timing of when other statements modify the database. The ATTACH and DETACH
|
|
// statements also cause this property to return true since, while
|
|
// those statements change the configuration of a database connection, they
|
|
// do not make changes to the content of the database files on disk.
|
|
property IsReadOnly: Boolean read GetReadOnly;
|
|
|
|
// 2. Bind parameters to a SQL query (for the last prepared statement)
|
|
public
|
|
/// Reset All Bindings On A Prepared Statement
|
|
// - Contrary to the intuition of many, Reset() does not reset the bindings
|
|
// on a prepared statement. Use this routine to reset all host parameter
|
|
procedure BindReset;
|
|
/// bind a NULL value to a parameter
|
|
// - the leftmost SQL parameter has an index of 1, but ?NNN may override it
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure BindNull(Param: Integer);
|
|
/// bind an integer value to a parameter
|
|
// - the leftmost SQL parameter has an index of 1, but ?NNN may override it
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure Bind(Param: Integer; Value: Int64); overload;
|
|
/// bind a double value to a parameter
|
|
// - the leftmost SQL parameter has an index of 1, but ?NNN may override it
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure Bind(Param: Integer; Value: double); overload;
|
|
/// bind a UTF-8 encoded string to a parameter
|
|
// - the leftmost SQL parameter has an index of 1, but ?NNN may override it
|
|
// - raise an ESQLite3Exception on any error
|
|
// - this function will use copy-on-write assignment of Value, with no memory
|
|
// allocation, then let sqlite3InternalFreeRawByteString release the variable
|
|
procedure Bind(Param: Integer; const Value: RawUTF8); overload;
|
|
/// bind a generic VCL string to a parameter
|
|
// - with versions prior to Delphi 2009, you may loose some content here:
|
|
// Bind(Param: integer; Value: RawUTF8) is the prefered method
|
|
// - the leftmost SQL parameter has an index of 1, but ?NNN may override it
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure BindS(Param: Integer; const Value: string);
|
|
/// bind a Blob buffer to a parameter
|
|
// - the leftmost SQL parameter has an index of 1, but ?NNN may override it
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure Bind(Param: Integer; Data: pointer; Size: integer); overload;
|
|
/// bind a Blob buffer to a parameter
|
|
// - the leftmost SQL parameter has an index of 1, but ?NNN may override it
|
|
// - raise an ESQLite3Exception on any error
|
|
// - this function will use copy-on-write assignment of Data, with no memory
|
|
// allocation, then let sqlite3InternalFreeRawByteString release the variable
|
|
procedure BindBlob(Param: Integer; const Data: RawByteString);
|
|
/// bind a Blob TCustomMemoryStream buffer to a parameter
|
|
// - the leftmost SQL parameter has an index of 1, but ?NNN may override it
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure Bind(Param: Integer; Data: TCustomMemoryStream); overload;
|
|
/// bind a ZeroBlob buffer to a parameter
|
|
// - uses a fixed amount of memory (just an integer to hold its size) while
|
|
// it is being processed. Zeroblobs are intended to serve as placeholders
|
|
// for BLOBs whose content is later written using incremental BLOB I/O routines
|
|
// (as with TSQLBlobStream created from TSQLDataBase.Blob() e.g.).
|
|
// - a negative value for the Size parameter results in a zero-length BLOB
|
|
// - the leftmost SQL parameter has an index of 1, but ?NNN may override it
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure BindZero(Param: Integer; Size: integer);
|
|
|
|
// 3. Field attributes after a sucessfull Step() (returned SQLITE_ROW)
|
|
public
|
|
/// the field name of the current ROW
|
|
function FieldName(Col: integer): RawUTF8;
|
|
/// the field index matching this name
|
|
// - return -1 if not found
|
|
function FieldIndex(const aColumnName: RawUTF8): integer;
|
|
/// return the field as a sqlite3.value object handle, first Col is 0
|
|
function FieldValue(Col: integer): TSQLite3Value;
|
|
/// return a field integer value, first Col is 0
|
|
function FieldInt(Col: integer): Int64;
|
|
/// return a field floating point value, first Col is 0
|
|
function FieldDouble(Col: integer): double;
|
|
/// return a field UTF-8 encoded text value, first Col is 0
|
|
function FieldUTF8(Col: integer): RawUTF8;
|
|
/// return a text value value as generic VCL string, first Col is 0
|
|
// - note that prior to Delphi 2009, you may loose content during conversion
|
|
function FieldS(Col: integer): string;
|
|
/// return a field as Win-Ansi (i.e. code page 1252) encoded text value, first Col is 0
|
|
function FieldA(Col: integer): WinAnsiString;
|
|
/// return a field RawUnicode encoded text value, first Col is 0
|
|
function FieldW(Col: integer): RawUnicode;
|
|
/// return a field as a blob value (RawByteString/TSQLRawBlob is an AnsiString),
|
|
// first Col is 0
|
|
function FieldBlob(Col: integer): RawByteString;
|
|
/// return a field as a TStream blob value, first Col is 0
|
|
// - caller shall release the returned TStream instance
|
|
function FieldBlobToStream(Col: integer): TStream;
|
|
/// return TRUE if the column value is NULL, first Col is 0
|
|
function FieldNull(Col: Integer): Boolean;
|
|
/// return the field type of this column
|
|
// - retrieve the "SQLite3" column type as returned by sqlite3.column_type -
|
|
// i.e. SQLITE_NULL, SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, or SQLITE_BLOB
|
|
function FieldType(Col: Integer): integer;
|
|
/// return the type of this column, as declared at creation
|
|
// - textual type used for CREATE TABLE of the corresponding column, as
|
|
// returned by sqlite3.column_decltype()
|
|
function FieldDeclaredType(Col: Integer): RawUTF8;
|
|
/// return the generic VCL string type of this column, as declared at creation
|
|
// - textual type used for CREATE TABLE of corresponding column, as
|
|
// returned by sqlite3.column_decltype()
|
|
// - note that prior to Delphi 2009, you may loose content during conversion
|
|
function FieldDeclaredTypeS(Col: Integer): string;
|
|
/// append all columns values of the current Row to a JSON stream
|
|
// - will use WR.Expand to guess the expected output format
|
|
// - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"
|
|
// format and contains true BLOB data
|
|
procedure FieldsToJSON(WR: TJSONWriter; DoNotFetchBlobs: boolean=false);
|
|
/// the column/field count of the current ROW
|
|
// - fields numerotation starts with 0
|
|
property FieldCount: integer read fFieldCount;
|
|
/// the bound parameters count
|
|
property ParamCount: integer read GetParamCount;
|
|
end;
|
|
|
|
/// used to retrieve a prepared statement
|
|
TSQLStatementCache = record
|
|
/// associated SQL statement
|
|
StatementSQL: RawUTF8;
|
|
/// associated prepared statement, ready to be executed after binding
|
|
Statement: TSQLRequest;
|
|
/// used to monitor execution time
|
|
Timer: TSynMonitor;
|
|
end;
|
|
/// used to store all prepared statement
|
|
TSQLStatementCacheDynArray = array of TSQLStatementCache;
|
|
|
|
/// handle a cache of prepared statements
|
|
// - is defined either as an object either as a record, due to a bug
|
|
// in Delphi 2009/2010 compiler (at least): this structure is not initialized
|
|
// if defined as an object on the stack, but will be as a record :(
|
|
{$ifdef UNICODE}
|
|
TSQLStatementCached = record
|
|
{$else}
|
|
TSQLStatementCached = object
|
|
{$endif}
|
|
/// prepared statements with parameters for faster SQLite3 execution
|
|
// - works for SQL code with ? internal parameters
|
|
Cache: TSQLStatementCacheDynArray;
|
|
/// current number of items in the Cache[] array
|
|
Count: integer;
|
|
/// hashing wrapper associated to the Cache[] array
|
|
Caches: TDynArrayHashed;
|
|
/// the associated SQLite3 database instance
|
|
// - any direct access to this cache list should be protected via DB.Lock
|
|
DB: TSQLite3DB;
|
|
/// intialize the cache
|
|
procedure Init(aDB: TSQLite3DB);
|
|
/// add or retrieve a generic SQL (with ? parameters) statement from cache
|
|
function Prepare(const GenericSQL: RawUTF8; WasPrepared: PBoolean=nil;
|
|
ExecutionTimer: PPPrecisionTimer=nil; ExecutionMonitor: PSynMonitor=nil): PSQLRequest;
|
|
/// used internaly to release all prepared statements from Cache[]
|
|
procedure ReleaseAllDBStatements;
|
|
/// could be used e.g. for statistics
|
|
// - will use internally the function StatementCacheTotalTimeCompare()
|
|
procedure SortCacheByTotalTime(var aIndex: TIntegerDynArray);
|
|
end;
|
|
|
|
/// those classes can be used to define custom SQL functions inside a TSQLDataBase
|
|
TSQLDataBaseSQLFunction = class
|
|
protected
|
|
fInternalFunction: TSQLFunctionFunc;
|
|
fSQLName: RawUTF8;
|
|
fFunctionParametersCount: integer;
|
|
function CreateFunction(DB: TSQLite3DB): Integer; virtual;
|
|
public
|
|
/// initialize the corresponding SQL function
|
|
// - expects at least the low-level TSQLFunctionFunc implementation (in
|
|
// sqlite3.create_function() format) and the number of expected parameters
|
|
// - if the function name is not specified, it will be retrieved from the type
|
|
// information (e.g. TReferenceDynArray will declare 'ReferenceDynArray')
|
|
constructor Create(aFunction: TSQLFunctionFunc; aFunctionParametersCount: Integer;
|
|
const aFunctionName: RawUTF8=''); reintroduce;
|
|
/// the internal function prototype
|
|
// - ready to be assigned to sqlite3.create_function() xFunc parameter
|
|
property InternalFunction: TSQLFunctionFunc read fInternalFunction;
|
|
/// the SQL function name, as called from the SQL statement
|
|
// - the same function name may be registered several times with a diverse
|
|
// number of parameters (e.g. to implement optional parameters)
|
|
property FunctionName: RawUTF8 read fSQLName;
|
|
/// the number of parameters expected by the SQL function
|
|
property FunctionParametersCount: integer read fFunctionParametersCount;
|
|
end;
|
|
|
|
/// to be used to define custom SQL functions for dynamic arrays BLOB search
|
|
TSQLDataBaseSQLFunctionDynArray = class(TSQLDataBaseSQLFunction)
|
|
protected
|
|
fDummyDynArray: TDynArray;
|
|
fDummyDynArrayValue: pointer;
|
|
public
|
|
/// initialize the corresponding SQL function
|
|
// - if the function name is not specified, it will be retrieved from the type
|
|
// information (e.g. TReferenceDynArray will declare 'ReferenceDynArray')
|
|
// - the SQL function will expect two parameters: the first is the BLOB
|
|
// field content, and the 2nd is the array element to search (set with
|
|
// TDynArray.ElemSave() or with BinToBase64WithMagic(aDynArray.ElemSave())
|
|
// if called via a Client and a JSON prepared parameter)
|
|
// - you should better use the already existing faster SQL functions
|
|
// Byte/Word/Integer/Cardinal/Int64/CurrencyDynArrayContains() if possible
|
|
// (this implementation will allocate each dynamic array into memory before
|
|
// comparison, and will be therefore slower than those optimized versions)
|
|
constructor Create(aTypeInfo: pointer; aCompare: TDynArraySortCompare;
|
|
const aFunctionName: RawUTF8=''); reintroduce;
|
|
end;
|
|
|
|
/// Stored Procedure prototype, used by TSQLDataBase.Execute() below
|
|
// - called for every row of a Statement
|
|
// - the implementation may update the database directly by using a
|
|
// local or shared TSQLRequest
|
|
// - the TSQLRequest may be shared and prepared before the call for even
|
|
// faster access than with a local TSQLRequest
|
|
// - no TSQLDataBase or higher levels objects can be used inside this method,
|
|
// since all locking and try..finally protection is outside it
|
|
// - can optionnaly trigger a ESQLite3Exception on any error
|
|
TOnSQLStoredProc = procedure(const Statement: TSQLRequest) of object;
|
|
|
|
/// TSQLDataBase.TransactionBegin can be deferred, immediate, or exclusive
|
|
// - tbDeferred means that no locks are acquired on the database until the
|
|
// database is first accessed. Thus with a deferred transaction, the BEGIN
|
|
// statement itself does nothing to the filesystem. Locks are not acquired
|
|
// until the first read or write operation. The first read operation against
|
|
// a database creates a SHARED lock and the first write operation creates a
|
|
// RESERVED lock. Because the acquisition of locks is deferred until they are
|
|
// needed, it is possible that another thread or process could create a
|
|
// separate transaction and write to the database after the BEGIN on the
|
|
// current thread has executed.
|
|
// - If the transaction is tbImmediate, then RESERVED locks are acquired
|
|
// on all databases as soon as the BEGIN command is executed, without waiting
|
|
// for the database to be used. After a BEGIN IMMEDIATE, no other database
|
|
// connection will be able to write to the database or do a BEGIN IMMEDIATE
|
|
// or BEGIN EXCLUSIVE. Other processes can continue to read from the database,
|
|
// however.
|
|
// - A tbExclusive transaction causes EXCLUSIVE locks to be acquired on all
|
|
// databases. After a BEGIN EXCLUSIVE, no other database connection except
|
|
// for read_uncommitted connections will be able to read the database and
|
|
// no other connection without exception will be able to write the database
|
|
// until the transaction is complete.
|
|
TSQLDataBaseTransactionBehaviour = (
|
|
tbDeferred,
|
|
tbImmediate,
|
|
tbExclusive);
|
|
|
|
{$M+}
|
|
TSQLDatabaseBackupThread = class;
|
|
{$M-}
|
|
|
|
/// callback called asynchronously during TSQLDatabase.BackupBackground()
|
|
// - implementation should return TRUE to continue the process: if the method
|
|
// returns FALSE, backup will be aborted, and destination file deleted
|
|
// - this method allows to monitor the backup process, thanks to
|
|
// TSQLDatabaseBackupThread properties (especialy the Step property)
|
|
// - this method will be executed in the context of the associated
|
|
// TSQLDatabaseBackupThread: so you should use Synchronize() to update the UI
|
|
TSQLDatabaseBackupEvent = function(Sender: TSQLDatabaseBackupThread): boolean of object;
|
|
|
|
/// simple wrapper for direct SQLite3 database manipulation
|
|
// - embed the SQLite3 database calls into a common object
|
|
// - thread-safe call of all SQLite3 queries (SQLITE_THREADSAFE 0 in sqlite.c)
|
|
// - can cache last results for SELECT statements, if property UseCache is true:
|
|
// this can speed up most read queries, for web server or client UI e.g.
|
|
TSQLDataBase = class(TSynPersistentLock)
|
|
protected
|
|
fDB: TSQLite3DB;
|
|
fFileName: TFileName;
|
|
fFileNameWithoutPath: TFileName;
|
|
fPageSize, fFileDefaultPageSize: cardinal;
|
|
fFileDefaultCacheSize: integer;
|
|
fIsMemory: boolean;
|
|
fPassword: RawUTF8;
|
|
fTransactionActive: boolean;
|
|
/// if not nil, cache is used - see UseCache property
|
|
fCache: TSynCache;
|
|
fInternalState: PCardinal;
|
|
fBusyTimeout: Integer;
|
|
fOpenV2Flags: Integer;
|
|
fBackupBackgroundInProcess: TSQLDatabaseBackupThread;
|
|
fBackupBackgroundLastTime: RawUTF8;
|
|
fBackupBackgroundLastFileName: TFileName;
|
|
fUseCacheSize: integer;
|
|
{$ifdef WITHLOG}
|
|
fLogResultMaximumSize: integer;
|
|
fLog: TSynLogClass;
|
|
{$endif}
|
|
/// store TSQLDataBaseSQLFunction instances
|
|
fSQLFunctions: TSynObjectList;
|
|
function GetUseCache: boolean;
|
|
procedure SetUseCache(const Value: boolean);
|
|
procedure SetBusyTimeout(const ms: Integer);
|
|
function GetUserVersion: cardinal;
|
|
procedure SetUserVersion(const Value: cardinal);
|
|
procedure SetWALMode(Value: Boolean);
|
|
function GetWALMode: boolean;
|
|
procedure SetSynchronous(const Value: TSQLSynchronousMode);
|
|
function GetSynchronous: TSQLSynchronousMode;
|
|
procedure SetLockingMode(const Value: TSQLLockingMode);
|
|
function GetLockingMode: TSQLLockingMode;
|
|
function GetCacheSize: cardinal;
|
|
procedure SetCacheSize(const Value: cardinal);
|
|
function GetPageSize: cardinal;
|
|
procedure SetPageSize(const Value: cardinal);
|
|
function GetPageCount: cardinal;
|
|
function GetFileSize: Int64;
|
|
function GetMemoryMappedMB: cardinal;
|
|
procedure SetMemoryMappedMB(const Value: cardinal);
|
|
function GetLimit(Category: TSQLLimitCategory): integer;
|
|
procedure SetLimit(Category: TSQLLimitCategory; Value: integer);
|
|
function GetBackupBackgroundInProcess: boolean;
|
|
function SQLShouldBeLogged(const aSQL: RawUTF8): boolean;
|
|
function GetSQLite3Library: TSQLite3Library; // class function = bug in D2005
|
|
public
|
|
/// enter the internal mutex: called before any DB access
|
|
// - provide the SQL statement about to be executed: handle proper caching
|
|
// - if the SQL statement is void, assume a SELECT statement (no cache flush)
|
|
procedure Lock(const aSQL: RawUTF8); overload;
|
|
/// enter the internal mutex without any cache flush
|
|
// - same as Lock('');
|
|
procedure Lock; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// flush the internal statement cache, and enter the internal mutex
|
|
// - same as Lock('ALTER');
|
|
procedure LockAndFlushCache;
|
|
/// leave the internal mutex: called after any DB access
|
|
procedure UnLock;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// enter the internal mutex: called before any DB access
|
|
// - provide the SQL statement about to be executed: handle proper caching
|
|
// - if this SQL statement has an already cached JSON response, return it and
|
|
// don't enter the internal mutex: no UnLockJSON() call is necessary
|
|
// - if this SQL statement is not a SELECT, cache is flushed and
|
|
// the next call to UnLockJSON() won't add any value to the cache since
|
|
// this statement is not a SELECT and doesn't have to be cached!
|
|
// - if aResultCount does map to an integer variable, it will be filled
|
|
// with the returned row count of data (excluding field names) in the result
|
|
function LockJSON(const aSQL: RawUTF8; aResultCount: PPtrInt): RawUTF8;
|
|
/// leave the internal mutex: called after any DB access
|
|
// - caller must provide the JSON result for the SQL statement previously set
|
|
// by LockJSON()
|
|
// - do proper caching of the JSON response for this SQL statement
|
|
procedure UnLockJSON(const aJSONResult: RawUTF8; aResultCount: PtrInt);
|
|
/// (re)open the database from file fFileName
|
|
// - TSQLDatabase.Create already opens the database: this method is to be
|
|
// used only on particular cases, e.g. to close temporary a DB file and
|
|
// allow making a backup on its content
|
|
// - returns the SQLITE_* status code, as retrieved from sqlite3.open()
|
|
// so that it should be SQLITE_OK on success
|
|
function DBOpen: integer; virtual;
|
|
/// close the opened database
|
|
// - TSQLDatabase.Destroy already closes the database: this method is to be
|
|
// used only on particular cases, e.g. to close temporary a DB file and
|
|
// allow making a backup on its content
|
|
// - returns the SQLITE_* status code, as retrieved from sqlite3.close(fDB)
|
|
// so that it should be SQLITE_OK on success
|
|
function DBClose: integer;
|
|
{$ifndef DELPHI5OROLDER}
|
|
/// for SQLite >= 3.11 - enable registation of a custom tokenizer
|
|
// - see details at http://sqlite.org/fts3.html#f3tknzr
|
|
function EnableCustomTokenizer: integer;
|
|
{$endif}
|
|
public
|
|
/// open a SQLite3 database file
|
|
// - open an existing database file or create a new one if no file exists
|
|
// - if specified, the password will be used to cypher this file on disk
|
|
// (the main SQLite3 database file is encrypted, not the wal file during run);
|
|
// the password may be a JSON-serialized TSynSignerParams object, or will use
|
|
// AES-OFB-128 after SHAKE_128 with rounds=1000 and a fixed salt on plain
|
|
// password text; note that our custom encryption is not compatible with the
|
|
// official SQLite Encryption Extension module
|
|
// - you can specify some optional flags for sqlite3.open_v2() as
|
|
// SQLITE_OPEN_READONLY or SQLITE_OPEN_READWRITE instead of supplied default
|
|
// value (which corresponds to the sqlite3.open() behavior)
|
|
// - by default, 10000 pages are used to cache data in memory (using around
|
|
// 40 MB of RAM), but you may specify another value for performance tuning
|
|
// - SYSTEMNOCASE collation is added (our custom fast UTF-8 case insensitive
|
|
// UTF8ILComp() function, which is used also in the SQLite3UI unit for
|
|
// coherency and efficiency
|
|
// - ISO8601 collation is added (TDateTime stored as ISO-8601 encoded TEXT)
|
|
// - WIN32CASE and WIN32NOCASE collations are added (use slow but accurate Win32 CompareW)
|
|
// - some additional SQl functions are registered: MOD, SOUNDEX/SOUNDEXFR/SOUNDEXES,
|
|
// RANK, CONCAT, TIMELOG, TIMELOGUNIX, JSONGET/JSONHAS/JSONSET and TDynArray-Blob
|
|
// Byte/Word/Integer/Cardinal/Int64/Currency/RawUTF8DynArrayContains
|
|
// - initialize a internal mutex to ensure that all access to the database is atomic
|
|
// - raise an ESQLite3Exception on any error
|
|
constructor Create(const aFileName: TFileName; const aPassword: RawUTF8='';
|
|
aOpenV2Flags: integer=0; aDefaultCacheSize: integer=10000;
|
|
aDefaultPageSize: integer=4096); reintroduce;
|
|
/// close a database and free its memory and context
|
|
//- if TransactionBegin was called but not commited, a RollBack is performed
|
|
destructor Destroy; override;
|
|
/// Execute all SQL statements in aSQL UTF-8 encoded string
|
|
// - can be prepared with TransactionBegin()
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure ExecuteAll(const aSQL: RawUTF8);
|
|
/// Execute one SQL statements in aSQL UTF-8 encoded string
|
|
// - can be prepared with TransactionBegin()
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure Execute(const aSQL: RawUTF8); overload;
|
|
/// Execute one SQL statement which return integers from the aSQL UTF-8 encoded string
|
|
// - Execute the first statement in aSQL
|
|
// - this statement must get a one field/column result of INTEGER
|
|
// - return result as a dynamic array of RawUTF8, as TEXT result
|
|
// - return count of row in integer function result (may be < length(ID))
|
|
// - raise an ESQLite3Exception on any error
|
|
function Execute(const aSQL: RawUTF8; var ID: TInt64DynArray): integer; overload;
|
|
/// Execute one SQL statement returning TEXT from the aSQL UTF-8 encoded string
|
|
// - Execute the first statement in aSQL
|
|
// - this statement must get (at least) one field/column result of TEXT
|
|
// - return result as a dynamic array of RawUTF8 in ID
|
|
// - return count of row in integer function result (may be < length(ID))
|
|
// - raise an ESQLite3Exception on any error
|
|
function Execute(const aSQL: RawUTF8; var Values: TRawUTF8DynArray): integer; overload;
|
|
/// Execute one SQL statement which returns one integer from the aSQL UTF-8 encoded string
|
|
// - Execute the first statement in aSQL
|
|
// - this statement must get a one field/column result of INTEGER
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure Execute(const aSQL: RawUTF8; out ID: Int64; NoLog: boolean=false); overload;
|
|
/// Execute one SQL statement which returns one UTF-8 encoded string value
|
|
// - Execute the first statement in aSQL
|
|
// - this statement must get a one field/column result of TEXT
|
|
// - raise an ESQLite3Exception on any error
|
|
procedure Execute(const aSQL: RawUTF8; out ID: RawUTF8; NoLog: boolean=false); overload;
|
|
/// Execute one SQL statements in aSQL UTF-8 encoded string
|
|
// - can be prepared with TransactionBegin()
|
|
// - raise no Exception on error, but returns FALSE in such case
|
|
function ExecuteNoException(const aSQL: RawUTF8): boolean;
|
|
/// Seamless execution of a SQL statement which returns one integer
|
|
// - Execute the first statement in aSQL
|
|
// - this statement must get a one field/column result of INTEGER
|
|
// - returns 0 on any error
|
|
function ExecuteNoExceptionInt64(const aSQL: RawUTF8): Int64;
|
|
/// Seamless execution of a SQL statement which returns one UTF-8 encoded string
|
|
// - Execute the first statement in aSQL
|
|
// - this statement must get a one field/column result of TEXT
|
|
// - returns '' on any error
|
|
function ExecuteNoExceptionUTF8(const aSQL: RawUTF8): RawUTF8;
|
|
/// Execute one SQL statement returning its results in JSON format
|
|
// - the BLOB data is encoded as '"\uFFF0base64encodedbinary"'
|
|
function ExecuteJSON(const aSQL: RawUTF8; Expand: boolean=false; aResultCount: PPtrInt=nil): RawUTF8;
|
|
/// begin a transaction
|
|
// - Execute SQL statements with Execute() procedure below
|
|
// - must be ended with Commit on success
|
|
// - must be aborted with Rollback after an ESQLite3Exception raised
|
|
// - The default transaction behavior is tbDeferred
|
|
procedure TransactionBegin(aBehavior: TSQLDataBaseTransactionBehaviour = tbDeferred);
|
|
/// end a transaction: write all Execute() statements to the disk
|
|
procedure Commit;
|
|
/// abort a transaction: restore the previous state of the database
|
|
procedure RollBack;
|
|
/// return the last Insert Rowid
|
|
function LastInsertRowID: Int64;
|
|
/// count the number of rows modified by the last SQL statement
|
|
// - this method 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.
|
|
// - wrapper around the sqlite3.changes() low-level function
|
|
function LastChangeCount: integer;
|
|
/// return the number of row changes caused by INSERT, UPDATE or
|
|
// DELETE statements since the database connection was opened
|
|
// - wrapper around the sqlite3.total_changes() low-level function
|
|
function TotalChangeCount: integer;
|
|
|
|
/// get all table names contained in this database file
|
|
procedure GetTableNames(var Names: TRawUTF8DynArray);
|
|
/// get all field names for a specified Table
|
|
procedure GetFieldNames(var Names: TRawUTF8DynArray; const TableName: RawUTF8);
|
|
/// check if the given table do exist
|
|
function HasTable(const Name: RawUTF8): boolean;
|
|
/// add a SQL custom function to the SQLite3 database engine
|
|
// - the supplied aFunction instance will be used globally and freed
|
|
// by TSQLDataBase.Destroy destructor
|
|
// - will do nothing if the same function name and parameters count have
|
|
// already been registered (you can register then same function name with
|
|
// several numbers of parameters)
|
|
// - you may use the overloaded function, which is a wrapper around:
|
|
// ! Demo.RegisterSQLFunction(
|
|
// ! TSQLDataBaseSQLFunction.Create(InternalSQLFunctionCharIndex,2,'CharIndex'));
|
|
procedure RegisterSQLFunction(aFunction: TSQLDataBaseSQLFunction); overload;
|
|
/// add a SQL custom function to the SQLite3 database engine
|
|
// - will do nothing if the same function name and parameters count have
|
|
// already been registered (you can register then same function name with
|
|
// several numbers of parameters)
|
|
// - typical use may be:
|
|
// ! Demo.RegisterSQLFunction(InternalSQLFunctionCharIndex,2,'CharIndex');
|
|
procedure RegisterSQLFunction(aFunction: TSQLFunctionFunc;
|
|
aFunctionParametersCount: Integer; const aFunctionName: RawUTF8); overload;
|
|
/// add a SQL custom function for a dynamic array to the database
|
|
// - the resulting SQL function will expect two parameters: the first is the
|
|
// BLOB field content, and the 2nd is the array element to search (as set with
|
|
// TDynArray.ElemSave() or with BinToBase64WithMagic(aDynArray.ElemSave())
|
|
// if called via a Client and a JSON prepared parameter)
|
|
// - if the function name is not specified, it will be retrieved from the type
|
|
// information (e.g. TReferenceDynArray will declare 'ReferenceDynArray')
|
|
// - you should better use the already existing faster SQL functions
|
|
// Byte/Word/Integer/Cardinal/Int64/CurrencyDynArrayContains() if possible
|
|
// (this implementation will allocate each dynamic array into memory before
|
|
// comparison, and will be therefore slower than those optimized versions -
|
|
// but it will be always faster than Client-Server query, in all cases)
|
|
procedure RegisterSQLFunction(aDynArrayTypeInfo: pointer; aCompare: TDynArraySortCompare;
|
|
const aFunctionName: RawUTF8=''); overload;
|
|
|
|
/// open a BLOB incrementally for read[/write] access
|
|
// - find a BLOB located in row RowID, column ColumnName, table TableName
|
|
// in database DBName; in other words, the same BLOB that would be selected by:
|
|
// ! SELECT ColumnName FROM DBName.TableName WHERE rowid = RowID;
|
|
// - use after a TSQLRequest.BindZero() to reserve Blob memory
|
|
// - if RowID=0, then the last inserted RowID is used (beware that this
|
|
// value won't be thread-safe, if another thread run another INSERT)
|
|
// - will raise an ESQLite3Exception on any error
|
|
function Blob(const DBName, TableName, ColumnName: RawUTF8;
|
|
RowID: Int64; ReadWrite: boolean=false): TSQLBlobStream;
|
|
/// backup of the opened Database into an external file name
|
|
// - warning: this method won't use the SQLite Online Backup API
|
|
// - database is closed, VACCUUMed, copied, then reopened: it's very fast for
|
|
// small databases, but is blocking and should be an issue
|
|
// - if you use some virtual tables, they won't be restored after backup:
|
|
// this method would probably fail e.g. in the context of mORMot.pas
|
|
function Backup(const BackupFileName: TFileName): boolean;
|
|
/// backup of the opened Database into an external file name
|
|
// - this method will use the SQLite Online Backup API and a dedicated
|
|
// background thread for the process
|
|
// - this will be asynchronous, and would block the main database process
|
|
// only when copying the StepPageNumber numer of pages for each step,
|
|
// waiting StepSleepMS milliseconds before performing the next step:
|
|
// as a result, the copy operation can be done incrementally, by blocks of
|
|
// StepPageNumber pages, in which case the source database does not need
|
|
// to be locked for the duration of the copy, only for the brief periods of
|
|
// time when it is actually being read from: this allows other database
|
|
// users to continue uninterrupted (at least during StepSleepMS
|
|
// milliseconds) while a backup is running
|
|
// - if StepPageNumber is -1, the whole DB will be copied in a single step,
|
|
// therefore in blocking mode, e.g. with BackupBackgroundWaitUntilFinished
|
|
// - if SynLzCompress is TRUE, the backup file would be compressed using
|
|
// FileSynLZ() function - you may use BackupUnSynLZ() class method to
|
|
// uncompress the .dbsynlz file into a proper SQLite3 file
|
|
// - the supplied OnProgress event handler will be called at each step, in
|
|
// the context of the background thread
|
|
// - the background thread will be released when the process is finished
|
|
// - if only one connection to the database does exist (e.g. if you use only
|
|
// one TSQLDataBase instance on the same database file), any modification
|
|
// to the source database during the background process will be included in
|
|
// the backup - so this method will work perfectly e.g. for mORMot.pas
|
|
// - if specified, a password will be used to cypher BackupFileName on disk
|
|
// (it will work only with SynSQLite3Static) - you can uncypher the resulting
|
|
// encrypted database file later via ChangeSQLEncryptTablePassWord()
|
|
// - returns TRUE if backup started as expected, or FALSE in case of error
|
|
// (e.g. if there is already another backup started, if the source or
|
|
// destination databases are locked or invalid, or if the sqlite3.dll is too
|
|
// old and does not support the Online Backup API)
|
|
// - you can also use this method to save an SQLite3 ':memory:' database,
|
|
// perhaps in conjunction with the BackupBackgroundWaitUntilFinished method
|
|
function BackupBackground(const BackupFileName: TFileName;
|
|
StepPageNumber, StepSleepMS: Integer; OnProgress: TSQLDatabaseBackupEvent;
|
|
SynLzCompress: boolean=false; const aPassword: RawUTF8=''): boolean;
|
|
/// background backup to another opened database instance
|
|
// - in respect to BackupBackground method, it will use an existing database
|
|
// the actual process
|
|
// - by design, SynLZCompress or aPassword parameters are unavailable
|
|
function BackupBackgroundToDB(BackupDB: TSQLDatabase;
|
|
StepPageNumber, StepSleepMS: Integer; OnProgress: TSQLDatabaseBackupEvent): boolean;
|
|
/// wait until any previous BackupBackground() is finished
|
|
// - warning: this method won't call the Windows message loop, so should not
|
|
// be called from main thread, unless the UI may become unresponsive: you
|
|
// should better rely on OnProgress() callback for any GUI application
|
|
// - by default, it will wait for ever so that process is finished, but you
|
|
// can set a time out (in seconds) after which the process will be aborted
|
|
// - could be used with BackupBackground() and StepPageNumber=-1 to perform
|
|
// a whole copy of a database in one shot:
|
|
// ! if aDB.BackupBackground('backup.db3',-1,0,nil) then
|
|
// ! aDB.BackupBackgroundWaitUntilFinished;
|
|
procedure BackupBackgroundWaitUntilFinished(TimeOutSeconds: Integer=-1);
|
|
/// uncompress a .dbsynlz backup file as previously compressed with BackupSynLZ()
|
|
// or if SynLZCompress parameter is TRUE for BackupBackground() method
|
|
// - any DestDB file name would be overwritten
|
|
// - returns TRUE on success, FALSE on failure
|
|
class function BackupUnSynLZ(const SourceSynLZ, DestDB: TFileName): boolean;
|
|
/// compress a SQlite3 file into a proprietary but efficient .dbsynlz layout
|
|
// - same format than BackupUnSynLZ() class method or if SynLZCompress
|
|
// parameter is TRUE for BackupBackground() method
|
|
// - the SourceDB file should not be active (e.g. be a backup file), i.e.
|
|
// not currently opened by the SQlite3 engine, otherwise behavior is unknown
|
|
// - returns TRUE on success, FALSE on failure
|
|
class function BackupSynLZ(const SourceDB, DestSynLZ: TFileName;
|
|
EraseSourceDB: boolean): boolean;
|
|
/// returns TRUE if the supplied name is a SQlite3 .dbsynlz compressed file
|
|
// - i.e. on the format generated by the BackupUnSynLZ() class method or
|
|
// if SynLZCompress parameter is TRUE for BackupBackground() method
|
|
class function IsBackupSynLZFile(const SynLZFile: TFileName): boolean;
|
|
/// flush the internal SQL-based JSON cache content
|
|
// - to be called when the regular Lock/LockJSON methods are not called,
|
|
// e.g. with external tables as defined in SQLite3DB unit
|
|
// - will also increment the global InternalState property value (if set)
|
|
procedure CacheFlush;
|
|
|
|
/// read-only access to the SQLite3 database handle
|
|
property DB: TSQLite3DB read fDB;
|
|
/// read-only access to the SQlite3 password used for encryption
|
|
// - may be a JSON-serialized TSynSignerParams object, or will use AES-OFB-128
|
|
// after SHAKE_128 with rounds=1000 and a fixed salt on plain password text
|
|
property Password: RawUTF8 read fPassword;
|
|
/// read-only access to the SQLite3 database filename opened without its path
|
|
property FileNameWithoutPath: TFileName read fFileNameWithoutPath;
|
|
/// access to the internal JSON cache, used by ExecuteJSON() method
|
|
// - see UseCache property and CacheFlush method
|
|
property Cache: TSynCache read fCache;
|
|
/// retrieve of define a limit on the current database connection
|
|
// - see TSQLLimitCategory for a details of all available limits
|
|
// - see @http://www.sqlite.org/c3ref/limit.html
|
|
property Limit[Category: TSQLLimitCategory]: integer read GetLimit write SetLimit;
|
|
{$ifdef WITHLOG}
|
|
/// access to the log class associated with this SQLite3 database engine
|
|
// - can be customized, e.g. by overriden TSQLRestServerDB.SetLogClass()
|
|
property Log: TSynLogClass read fLog write fLog;
|
|
/// sets a maximum size (in bytes) to be logged as sllResult rows
|
|
// - by default, is set to 512 bytes, which sounds a good compromise
|
|
// since it does not make sense to log all the JSON content retrieved from
|
|
// the database engine, when a huge SELECT is executed
|
|
property LogResultMaximumSize: integer read fLogResultMaximumSize write fLogResultMaximumSize;
|
|
{$endif}
|
|
/// this integer pointer (if not nil) is incremented when any SQL statement
|
|
// changes the database contents (i.e. any not SELECT statement)
|
|
// - this pointer is thread-safe updated, inside a critical section
|
|
property InternalState: PCardinal read fInternalState write fInternalState;
|
|
published
|
|
/// read-only access to the SQLite3 database filename opened
|
|
property FileName: TFileName read fFileName;
|
|
/// equals TRUE if the SQLite3 database was created as ':memory:'
|
|
// (i.e. SQLITE_MEMORY_DATABASE_NAME)
|
|
property IsMemory: boolean read fIsMemory;
|
|
/// if this property is set, all ExecuteJSON() responses will be cached
|
|
// - cache is flushed on any write access to the DB (any not SELECT statement)
|
|
// - cache is consistent only if ExecuteJSON() Expand parameter is constant
|
|
// - cache is used by TSQLDataBase.ExecuteJSON() and TSQLTableDB.Create()
|
|
property UseCache: boolean read GetUseCache write SetUseCache;
|
|
/// cache size in JSON bytes, to be set before UseCache is set to true
|
|
// - default is 16MB
|
|
property UseCacheSize: integer read fUseCacheSize write fUseCacheSize;
|
|
/// return TRUE if a Transaction begun
|
|
property TransactionActive: boolean read fTransactionActive;
|
|
/// sets a busy handler that sleeps for a specified amount of time
|
|
// (in milliseconds) when a table is locked, before returning an error
|
|
property BusyTimeout: Integer read fBusyTimeout write SetBusyTimeout;
|
|
/// query or change the suggested maximum number of database disk pages
|
|
// that SQLite will hold in memory at once per open database file
|
|
// - DBOpen method will set this cache size to a big 10000 default, which
|
|
// sounds reasonnable in the context of a server application (will use
|
|
// up to 40 MB of memory cache, with the default PageSize of 4096 bytes)
|
|
// - when you change the cache size using the cache_size pragma, the change
|
|
// only endures for the current session. The cache size reverts to the
|
|
// default value when the database is closed and reopened
|
|
// - we do not handle negative values here (i.e. KB of RAM), since it won't
|
|
// work if the linked SQLite3 library is version 3.7.9 and earlier
|
|
property CacheSize: cardinal read GetCacheSize write SetCacheSize;
|
|
/// query or change the page size of the database
|
|
// - the page size must be a power of two between 512 and 65536 inclusive
|
|
// - DBOpen method will set the PageSize to 4096 (if the database is not
|
|
// encrypted), which sounds better than the default 1024 value - you should
|
|
// not have to set this property usually
|
|
// - setting this property will only cause an immediate change in the page
|
|
// size if it is issued while the database is still empty, prior to the
|
|
// first CREATE TABLE statement; if this property is used to specify a new
|
|
// page size just prior to running the VACUUM command and if the database
|
|
// is not in WAL journal mode then VACUUM will change the page size to the
|
|
// new value for the newly created database file
|
|
property PageSize: cardinal read GetPageSize write SetPageSize;
|
|
/// return the total number of pages in the database file
|
|
property PageCount: cardinal read GetPageCount;
|
|
/// return the total number of bytes in the database file
|
|
// - computes PageSize*PageCount
|
|
property FileSize: Int64 read GetFileSize;
|
|
/// query or change the Write-Ahead Logging mode for the database
|
|
// - beginning with version 3.7 of the SQLite3 engine, a new "Write-Ahead Log"
|
|
// option (hereafter referred to as "WAL") is optionaly available
|
|
// - WAL might be very slightly slower (perhaps 1% or 2% slower) than the
|
|
// traditional rollback-journal approach in applications that do mostly reads
|
|
// and seldom write; but WAL provides more concurrency as readers do not block
|
|
// writers and a writer does not block readers. Reading and writing can
|
|
// proceed concurrently. With our SQLite3 framework, it's not needed.
|
|
// - by default, this option is not set: only implement if you really need it,
|
|
// but our SQlite3 framework use locked access to the databse, so there
|
|
// should be no benefit of WAL for the framework; but if you call
|
|
// directly TSQLDatabase instances in your code, it may be useful to you
|
|
property WALMode: Boolean read GetWALMode write SetWALMode;
|
|
/// query or change the SQlite3 file-based syncrhonization mode, i.e. the
|
|
// way it waits for the data to be flushed on hard drive
|
|
// - default smFull is very slow, but achieve 100% ACID behavior
|
|
// - smNormal is faster, and safe until a catastrophic hardware failure occurs
|
|
// - smOff is the fastest, data should be safe if the application crashes,
|
|
// but database file may be corrupted in case of failure at the wrong time
|
|
property Synchronous: TSQLSynchronousMode read GetSynchronous write SetSynchronous;
|
|
/// query or change the SQlite3 file-based locking mode, i.e. the
|
|
// way it locks the file
|
|
// - default lmNormal is ACID and safe
|
|
// - lmExclusive gives better performance in case of a number of write
|
|
// transactions, so can be used to release a mORMot server power: but you
|
|
// won't be able to access the database file from outside the process (like
|
|
// a "normal" database engine)
|
|
property LockingMode: TSQLLockingMode read GetLockingMode write SetLockingMode;
|
|
/// enables or disables disk content access using memory-mapped I/O
|
|
// - 0 to disable it (the default, because of potential disadvantages)
|
|
// - set to a number of Mega Bytes value of memory for the mapping
|
|
// - expects a SQLite3 engine version >= 3.7.17
|
|
// - Memory-Mapped I/O is NOT compatible with password encryption as
|
|
// implemented in our SynSQLite3Static unit
|
|
property MemoryMappedMB: cardinal read GetMemoryMappedMB write SetMemoryMappedMB;
|
|
/// retrieve or set the user_version stored in the SQLite3 database file
|
|
// - user-version is a 32-bit signed integer stored in the database header
|
|
//- it can be used to change the database in case of format upgrade (e.g.
|
|
// refresh some hand-made triggers)
|
|
property user_version: cardinal read GetUserVersion write SetUserVersion;
|
|
/// reflects how the database connection was created in the constructor
|
|
property OpenV2Flags: Integer read fOpenV2Flags;
|
|
/// is set to TRUE while a BackupBackground() process is still running
|
|
// - see also BackupBackgroundWaitUntilFinished() method
|
|
property BackupBackgroundInProcess: boolean read GetBackupBackgroundInProcess;
|
|
/// how much time did the latest BackupBackground() finished process take
|
|
property BackupBackgroundLastTime: RawUTF8 read fBackupBackgroundLastTime;
|
|
/// the latest BackupBackground() process file name
|
|
property BackupBackgroundLastFileName: TFileName read fBackupBackgroundLastFileName;
|
|
/// the SQLite3 library which is currently running
|
|
// - part of TSQLDatabase published properties, to publish e.g. Version
|
|
property SQLite3Library: TSQLite3Library read GetSQLite3Library;
|
|
end;
|
|
|
|
/// used to read or write a BLOB Incrementaly
|
|
// - data is read/written directly from/to the SQLite3 BTree
|
|
// - data can be written after a TSQLRequest.BindZero() call to reserve memory
|
|
// - this TStream has a fixed size, but Position property can be used to rewind
|
|
TSQLBlobStream = class(TStream)
|
|
protected
|
|
fBlob: TSQLite3Blob;
|
|
fDB: TSQLite3DB;
|
|
fSize, fPosition: integer;
|
|
fWritable: boolean;
|
|
public
|
|
/// Opens a BLOB located in row RowID, column ColumnName, table TableName
|
|
// in database DBName; in other words, the same BLOB that would be selected by:
|
|
// ! SELECT ColumnName FROM DBName.TableName WHERE rowid = RowID;
|
|
constructor Create(aDB: TSQLite3DB; const DBName, TableName,
|
|
ColumnName: RawUTF8; RowID: Int64; ReadWrite: boolean);
|
|
/// release the BLOB object
|
|
destructor Destroy; override;
|
|
/// read Count bytes from the opened BLOB in Buffer
|
|
function Read(var Buffer; Count: Longint): Longint; override;
|
|
/// write is allowed for in-place replacement (resizing is not allowed)
|
|
// - Create() must have been called with ReadWrite=true
|
|
function Write(const Buffer; Count: Longint): Longint; override;
|
|
/// change the current read position
|
|
function Seek(Offset: Longint; Origin: Word): Longint; override;
|
|
/// reuse this class instance with another row of the same table
|
|
// - will update the stream size, and also rewind position to the beginning
|
|
// - it is actually faster than creating a new TSQLBlobStream instance
|
|
procedure ChangeRow(RowID: Int64);
|
|
/// read-only access to the BLOB object handle
|
|
property Handle: TSQLite3Blob read fBlob;
|
|
end;
|
|
|
|
/// kind of event triggerred during TSQLDatabase.BackupBackground() process
|
|
// - you can use (Sender.Step in backupAnyStep), to check for normal step,
|
|
// or (Sender.Step in backupFinished) to check for process end
|
|
TSQLDatabaseBackupEventStep = (
|
|
backupNone, backupStart, backupSuccess, backupFailure,
|
|
backupStepOk, backupStepBusy, backupStepLocked, backupStepSynLz);
|
|
|
|
/// background thread used for TSQLDatabase.BackupBackground() process
|
|
TSQLDatabaseBackupThread = class(TThread)
|
|
protected
|
|
fBackupDestFile: TFileName;
|
|
fSourceDB: TSQLDatabase;
|
|
fDestDB: TSQLDatabase;
|
|
fStepPageNumber, fStepSleepMS: Integer;
|
|
fBackup: TSQLite3Backup;
|
|
fStep: TSQLDatabaseBackupEventStep;
|
|
fStepNumberToFinish, fStepNumberTotal: integer;
|
|
fStepSynLzCompress: boolean;
|
|
fOnProgress: TSQLDatabaseBackupEvent;
|
|
fError: Exception;
|
|
fTimer: TPrecisionTimer;
|
|
fOwnerDest: boolean;
|
|
/// main process
|
|
procedure Execute; override;
|
|
public
|
|
/// initialize the background thread
|
|
// - execution is started immediately - caller may call the WaitFor
|
|
// inherited method to run the process in blocking mode
|
|
constructor Create(Backup: TSQLite3Backup; Source, Dest: TSQLDatabase;
|
|
StepPageNumber,StepSleepMS: Integer; SynLzCompress: boolean;
|
|
OnProgress: TSQLDatabaseBackupEvent; OwnerDest: boolean=true); reintroduce;
|
|
/// the source database of the backup process
|
|
property SourceDB: TSQLDatabase read fSourceDB;
|
|
/// the destination database of the backup process
|
|
property DestDB: TSQLDatabase read fDestDB;
|
|
/// the raised exception in case of backupFailure notification
|
|
property FailureError: Exception read fError;
|
|
/// the backup target database file name
|
|
property BackupDestFile: TFileName read fBackupDestFile;
|
|
published
|
|
/// the current state of the backup process
|
|
// - only set before a call to TSQLDatabaseBackupEvent
|
|
property Step: TSQLDatabaseBackupEventStep read fStep;
|
|
/// the number of pages which remain before end of backup
|
|
// - only set before a call to TSQLDatabaseBackupEvent with backupStep* event
|
|
property StepNumberToFinish: integer read fStepNumberToFinish;
|
|
/// the number of pages for the whole database
|
|
// - only set before a call to TSQLDatabaseBackupEvent with backupStep* event
|
|
property StepNumberTotal: integer read fStepNumberTotal;
|
|
/// if .dbsynlz compression would be done on the backup file
|
|
// - would use FileSynLZ(), so compress in chunks of 128 MB
|
|
property StepSynLzCompress: boolean read fStepSynLzCompress;
|
|
end;
|
|
|
|
const
|
|
/// identify the iterative step events during TSQLDatabase.BackupBackground()
|
|
// - you can use (Sender.Step in backupAnyStep), to check for normal step
|
|
backupAnyStep = [backupStepOk,backupStepBusy,backupStepLocked,backupStepSynLz];
|
|
/// identify the end step events during TSQLDatabase.BackupBackground()
|
|
// - you can use (Sender.Step in backupFinished) to check for process end
|
|
backupFinished = [backupSuccess,backupFailure];
|
|
|
|
|
|
{$ifdef WITHLOG}
|
|
var
|
|
/// the TSynLog class used for logging for all our SynSQlite3 related functions
|
|
// - you may override it with TSQLLog, if available from mORMot.pas
|
|
// - since not all exceptions are handled specificaly by this unit, you
|
|
// may better use a common TSynLog class for the whole application or module
|
|
SynSQLite3Log: TSynLogClass = TSynLog;
|
|
{$endif}
|
|
|
|
|
|
/// check from the file beginning if sounds like a valid SQLite3 file
|
|
// - returns true if a database file is encrypted or not
|
|
// - optional retrieve the file page size from header
|
|
function IsSQLite3File(const FileName: TFileName; PageSize: PInteger=nil): boolean;
|
|
|
|
/// check if sounds like an encrypted SQLite3 file
|
|
function IsSQLite3FileEncrypted(const FileName: TFileName): boolean;
|
|
|
|
/// comparison function using TSQLStatementCache.Timer.TimeInMicroSec
|
|
function StatementCacheTotalTimeCompare(const A,B): integer;
|
|
|
|
|
|
const
|
|
/// a magic text constant which will prevent any JSON result to be cached
|
|
// in TSQLDataBase, if present in the SQL statement
|
|
// - to be used e.g. when you put some pointers as bound parameters
|
|
SQLDATABASE_NOCACHE: RawUTF8 = '/*nocache*/';
|
|
|
|
/// the "magic" number used to identify .dbsynlz compressed files, as
|
|
// created by TSQLDataBase.BackupSynLZ() or if SynLZCompress parameter is TRUE
|
|
// for the TSQLDataBase.BackupBackground() method
|
|
// - note that the SynDBExplorer tool is able to recognize such files, and
|
|
// open them directly - or use the DBSynLZ.dpr command-line sample tool
|
|
SQLITE3_MAGIC = $ABA5A5AB;
|
|
|
|
/// the "magic" 16 bytes header stored at the begining of every SQlite3 file
|
|
SQLITE_FILE_HEADER: array[0..15] of AnsiChar = 'SQLite format 3';
|
|
var
|
|
SQLITE_FILE_HEADER128: THash128Rec absolute SQLITE_FILE_HEADER;
|
|
|
|
|
|
implementation
|
|
|
|
{ ************ direct access to sqlite3.c / sqlite3.obj consts and functions }
|
|
|
|
function IsSQLite3File(const FileName: TFileName; PageSize: PInteger): boolean;
|
|
var F: THandle;
|
|
Header: THash256Rec;
|
|
begin
|
|
F := FileOpen(FileName,fmOpenRead or fmShareDenyNone);
|
|
if F=INVALID_HANDLE_VALUE then
|
|
result := false else begin
|
|
result := (FileRead(F,Header,sizeof(Header))=SizeOf(Header)) and
|
|
(Header.d0=SQLITE_FILE_HEADER128.Lo) and
|
|
// don't check header 8..15 (may equal encrypted bytes 16..23)
|
|
(Header.b[21]=64) and (Header.b[22]=32) and (Header.b[23]=32);
|
|
if result and (PageSize<>nil) then
|
|
// header bytes 16..23 are always stored unencrypted
|
|
PageSize^ := Integer(Header.b[16]) shl 8+Header.b[17];
|
|
FileClose(F);
|
|
end;
|
|
end;
|
|
|
|
function IsSQLite3FileEncrypted(const FileName: TFileName): boolean;
|
|
var F: THandle;
|
|
Header: THash256Rec;
|
|
begin // see CodecEncrypt/CodecDecrypt in SynSQLite3Static
|
|
result := false;
|
|
F := FileOpen(FileName,fmOpenRead or fmShareDenyNone);
|
|
if F=INVALID_HANDLE_VALUE then
|
|
exit;
|
|
if (FileRead(F,Header,SizeOf(Header))=SizeOf(Header)) and
|
|
(Header.d0=SQLITE_FILE_HEADER128.Lo) and
|
|
// header bytes 8..15 are encrypted bytes 16..23
|
|
(Header.d1<>SQLITE_FILE_HEADER128.Hi) and
|
|
// header bytes 16..23 are stored unencrypted
|
|
(Header.b[21]=64) and (Header.b[22]=32) and (Header.b[23]=32) then
|
|
result := true;
|
|
FileClose(F);
|
|
end;
|
|
|
|
|
|
// ************ objects to access SQLite3 database engine
|
|
|
|
|
|
{ TSQLDataBase }
|
|
|
|
function TSQLDataBase.Blob(const DBName, TableName, ColumnName: RawUTF8;
|
|
RowID: Int64; ReadWrite: boolean): TSQLBlobStream;
|
|
begin
|
|
if self=nil then begin
|
|
result := nil;
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
end;
|
|
Lock;
|
|
try
|
|
if RowID=0 then
|
|
RowID := LastInsertRowID; // warning: won't work on multi-thread process
|
|
result := TSQLBlobStream.Create(DB,DBName,TableName,ColumnName,RowID,ReadWrite);
|
|
finally
|
|
UnLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDataBase.Rollback;
|
|
begin
|
|
if (self=nil) or not fTransactionActive then
|
|
exit;
|
|
Execute('ROLLBACK TRANSACTION;');
|
|
fTransactionActive := false;
|
|
end;
|
|
|
|
procedure TSQLDataBase.TransactionBegin(aBehavior: TSQLDataBaseTransactionBehaviour = tbDeferred);
|
|
const
|
|
TBTOKENS: array[TSQLDataBaseTransactionBehaviour] of RawUTF8 = (
|
|
'', 'IMMEDIATE ', 'EXCLUSIVE '); // see http://www.sqlite.org/lang_transaction.html
|
|
begin
|
|
if self=nil then
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
if fTransactionActive then
|
|
try
|
|
Execute('ROLLBACK TRANSACTION;');
|
|
finally
|
|
fTransactionActive := false;
|
|
end;
|
|
Execute('BEGIN '+TBTOKENS[aBehavior]+'TRANSACTION;');
|
|
fTransactionActive := true;
|
|
end;
|
|
|
|
procedure TSQLDataBase.Commit;
|
|
begin
|
|
if (Self<>nil) and fTransactionActive then
|
|
try
|
|
Execute('COMMIT TRANSACTION;');
|
|
finally
|
|
fTransactionActive := false;
|
|
end;
|
|
end;
|
|
|
|
{ from WladiD about all SQLite3 collation functions:
|
|
If a field with your custom collate ISO8601 is empty '' (not NULL),
|
|
then SQLite calls the registered collate function with s1len=0 or s2len=0,
|
|
but the pointers s1 or s2 map to the string of the previous call }
|
|
|
|
function Utf16SQLCompCase(CollateParam: pointer; s1Len: integer; S1: pointer;
|
|
s2Len: integer; S2: pointer) : integer; cdecl;
|
|
begin
|
|
if s1Len<=0 then
|
|
if s2Len<=0 then
|
|
result := 0 else
|
|
result := -1 else
|
|
if s2Len<=0 then
|
|
result := 1 else
|
|
result := CompareStringW(
|
|
LOCALE_USER_DEFAULT,0,S1,s1len shr 1,S2,s2Len shr 1)-2;
|
|
end;
|
|
|
|
function Utf16SQLCompNoCase(CollateParam: pointer; s1Len: integer; s1: pointer;
|
|
s2Len: integer; s2: pointer) : integer; cdecl;
|
|
begin
|
|
if s1Len<=0 then
|
|
if s2Len<=0 then
|
|
result := 0 else
|
|
result := -1 else
|
|
if s2Len<=0 then
|
|
result := 1 else
|
|
result := CompareStringW(
|
|
LOCALE_USER_DEFAULT,NORM_IGNORECASE,S1,s1len shr 1,S2,s2Len shr 1)-2;
|
|
end;
|
|
|
|
function Utf8SQLCompNoCase(CollateParam: pointer; s1Len: integer; s1: pointer;
|
|
s2Len: integer; s2: pointer) : integer; cdecl;
|
|
begin
|
|
if s1Len<=0 then
|
|
if s2Len<=0 then
|
|
result := 0 else
|
|
result := -1 else
|
|
if s2Len<=0 then
|
|
result := 1 else
|
|
result := UTF8ILComp(s1,s2,s1Len,s2Len);
|
|
end;
|
|
|
|
function Utf8SQLDateTime(CollateParam: pointer; s1Len: integer; s1: pointer;
|
|
s2Len: integer; s2: pointer) : integer; cdecl;
|
|
var V1,V2: TDateTime; // will handle up to .sss milliseconds resolution
|
|
begin
|
|
if s1Len<=0 then // see WladiD note above
|
|
s1 := nil;
|
|
if s2Len<=0 then
|
|
s2 := nil;
|
|
if s1=s2 then
|
|
result := 0 else begin
|
|
Iso8601ToDateTimePUTF8CharVar(s1,s1Len,V1);
|
|
Iso8601ToDateTimePUTF8CharVar(s2,s2Len,V2);
|
|
if (V1=0) or (V2=0) then // any invalid date -> compare as UTF-8 strings
|
|
result := UTF8ILComp(s1,s2,s1Len,s2Len) else
|
|
if SameValue(V1,V2,1/MSecsPerDay) then
|
|
result := 0 else
|
|
if V1<V2 then
|
|
result := -1 else
|
|
result := +1;
|
|
end;
|
|
end;
|
|
|
|
const
|
|
NULCHAR: AnsiChar = #0;
|
|
|
|
function SQLVarToSQlite3Context(const Res: TSQLVar; Context: TSQLite3FunctionContext): boolean;
|
|
var tmp: array[0..31] of AnsiChar;
|
|
begin
|
|
case Res.VType of
|
|
ftNull:
|
|
sqlite3.result_null(Context);
|
|
ftInt64:
|
|
sqlite3.result_int64(Context,Res.VInt64);
|
|
ftDouble:
|
|
sqlite3.result_double(Context,Res.VDouble);
|
|
ftCurrency:
|
|
sqlite3.result_double(Context,Res.VCurrency);
|
|
ftDate: begin
|
|
DateTimeToIso8601ExpandedPChar(Res.VDateTime,tmp,'T',svoDateWithMS in Res.Options);
|
|
sqlite3.result_text(Context,tmp,-1,SQLITE_TRANSIENT_VIRTUALTABLE);
|
|
end;
|
|
// WARNING! use pointer(integer(-1)) instead of SQLITE_TRANSIENT=pointer(-1)
|
|
// due to a bug in SQLite3 current implementation of virtual tables in Win64
|
|
ftUTF8:
|
|
if Res.VText=nil then
|
|
sqlite3.result_text(Context,@NULCHAR,0,SQLITE_STATIC) else
|
|
sqlite3.result_text(Context,Res.VText,-1,SQLITE_TRANSIENT_VIRTUALTABLE);
|
|
ftBlob:
|
|
sqlite3.result_blob(Context,Res.VBlob,Res.VBlobLen,SQLITE_TRANSIENT_VIRTUALTABLE);
|
|
else begin
|
|
sqlite3.result_null(Context);
|
|
{$ifdef WITHLOG}
|
|
SynSQLite3Log.DebuggerNotify(sllWarning,'SQLVarToSQlite3Context(%)',[ord(Res.VType)]);
|
|
{$endif}
|
|
result := false; // not handled type (will set null value)
|
|
exit;
|
|
end;
|
|
end;
|
|
result := true;
|
|
end;
|
|
|
|
procedure SQlite3ValueToSQLVar(Value: TSQLite3Value; var Res: TSQLVar);
|
|
var ValueType: Integer;
|
|
begin
|
|
Res.Options := [];
|
|
ValueType := sqlite3.value_type(Value);
|
|
case ValueType of
|
|
SQLITE_NULL:
|
|
Res.VType := ftNull;
|
|
SQLITE_INTEGER: begin
|
|
Res.VType := ftInt64;
|
|
Res.VInt64 := sqlite3.value_int64(Value);
|
|
end;
|
|
SQLITE_FLOAT: begin
|
|
Res.VType := ftDouble;
|
|
Res.VDouble := sqlite3.value_double(Value);
|
|
end;
|
|
SQLITE_TEXT: begin
|
|
Res.VType := ftUTF8;
|
|
Res.VText := sqlite3.value_text(Value);
|
|
end;
|
|
SQLITE_BLOB: begin
|
|
Res.VType := ftBlob;
|
|
Res.VBlobLen := sqlite3.value_bytes(Value);
|
|
Res.VBlob := sqlite3.value_blob(Value);
|
|
end;
|
|
else begin
|
|
{$ifdef WITHLOG}
|
|
SynSQLite3Log.DebuggerNotify(sllWarning,'SQlite3ValueToSQLVar(%)',[ValueType]);
|
|
{$endif}
|
|
Res.VType := ftUnknown;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure ErrorWrongNumberOfArgs(Context: TSQLite3FunctionContext);
|
|
begin
|
|
sqlite3.result_error(Context, 'wrong number of arguments');
|
|
end;
|
|
|
|
function CheckNumberOfArgs(Context: TSQLite3FunctionContext; expected,sent: integer): boolean;
|
|
var msg: ShortString;
|
|
begin
|
|
if sent<>expected then begin
|
|
FormatShort('wrong number of arguments: expected %, got %',[expected,sent],msg);
|
|
sqlite3.result_error(Context,@msg[1],ord(msg[0]));
|
|
result := false;
|
|
end else
|
|
result := true;
|
|
end;
|
|
|
|
procedure ExceptionToSqlite3Err(E: Exception; var pzErr: PUTF8Char);
|
|
var U: RawUTF8;
|
|
begin
|
|
U := StringToUTF8(E.Message);
|
|
pzErr := sqlite3.malloc(length(U));
|
|
MoveFast(pointer(U)^,pzErr^,length(U));
|
|
end;
|
|
|
|
procedure InternalSoundex(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
begin
|
|
if CheckNumberOfArgs(Context,1,argc) then
|
|
sqlite3.result_int64(Context, SoundExUTF8(sqlite3.value_text(argv[0])));
|
|
end;
|
|
|
|
procedure InternalSoundexFr(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
begin
|
|
if CheckNumberOfArgs(Context,1,argc) then
|
|
sqlite3.result_int64(Context, SoundExUTF8(sqlite3.value_text(argv[0]),nil,sndxFrench));
|
|
end;
|
|
|
|
procedure InternalSoundexEs(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
begin
|
|
if CheckNumberOfArgs(Context,1,argc) then
|
|
sqlite3.result_int64(Context, SoundExUTF8(sqlite3.value_text(argv[0]),nil,sndxSpanish));
|
|
end;
|
|
|
|
procedure InternalMod(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
var A1, A2: Int64;
|
|
begin // implements the MOD() function, just like Oracle and others
|
|
if argc<>2 then begin
|
|
ErrorWrongNumberOfArgs(Context);
|
|
exit; // two parameters expected
|
|
end;
|
|
A1 := sqlite3.value_int64(argv[0]);
|
|
A2 := sqlite3.value_int64(argv[1]);
|
|
if A2=0 then // avoid computation exception, returns NULL
|
|
sqlite3.result_null(Context) else
|
|
sqlite3.result_int64(Context,A1 mod A2);
|
|
end;
|
|
|
|
procedure InternalTimeLog(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
var TimeLog: TTimeLogBits;
|
|
begin
|
|
if argc<>1 then begin
|
|
ErrorWrongNumberOfArgs(Context);
|
|
exit;
|
|
end;
|
|
TimeLog.Value := sqlite3.value_int64(argv[0]);
|
|
RawUTF8ToSQlite3Context(TimeLog.Text(True,'T'),Context,false);
|
|
end;
|
|
|
|
procedure InternalTimeLogUnix(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
var TimeLog: TTimeLogBits;
|
|
begin
|
|
if argc<>1 then begin
|
|
ErrorWrongNumberOfArgs(Context);
|
|
exit;
|
|
end;
|
|
TimeLog.Value := sqlite3.value_int64(argv[0]);
|
|
sqlite3.result_int64(Context,TimeLog.ToUnixTime);
|
|
end;
|
|
|
|
procedure InternalRank(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
// supplies the same "RANK" internal function as proposed in
|
|
// http://www.sqlite.org/fts3.html#appendix_a
|
|
var MI: PFTSMatchInfo;
|
|
p,c: integer;
|
|
score: Double;
|
|
begin
|
|
if argc>=1 then begin
|
|
MI := sqlite3.value_blob(argv[0]);
|
|
// rank(nil) for example select rank(matchinfo(tabName)) without corresponding MATCH clause
|
|
if MI=nil then begin
|
|
sqlite3.result_double(Context,0);
|
|
exit;
|
|
end;
|
|
if argc=MI^.nCol+1 then begin
|
|
score := 0;
|
|
for p := 1 to MI^.nPhrase do
|
|
for c := 1 to MI^.nCol do
|
|
with MI^.hits[c] do
|
|
if this_row>0 then
|
|
score := score+(this_row/all_rows)*sqlite3.value_double(argv[c]);
|
|
sqlite3.result_double(Context,score);
|
|
exit; // success: don't call sqlite3.result_error()
|
|
end;
|
|
end;
|
|
ErrorWrongNumberOfArgs(Context);
|
|
end;
|
|
|
|
procedure sqlite3InternalFree(p: pointer); cdecl;
|
|
begin
|
|
Freemem(p);
|
|
end;
|
|
|
|
procedure sqlite3InternalFreeObject(p: pointer); cdecl;
|
|
begin
|
|
TObject(p).Free;
|
|
end;
|
|
|
|
procedure sqlite3InternalFreeRawByteString(p: pointer); cdecl;
|
|
begin
|
|
RawByteString(p) := '';
|
|
end;
|
|
|
|
|
|
// supplies a CONCAT() function to process fast string concatenation
|
|
type
|
|
PConcatRec = ^TConcatRec;
|
|
TConcatRec = record
|
|
result: PUTF8Char;
|
|
resultlen: PtrInt;
|
|
end;
|
|
|
|
procedure InternalConcatStep(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
var sep, txt: PUTF8Char;
|
|
seplen, txtlen: PtrInt;
|
|
begin
|
|
if argc=2 then
|
|
with PConcatRec(sqlite3.aggregate_context(Context,sizeof(TConcatRec)))^ do begin
|
|
// +1 below for adding a final #0
|
|
txt := sqlite3.value_text(argv[0]);
|
|
txtlen := SynCommons.StrLen(txt);
|
|
if result=nil then
|
|
GetMem(result,txtlen+1)
|
|
else begin
|
|
sep := sqlite3.value_text(argv[1]);
|
|
seplen := SynCommons.StrLen(sep);
|
|
ReallocMem(result,resultlen+txtlen+seplen+1);
|
|
MoveFast(sep^,result[resultlen],seplen);
|
|
inc(resultlen,seplen);
|
|
end;
|
|
MoveFast(txt^,result[resultlen],txtlen+1);
|
|
inc(resultlen,txtlen);
|
|
end else
|
|
ErrorWrongNumberOfArgs(Context);
|
|
end;
|
|
|
|
procedure InternalConcatFinal(Context: TSQLite3FunctionContext); cdecl;
|
|
begin
|
|
with PConcatRec(sqlite3.aggregate_context(Context,sizeof(TConcatRec)))^ do
|
|
// sqlite3InternalFree will call Freemem(PConcatRec()^.result)
|
|
sqlite3.result_text(Context,result,resultlen+1,sqlite3InternalFree);
|
|
end;
|
|
|
|
procedure InternalIntegerDynArray(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
var Blob: pointer;
|
|
PI: PIntegerArray;
|
|
Count: integer;
|
|
begin // SQL function: IntegerDynArrayContains(BlobField,10) returning a boolean
|
|
if not CheckNumberOfArgs(Context,2,argc) then
|
|
exit;
|
|
Blob := sqlite3.value_blob(argv[0]);
|
|
if Blob<>nil then begin
|
|
PI := IntegerDynArrayLoadFrom(Blob,Count); // fast map into in-memory array
|
|
if not IntegerScanExists(pointer(PI),Count,sqlite3.value_int64(argv[1])) then
|
|
Blob := nil;
|
|
end;
|
|
sqlite3.result_int64(Context,Int64(Blob<>nil));
|
|
end;
|
|
|
|
procedure InternalSimpleInt64DynArray(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
var Blob: pointer;
|
|
Count, ElemSize: integer;
|
|
V: Int64;
|
|
begin // Byte/Word/Cardinal/Int64/CurrencyDynArrayContains(BlobField,I64)
|
|
// for currency, expect I64 value = aCurrency*10000 = PInt64(@aCurrency)^
|
|
if not CheckNumberOfArgs(Context,2,argc) then
|
|
exit;
|
|
Blob := sqlite3.value_blob(argv[0]);
|
|
if Blob<>nil then begin // search into direct in-memory mapping (no allocation)
|
|
Blob := SimpleDynArrayLoadFrom(Blob,sqlite3.user_data(Context),Count,ElemSize,{nohash32=}true);
|
|
if Blob<>nil then begin
|
|
V := sqlite3.value_int64(argv[1]);
|
|
sqlite3.result_int64(Context,Int64(true)); // exit will report value found
|
|
if AnyScanExists(Blob,@V,Count,ElemSize) then
|
|
exit;
|
|
end;
|
|
end;
|
|
sqlite3.result_int64(Context,Int64(false)); // not found
|
|
end;
|
|
|
|
procedure InternalRawUTF8DynArray(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
var Blob: pointer;
|
|
Value: PUTF8Char;
|
|
begin // SQL function: RawUTF8DynArrayContainsCase/NoCase(BlobField,'Text') returning a boolean
|
|
if not CheckNumberOfArgs(Context,2,argc) then
|
|
exit;
|
|
Blob := sqlite3.value_blob(argv[0]);
|
|
if Blob<>nil then begin
|
|
Value := sqlite3.value_text(argv[1]);
|
|
if RawUTF8DynArrayLoadFromContains(Blob,Value,SynCommons.StrLen(Value),
|
|
sqlite3.user_data(Context)=nil)<0 then
|
|
Blob := nil;
|
|
end;
|
|
sqlite3.result_int64(Context,Int64(Blob<>nil));
|
|
end;
|
|
|
|
procedure RawUTF8ToSQlite3Context(const Text: RawUTF8; Context: TSQLite3FunctionContext;
|
|
VoidTextAsNull: boolean);
|
|
var tmp: pointer;
|
|
begin
|
|
if Text='' then
|
|
if VoidTextAsNull then
|
|
sqlite3.result_null(Context) else
|
|
sqlite3.result_text(Context,@NULCHAR,0,SQLITE_STATIC) else begin
|
|
tmp := nil;
|
|
RawUTF8(tmp) := Text; // fast COW assignment
|
|
sqlite3.result_text(Context,tmp,length(Text)+1,sqlite3InternalFreeRawByteString);
|
|
end;
|
|
end;
|
|
|
|
{$ifndef NOVARIANTS}
|
|
|
|
procedure VariantToSQlite3Context(const Value: Variant; Context: TSQLite3FunctionContext);
|
|
var res: TSQLVar;
|
|
tmp: RawByteString;
|
|
begin
|
|
VariantToSQLVar(Value,tmp,res);
|
|
SQLVarToSQlite3Context(res,Context);
|
|
end;
|
|
|
|
procedure JsonToSQlite3Context(json: PUTF8Char; Context: TSQLite3FunctionContext);
|
|
var tmp: Variant;
|
|
start: PUTF8Char;
|
|
begin
|
|
if json=nil then
|
|
sqlite3.result_null(Context) else begin
|
|
start := GotoNextNotSpace(json);
|
|
if start^ in ['[','{'] then begin
|
|
// JSON object or array is returned as plain TEXT
|
|
json := GotoNextJSONObjectOrArray(start);
|
|
if json=nil then
|
|
sqlite3.result_null(Context) else begin
|
|
json^ := #0; // truncate to the matching object or array
|
|
sqlite3.result_text(Context,start,json-start+1,SQLITE_TRANSIENT);
|
|
end;
|
|
end else
|
|
// JSON simple types (text, numbers) would be converted via a variant
|
|
if VariantLoadJSON(tmp,start,nil,nil)=nil then
|
|
sqlite3.result_null(Context) else
|
|
VariantToSQlite3Context(tmp,Context);
|
|
end;
|
|
end;
|
|
|
|
{$else}
|
|
|
|
procedure JsonToSQlite3Context(json: PUTF8Char; Context: TSQLite3FunctionContext);
|
|
var j: RawJSON;
|
|
begin // JsonGet() would return the raw JSON for Delphi 5
|
|
GetJSONItemAsRawJSON(json,j);
|
|
RawUTF8ToSQlite3Context(j,Context,true);
|
|
end;
|
|
|
|
{$endif}
|
|
|
|
procedure InternalJsonGet(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
function returnObject(w: PUTF8Char): boolean;
|
|
begin
|
|
if w<>nil then
|
|
repeat
|
|
case w^ of
|
|
#0: break;
|
|
',','*': begin
|
|
result := true;
|
|
exit;
|
|
end;
|
|
end;
|
|
inc(w);
|
|
until false;
|
|
result := false;
|
|
end;
|
|
var where,json: PUTF8Char;
|
|
begin // JsonGet(VariantField,'PropName') returns the value of a JSON object
|
|
// JsonGet(VariantField,'Obj1.Obj2.PropName') to search by path
|
|
// JsonGet(VariantField,0) returns the 1st item in the JSON array
|
|
// JsonGet(VariantField,'Prop1,Prop2') returns the values as a JSON object
|
|
// JsonGet(VariantField,'Prop*') returns the values as a JSON object
|
|
// JsonGet(VariantField,'Obj1.Obj2.Prop1,Obj1.Prop2') to search by path
|
|
// JsonGet(VariantField,'Obj1.Obj2.Prop*,Obj1.Prop2') to search by path
|
|
if not CheckNumberOfArgs(Context,2,argc) then
|
|
exit;
|
|
if sqlite3.value_type(argv[0])<>SQLITE_TEXT then
|
|
sqlite3.result_null(Context) else
|
|
case sqlite3.value_type(argv[1]) of // fast SAX search (no memory allocation)
|
|
SQLITE_TEXT: begin
|
|
json := sqlite3.value_text(argv[0]);
|
|
where := sqlite3.value_text(argv[1]);
|
|
if returnObject(where) then
|
|
RawUTF8ToSQlite3Context(JsonObjectsByPath(json,where),Context,true) else
|
|
JsonToSQlite3Context(JsonObjectByPath(json,where),Context);
|
|
end;
|
|
SQLITE_INTEGER: begin
|
|
json := JSONArrayItem(sqlite3.value_text(argv[0]),sqlite3.value_int64(argv[1]));
|
|
JsonToSQlite3Context(json,Context);
|
|
end;
|
|
else
|
|
sqlite3.result_null(Context);
|
|
end;
|
|
end;
|
|
|
|
procedure InternalJsonHas(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
begin // JsonHas(VariantField,'PropName') returns TRUE if matches a JSON object property
|
|
// JsonHas(VariantField,'Obj1.Obj2.PropName') to search by path
|
|
// JsonHas(VariantField,0) returns TRUE if the JSON array has at least one item
|
|
if not CheckNumberOfArgs(Context,2,argc) then
|
|
exit;
|
|
if sqlite3.value_type(argv[0])<>SQLITE_TEXT then
|
|
sqlite3.result_int64(Context,Int64(false)) else
|
|
case sqlite3.value_type(argv[1]) of // fast SAX search (no memory allocation)
|
|
SQLITE_TEXT:
|
|
sqlite3.result_int64(Context,ord(JsonObjectByPath(
|
|
sqlite3.value_text(argv[0]),sqlite3.value_text(argv[1]))<>nil));
|
|
SQLITE_INTEGER:
|
|
sqlite3.result_int64(Context,ord(JSONArrayItem(
|
|
sqlite3.value_text(argv[0]),sqlite3.value_int64(argv[1]))<>nil));
|
|
else
|
|
sqlite3.result_int64(Context,Int64(false));
|
|
end;
|
|
end;
|
|
|
|
{$ifndef NOVARIANTS}
|
|
procedure InternalJsonSet(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
var doc: TDocVariantData;
|
|
json: PUTF8Char;
|
|
tmp: RawUTF8;
|
|
v: PVariant;
|
|
begin // JsonSet(VariantField,'PropName','abc') to set a value
|
|
// JsonSet(VariantField,'Obj1.Obj2.PropName','def') to set by path
|
|
if not CheckNumberOfArgs(Context,3,argc) then
|
|
exit;
|
|
if sqlite3.value_type(argv[0])<>SQLITE_TEXT then
|
|
sqlite3.result_null(Context) else begin
|
|
json := sqlite3.value_text(argv[0]);
|
|
FastSetString(tmp,json,SynCommons.StrLen(json));
|
|
doc.InitJSONInPlace(pointer(tmp),JSON_OPTIONS_FAST);
|
|
v := doc.GetPVariantByPath(sqlite3.value_text(argv[1]));
|
|
if v<>nil then begin
|
|
json := sqlite3.value_text(argv[2]);
|
|
FastSetString(tmp,json,SynCommons.StrLen(json));
|
|
VariantLoadJSON(v^,pointer(tmp),nil,@JSON_OPTIONS[true]);
|
|
RawUTF8ToSQlite3Context(doc.ToJSON,Context,false);
|
|
end else begin
|
|
FastSetString(tmp,json,SynCommons.StrLen(json));
|
|
RawUTF8ToSQlite3Context(tmp,Context,false);
|
|
end;
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
constructor TSQLDataBase.Create(const aFileName: TFileName; const aPassword: RawUTF8;
|
|
aOpenV2Flags, aDefaultCacheSize,aDefaultPageSize: integer);
|
|
var result: integer;
|
|
begin
|
|
inherited Create; // initialize fSafe
|
|
if sqlite3=nil then
|
|
raise ESQLite3Exception.CreateUTF8('%.Create: No SQLite3 libray available'+
|
|
' - you shall either add SynSQLite3Static to your project uses clause, '+
|
|
'or run sqlite3 := TSQLite3LibraryDynamic.Create(..)',[self]);
|
|
{$ifdef WITHLOG}
|
|
fLog := SynSQLite3Log; // leave fLog=nil if no Logging wanted
|
|
fLogResultMaximumSize := 512;
|
|
{$endif}
|
|
if SysUtils.Trim(aFileName)='' then
|
|
raise ESQLite3Exception.CreateUTF8('%.Create('''')',[self]);
|
|
if aOpenV2Flags=0 then
|
|
fOpenV2Flags := SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE else
|
|
fOpenV2Flags := aOpenV2Flags;
|
|
fFileDefaultPageSize := aDefaultPageSize;
|
|
fFileDefaultCacheSize := aDefaultCacheSize;
|
|
if (fOpenV2Flags<>(SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE)) and
|
|
not Assigned(sqlite3.open_v2) then
|
|
raise ESQLite3Exception.CreateUTF8(
|
|
'Your % version of SQLite3 does not support custom OpenV2Flags=%',
|
|
[sqlite3.libversion,fOpenV2Flags]);
|
|
fFileName := aFileName;
|
|
if fFileName=SQLITE_MEMORY_DATABASE_NAME then
|
|
fIsMemory := true else
|
|
fFileNameWithoutPath := ExtractFileName(fFileName);
|
|
fPassword := aPassword;
|
|
fUseCacheSize := 16384*1024;
|
|
fSQLFunctions := TSynObjectList.Create;
|
|
result := DBOpen;
|
|
if result<>SQLITE_OK then
|
|
raise ESQLite3Exception.Create(fDB,result,'DBOpen');
|
|
end;
|
|
|
|
destructor TSQLDataBase.Destroy;
|
|
{$ifdef WITHLOG}
|
|
var FPCLog: ISynLog;
|
|
begin
|
|
FPCLog := fLog.Enter('Destroy %',[fFileNameWithoutPath],self);
|
|
{$else}
|
|
begin
|
|
{$endif}
|
|
if DB<>0 then
|
|
try
|
|
Rollback; // any unfinished transaction is rollbacked
|
|
finally
|
|
(*
|
|
{ Applications should finalize all prepared statements and close all BLOB handles
|
|
associated with the sqlite3 object prior to attempting to close the object }
|
|
repeat
|
|
S := sqlite3.next_stmt(DB,0); // 0: get first prepared statement for DB
|
|
if S=0 then
|
|
break;
|
|
// if code was correctly protected with try/finally, as in
|
|
// TSQLDataBase.Execute() and TSQLRequest.Execute(), we should never go here
|
|
// -> BUT it seems that the FTS3 leaves some statements open at closing
|
|
// assert(false,FileName); // debug purpose, but not FTS3 ready
|
|
until not (sqlite3.finalize(S) in [SQLITE_OK,SQLITE_ABORT]);
|
|
{ BUT the problem is that if you use FTS3, the statements will be released
|
|
twice (i.e. one time above and next time in sqlite3.close below),
|
|
so some GPF will occur :(
|
|
-> we don't release any statement in case of FTS3 usage, and rely on our
|
|
framework, which protects all SQL statements with try..finally clauses }
|
|
*)
|
|
DBClose;
|
|
end;
|
|
FillZero(fPassword);
|
|
fCache.Free;
|
|
fSQLFunctions.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TSQLDataBase.SQLShouldBeLogged(const aSQL: RawUTF8): boolean;
|
|
begin
|
|
result := false;
|
|
{$ifdef WITHLOG}
|
|
if (self=nil) or (fLog=nil) or not (sllSQL in fLog.Family.Level) then
|
|
exit;
|
|
if not IdemPChar(pointer(aSQL),'PRAGMA ') or (PosEx('=',aSQL)>0) then
|
|
result := true;
|
|
{$endif}
|
|
end;
|
|
|
|
procedure TSQLDataBase.ExecuteAll(const aSQL: RawUTF8);
|
|
var R: TSQLRequest;
|
|
{$ifdef WITHLOG} log: ISynLog; {$endif}
|
|
begin
|
|
if self=nil then
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
{$ifdef WITHLOG}
|
|
if SQLShouldBeLogged(aSQL) then begin
|
|
log := fLog.Enter(self{$ifndef DELPHI5OROLDER},'ExecuteAll'{$endif});
|
|
if log<>nil then
|
|
log.Log(sllSQL,aSQL,self,4096);
|
|
end;
|
|
{$endif WITHLOG}
|
|
LockAndFlushCache; // don't trust aSQL -> assume modify -> inc(InternalState^)
|
|
try
|
|
R.ExecuteAll(DB,aSQl);
|
|
finally
|
|
UnLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDataBase.Execute(const aSQL: RawUTF8);
|
|
var R: TSQLRequest;
|
|
Timer: TPrecisionTimer;
|
|
begin
|
|
if self=nil then
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
Timer.Start;
|
|
Lock(aSQL); // run one statement -> we can trust isSelect()
|
|
try
|
|
R.Execute(DB,aSQL);
|
|
finally
|
|
UnLock;
|
|
{$ifdef WITHLOG}
|
|
fLog.Add.Log(sllSQL,'% % %',[Timer.Stop,FileNameWithoutPath,aSQL],self);
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.Execute(const aSQL: RawUTF8; var ID: TInt64DynArray): integer;
|
|
var R: TSQLRequest;
|
|
{$ifdef WITHLOG} log: ISynLog; {$endif}
|
|
begin
|
|
if self=nil then begin
|
|
result := 0;
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
end;
|
|
{$ifdef WITHLOG}
|
|
if SQLShouldBeLogged(aSQL) then begin
|
|
log := fLog.Enter(self{$ifndef DELPHI5OROLDER},'Execute'{$endif});
|
|
if log<>nil then
|
|
log.Log(sllSQL,aSQL,self,2048);
|
|
end;
|
|
{$endif}
|
|
Lock(aSQL);
|
|
try
|
|
result := R.Execute(DB,aSQL,ID);
|
|
finally
|
|
UnLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDataBase.Execute(const aSQL: RawUTF8; out ID: Int64; NoLog: boolean);
|
|
var R: TSQLRequest;
|
|
Timer: TPrecisionTimer;
|
|
begin
|
|
if self=nil then
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
Timer.Start;
|
|
Lock(aSQL);
|
|
try
|
|
R.Execute(DB,aSQL,ID);
|
|
finally
|
|
UnLock;
|
|
{$ifdef WITHLOG}
|
|
if not NoLog then
|
|
fLog.Add.Log(sllSQL,'% % returned % for %',[Timer.Stop,FileNameWithoutPath,ID,aSQL],self);
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDataBase.Execute(const aSQL: RawUTF8; out ID: RawUTF8; NoLog: boolean);
|
|
var R: TSQLRequest;
|
|
Timer: TPrecisionTimer;
|
|
begin
|
|
if self=nil then
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
Timer.Start;
|
|
Lock(aSQL);
|
|
try
|
|
R.Execute(DB,aSQL,ID);
|
|
finally
|
|
UnLock;
|
|
{$ifdef WITHLOG}
|
|
if not NoLog then
|
|
fLog.Add.Log(sllSQL,'% % returned [%] for %',
|
|
[Timer.Stop,FileNameWithoutPath,ID,aSQL],self);
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.ExecuteNoException(const aSQL: RawUTF8): boolean;
|
|
var R: TSQLRequest;
|
|
Timer: TPrecisionTimer;
|
|
begin
|
|
result := false;
|
|
if (self=nil) or (DB=0) then
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
Timer.Start;
|
|
Lock(aSQL); // run one statement -> we can trust IsCacheable()
|
|
try
|
|
result := R.ExecuteNoException(DB,aSQL);
|
|
finally
|
|
UnLock;
|
|
{$ifdef WITHLOG}
|
|
fLog.Add.Log(sllSQL,'% % % = %',[Timer.Stop,FileNameWithoutPath,aSQL,BOOL_STR[result]],self);
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.ExecuteNoExceptionInt64(const aSQL: RawUTF8): Int64;
|
|
begin
|
|
if (self=nil) or (DB=0) then
|
|
result := 0 else
|
|
try
|
|
Execute(aSQL,result,true);
|
|
except
|
|
result := 0;
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.ExecuteNoExceptionUTF8(const aSQL: RawUTF8): RawUTF8;
|
|
begin
|
|
if (self=nil) or (DB=0) then
|
|
result := '' else
|
|
try
|
|
Execute(aSQL,result,true);
|
|
except
|
|
result := '';
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.ExecuteJSON(const aSQL: RawUTF8; Expand: boolean=false;
|
|
aResultCount: PPtrInt=nil): RawUTF8;
|
|
var R: TSQLRequest;
|
|
Count: PtrInt;
|
|
Timer: TPrecisionTimer;
|
|
begin
|
|
if self=nil then begin
|
|
result := '';
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
end;
|
|
Timer.Start;
|
|
result := LockJSON(aSQL,aResultCount); // lock and try getting the request from the cache
|
|
if result='' then // only Execute the DB request if not got from cache
|
|
try
|
|
result := R.ExecuteJSON(DB,aSQL,Expand,@Count);
|
|
if aResultCount<>nil then
|
|
aResultCount^ := Count;
|
|
finally
|
|
UnLockJSON(result,Count);
|
|
{$ifdef WITHLOG}
|
|
fLog.Add.Log(sllSQL,'% % returned % bytes %',
|
|
[Timer.Stop,FileNameWithoutPath,length(result),aSQL],self);
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.Execute(const aSQL: RawUTF8; var Values: TRawUTF8DynArray): integer;
|
|
var R: TSQLRequest;
|
|
Timer: TPrecisionTimer;
|
|
begin
|
|
result := 0;
|
|
if self=nil then
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
Timer.Start;
|
|
Lock(aSQL);
|
|
try
|
|
result := R.Execute(DB,aSQL,Values);
|
|
finally
|
|
UnLock;
|
|
{$ifdef WITHLOG}
|
|
fLog.Add.Log(sllSQL,'% % returned % rows %',
|
|
[Timer.Stop,FileNameWithoutPath,length(Values),aSQL],self);
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.LastInsertRowID: Int64;
|
|
begin
|
|
if (self=nil) or (DB=0) then
|
|
result := 0 else
|
|
try
|
|
Lock;
|
|
result := sqlite3.last_insert_rowid(DB);
|
|
finally
|
|
UnLock;
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.LastChangeCount: integer;
|
|
begin
|
|
if (self=nil) or (DB=0) then
|
|
result := 0 else
|
|
try
|
|
Lock;
|
|
result := sqlite3.changes(DB);
|
|
finally
|
|
UnLock;
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.TotalChangeCount: integer;
|
|
begin
|
|
if (self=nil) or (DB=0) or not Assigned(sqlite3.total_changes) then
|
|
result := 0 else
|
|
try
|
|
Lock;
|
|
result := sqlite3.total_changes(DB);
|
|
finally
|
|
UnLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDataBase.GetTableNames(var Names: TRawUTF8DynArray);
|
|
begin // SQL statement taken from official SQLite3 FAQ
|
|
SetLength(Names,Execute(SQL_GET_TABLE_NAMES,Names));
|
|
end;
|
|
|
|
function TSQLDataBase.HasTable(const Name: RawUTF8): boolean;
|
|
var names: TRawUTF8DynArray;
|
|
begin
|
|
GetTableNames(names);
|
|
result := FindPropName(names, Name)>=0;
|
|
end;
|
|
|
|
procedure TSQLDataBase.GetFieldNames(var Names: TRawUTF8DynArray; const TableName: RawUTF8);
|
|
var R: TSQLRequest;
|
|
n: integer;
|
|
begin
|
|
if (self=nil) or (fDB=0) then
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
Lock;
|
|
try
|
|
try
|
|
R.Prepare(fDB,'PRAGMA table_info('+TableName+');'); // ESQLite3Exception
|
|
n := 0;
|
|
while R.Step=SQLITE_ROW do // cid,name,type,notnull,dflt_value,pk
|
|
AddRawUTF8(Names,n,sqlite3.column_text(R.Request,1));
|
|
SetLength(Names,n);
|
|
finally
|
|
R.Close;
|
|
end;
|
|
finally
|
|
UnLock;
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.GetUseCache: boolean;
|
|
begin
|
|
result := (self<>nil) and (fCache<>nil);
|
|
end;
|
|
|
|
procedure TSQLDataBase.SetUseCache(const Value: boolean);
|
|
begin
|
|
if self<>nil then
|
|
if Value<>UseCache then
|
|
if Value then
|
|
fCache := TSynCache.Create(fUseCacheSize,true) else
|
|
FreeAndNil(fCache);
|
|
end;
|
|
|
|
function IsCacheable(const aSQL: RawUTF8): boolean;
|
|
begin
|
|
result := isSelect(pointer(aSQL)) and (PosEx(SQLDATABASE_NOCACHE,aSQL)=0);
|
|
end;
|
|
|
|
procedure TSQLDataBase.Lock(const aSQL: RawUTF8);
|
|
begin
|
|
if self=nil then
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
if (aSQL='') or IsCacheable(aSQL) then
|
|
fSafe.Lock else // on non-concurent calls, is very fast
|
|
LockAndFlushCache; // INSERT UPDATE DELETE statements need to flush cache
|
|
end;
|
|
|
|
procedure TSQLDataBase.LockAndFlushCache;
|
|
begin
|
|
if self=nil then
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
fSafe.Lock; // on non-concurent calls, this API is very fast
|
|
try
|
|
CacheFlush;
|
|
except
|
|
on Exception do begin // ensure critical section is left even on error
|
|
fSafe.UnLock;
|
|
raise;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDataBase.Lock;
|
|
begin
|
|
if self<>nil then
|
|
fSafe.Lock; // on non-concurent calls, this API is very fast
|
|
end;
|
|
|
|
procedure TSQLDataBase.UnLock;
|
|
begin
|
|
if self<>nil then
|
|
fSafe.UnLock; // on non-concurent calls, this API is very fast
|
|
end;
|
|
|
|
function TSQLDataBase.LockJSON(const aSQL: RawUTF8; aResultCount: PPtrInt): RawUTF8;
|
|
begin
|
|
if self=nil then begin
|
|
result := '';
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
end;
|
|
fSafe.Lock; // cache access is also protected by fSafe
|
|
try
|
|
if IsCacheable(aSQL) then begin
|
|
result := fCache.Find(aSQL,aResultCount); // try to get JSON result from cache
|
|
if result<>'' then begin
|
|
{$ifdef WITHLOG}
|
|
if fLog<>nil then begin
|
|
fLog.Add.Log(sllSQL,'from cache % %',[FileNameWithoutPath,aSQL],self);
|
|
fLog.Add.Log(sllResult,result,self,fLogResultMaximumSize);
|
|
end;
|
|
{$endif}
|
|
fSafe.UnLock; // found in cache -> leave critical section
|
|
end;
|
|
end else begin
|
|
// UPDATE, INSERT or any non SELECT statement
|
|
CacheFlush;
|
|
result := '';
|
|
end;
|
|
except
|
|
on Exception do begin // ensure critical section is left even on error
|
|
fSafe.UnLock;
|
|
raise;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDataBase.UnLockJSON(const aJSONResult: RawUTF8; aResultCount: PtrInt);
|
|
begin
|
|
if self<>nil then
|
|
try
|
|
{$ifdef WITHLOG}
|
|
if fLog<>nil then
|
|
fLog.Add.Log(sllResult,aJSONResult,self,fLogResultMaximumSize);
|
|
{$endif}
|
|
fCache.Add(aJSONResult,aResultCount); // no-op if Reset was made just before
|
|
finally
|
|
fSafe.UnLock; // on non-concurent calls, this API is very fast
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.Backup(const BackupFileName: TFileName): boolean;
|
|
{$ifdef WITHLOG}
|
|
var Log: ISynLog;
|
|
begin
|
|
Log := fLog.Enter('Backup % -> %',[fFileNameWithoutPath,BackupFileName],self);
|
|
{$else}
|
|
begin
|
|
{$endif}
|
|
if self=nil then begin
|
|
result := false;
|
|
exit; // avoid GPF in case of call from a static-only server
|
|
end;
|
|
Rollback; // any unfinished transaction is rollbacked
|
|
Execute('VACUUM;');
|
|
LockAndFlushCache;
|
|
try
|
|
try
|
|
{$ifdef WITHLOG}
|
|
if Log<>nil then
|
|
Log.Log(sllTrace,'close',self);
|
|
{$endif}
|
|
DBClose;
|
|
{$ifdef WITHLOG}
|
|
if Log<>nil then
|
|
Log.Log(sllTrace,'copy file',self);
|
|
{$endif}
|
|
result := CopyFile(fFileName,BackupFileName,false);
|
|
finally
|
|
{$ifdef WITHLOG}
|
|
if Log<>nil then
|
|
Log.Log(sllTrace,'reopen',self);
|
|
{$endif}
|
|
DBOpen;
|
|
end;
|
|
finally
|
|
UnLock;
|
|
end;
|
|
end;
|
|
|
|
function TSQLDataBase.GetBackupBackgroundInProcess: boolean;
|
|
begin
|
|
result := (self<>nil) and (fBackupBackgroundInProcess<>nil);
|
|
end;
|
|
|
|
function TSQLDataBase.GetSQLite3Library: TSQLite3Library;
|
|
begin // class function may be better, but fails on Delphi 2005
|
|
result := sqlite3;
|
|
end;
|
|
|
|
function TSQLDataBase.BackupBackground(const BackupFileName: TFileName;
|
|
StepPageNumber, StepSleepMS: Integer; OnProgress: TSQLDatabaseBackupEvent;
|
|
SynLzCompress: boolean; const aPassword: RawUTF8): boolean;
|
|
var Dest: TSQLDatabase;
|
|
Backup: TSQLite3Backup;
|
|
begin
|
|
result := false;
|
|
if (self=nil) or (BackupFileName='') or not Assigned(sqlite3.backup_init) or
|
|
(fBackupBackgroundInProcess<>nil) then
|
|
exit;
|
|
{$ifdef WITHLOG}
|
|
fLog.Add.Log(sllDB,'BackupBackground("%") started on %',
|
|
[BackupFileName,FileNameWithoutPath],self);
|
|
{$endif}
|
|
if FileExists(BackupFileName) then
|
|
if not DeleteFile(BackupFileName) then
|
|
exit;
|
|
// see https://bitbucket.org/egrange/sql3bak for proper parameters
|
|
Dest := TSQLDatabase.Create(BackupFileName,aPassword,0,1);
|
|
Dest.SetLockingMode(lmExclusive);
|
|
Dest.SetSynchronous(smOff);
|
|
Dest.ExecuteNoException('PRAGMA journal_mode=MEMORY');
|
|
Dest.ExecuteNoException('PRAGMA temp_store=MEMORY');
|
|
Backup := sqlite3.backup_init(Dest.DB,'main',DB,'main');
|
|
if Backup=0 then begin
|
|
Dest.Free;
|
|
exit;
|
|
end;
|
|
fBackupBackgroundInProcess := TSQLDatabaseBackupThread.Create(
|
|
Backup,self,Dest,StepPageNumber,StepSleepMS,SynLzCompress,OnProgress);
|
|
result := true;
|
|
end;
|
|
|
|
function TSQLDataBase.BackupBackgroundToDB(BackupDB: TSQLDatabase;
|
|
StepPageNumber, StepSleepMS: Integer; OnProgress: TSQLDatabaseBackupEvent): boolean;
|
|
var Backup: TSQLite3Backup;
|
|
begin
|
|
result := false;
|
|
if (self=nil) or (BackupDB=nil) or not Assigned(sqlite3.backup_init) or
|
|
(fBackupBackgroundInProcess<>nil) then
|
|
exit;
|
|
{$ifdef WITHLOG}
|
|
fLog.Add.Log(sllDB,'BackupBackgroundToDB("%") started on %',
|
|
[BackupDB.FileName,FileNameWithoutPath],self);
|
|
{$endif}
|
|
Backup := sqlite3.backup_init(BackupDB.DB,'main',DB,'main');
|
|
if Backup=0 then
|
|
exit;
|
|
fBackupBackgroundInProcess := TSQLDatabaseBackupThread.Create(
|
|
Backup,self,BackupDB,StepPageNumber,StepSleepMS,false,OnProgress,false);
|
|
result := true;
|
|
end;
|
|
|
|
procedure TSQLDataBase.BackupBackgroundWaitUntilFinished(TimeOutSeconds: Integer);
|
|
var i: integer;
|
|
begin
|
|
if fBackupBackgroundInProcess<>nil then
|
|
if TimeOutSeconds<0 then // TimeOutSeconds=-1 for infinite wait
|
|
while fBackupBackgroundInProcess<>nil do
|
|
SleepHiRes(10) else begin
|
|
for i := 1 to TimeOutSeconds*100 do begin // wait for process end
|
|
SleepHiRes(10);
|
|
if fBackupBackgroundInProcess=nil then
|
|
exit;
|
|
end;
|
|
Lock;
|
|
if fBackupBackgroundInProcess<>nil then
|
|
fBackupBackgroundInProcess.Terminate; // notify Execute loop abortion
|
|
UnLock;
|
|
for i := 1 to 500 do begin // wait 5 seconds for process to be aborted
|
|
SleepHiRes(10);
|
|
if fBackupBackgroundInProcess=nil then
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
class function TSQLDataBase.BackupSynLZ(const SourceDB, DestSynLZ: TFileName;
|
|
EraseSourceDB: boolean): boolean;
|
|
begin
|
|
result := FileSynLZ(SourceDB, DestSynLZ, SQLITE3_MAGIC);
|
|
if result and EraseSourceDB then
|
|
result := DeleteFile(SourceDB);
|
|
end;
|
|
|
|
class function TSQLDataBase.BackupUnSynLZ(const SourceSynLZ, DestDB: TFileName): boolean;
|
|
begin
|
|
result := FileUnSynLZ(SourceSynLZ, DestDB, SQLITE3_MAGIC);
|
|
end;
|
|
|
|
class function TSQLDataBase.IsBackupSynLZFile(const SynLZFile: TFileName): boolean;
|
|
begin
|
|
result := FileIsSynLZ(SynLZFile, SQLITE3_MAGIC);
|
|
end;
|
|
|
|
function TSQLDataBase.DBClose: integer;
|
|
{$ifdef WITHLOG}
|
|
var log: ISynLog;
|
|
{$endif}
|
|
begin
|
|
result := SQLITE_OK;
|
|
if (self=nil) or (fDB=0) then
|
|
exit;
|
|
{$ifdef WITHLOG}
|
|
log := fLog.Enter(self{$ifndef DELPHI5OROLDER},'DBClose'{$endif});
|
|
if log<>nil then
|
|
log.Log(sllDB,'closing [%] %',[FileName, KB(GetFileSize)],self);
|
|
{$endif}
|
|
if (sqlite3=nil) or not Assigned(sqlite3.close) then
|
|
raise ESQLite3Exception.CreateUTF8('%.DBClose called with no sqlite3 global',[self]);
|
|
if fBackupBackgroundInProcess<>nil then
|
|
BackupBackgroundWaitUntilFinished;
|
|
result := sqlite3.close(fDB);
|
|
fDB := 0;
|
|
fPageSize := 0;
|
|
end;
|
|
|
|
{$ifndef DELPHI5OROLDER}
|
|
function TSQLDataBase.EnableCustomTokenizer: integer;
|
|
{$ifdef WITHLOG}
|
|
var log: ISynLog;
|
|
{$endif}
|
|
begin
|
|
result := SQLITE_OK;
|
|
if (self=nil) or (fDB=0) then
|
|
exit;
|
|
{$ifdef WITHLOG}
|
|
log := fLog.Enter;
|
|
if log<>nil then
|
|
log.Log(sllDB,'Enable custom tokenizer for [%]',[FileName],self);
|
|
{$endif}
|
|
if (sqlite3=nil) or not Assigned(sqlite3.db_config) then
|
|
raise ESQLite3Exception.CreateUTF8('%.EnableCustomTokenizer called with no sqlite3 engine',[self]);
|
|
result := sqlite3.db_config(fDB, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1);
|
|
end;
|
|
{$endif}
|
|
|
|
function TSQLDataBase.DBOpen: integer;
|
|
var utf8: RawUTF8;
|
|
i: integer;
|
|
{$ifdef WITHLOG}
|
|
log: ISynLog;
|
|
begin
|
|
log := fLog.Enter('DBOpen %',[fFileNameWithoutPath],self);
|
|
{$else}
|
|
begin
|
|
{$endif WITHLOG}
|
|
if fDB<>0 then
|
|
raise ESQLite3Exception.Create('DBOpen called twice');
|
|
// open the database with the proper API call
|
|
if (sqlite3=nil) or not Assigned(sqlite3.open) then
|
|
raise ESQLite3Exception.Create('DBOpen called with no sqlite3 global');
|
|
utf8 := StringToUTF8(fFileName);
|
|
{$ifdef LINUX}
|
|
// for WAL to work under Linux - see http://www.sqlite.org/vfs.html
|
|
if assigned(sqlite3.open_v2) and (fPassword='') then begin
|
|
result := sqlite3.open_v2(pointer(utf8),fDB,fOpenV2Flags,'unix-excl');
|
|
if result<>SQLITE_OK then // may be 'unix-excl' is not supported by the library
|
|
result := sqlite3.open_v2(pointer(utf8),fDB,fOpenV2Flags,nil);
|
|
end else
|
|
{$else}
|
|
if fOpenV2Flags<>(SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE) then
|
|
result := sqlite3.open_v2(pointer(utf8),fDB,fOpenV2Flags,nil) else
|
|
{$endif LINUX}
|
|
result := sqlite3.open(pointer(utf8),fDB);
|
|
if result<>SQLITE_OK then begin
|
|
{$ifdef WITHLOG}
|
|
if log<>nil then
|
|
log.Log(sllError,'sqlite3_open ("%") failed with error % (%): %',
|
|
[utf8,sqlite3_resultToErrorText(result),result,sqlite3.errmsg(fDB)]);
|
|
{$endif WITHLOG}
|
|
sqlite3.close(fDB); // should always be closed, even on failure
|
|
fDB := 0;
|
|
exit;
|
|
end;
|
|
// initialize optional encryption (if supported by the compiled engine)
|
|
if Assigned(sqlite3.key) and (fPassword<>'') and (fFileName<>'') and
|
|
(fFileName<>SQLITE_MEMORY_DATABASE_NAME) then
|
|
sqlite3.key(fDB,pointer(fPassword),length(fPassword));
|
|
// tune up execution context (before accessing the database)
|
|
if not fIsMemory then begin
|
|
if (fOpenV2Flags and SQLITE_OPEN_CREATE<>0) and (fFileDefaultPageSize<>0) then
|
|
PageSize := fFileDefaultPageSize;
|
|
if fFileDefaultCacheSize<>0 then
|
|
CacheSize := fFileDefaultCacheSize; // 10000 by default (i.e. 40 MB)
|
|
end;
|
|
// always try to check for proper database content (and password)
|
|
if not ExecuteNoException('select count(*) from sqlite_master') then begin
|
|
result := SQLITE_NOTADB; // likely a password error
|
|
sqlite3.close(fDB); // should always be closed, even on failure
|
|
fDB := 0;
|
|
exit;
|
|
end;
|
|
// our custom fast UTF-8 WinAnsi case insensitive compare, using NormToUpper[]
|
|
sqlite3.create_collation(DB,'SYSTEMNOCASE',SQLITE_UTF8,nil,Utf8SQLCompNoCase);
|
|
// our custom fast ISO-8601 date time encoded
|
|
sqlite3.create_collation(DB,'ISO8601',SQLITE_UTF8,nil,Utf8SQLDateTime);
|
|
// two slow but always accurate compare, using the Win32 Unicode API
|
|
sqlite3.create_collation(DB,'WIN32CASE',SQLITE_UTF16,nil,Utf16SQLCompCase);
|
|
sqlite3.create_collation(DB,'WIN32NOCASE',SQLITE_UTF16,nil,Utf16SQLCompNoCase);
|
|
// note: standard SQLite3 NOCASE collation is used for AnsiString
|
|
// register the MOD() user function, similar to the standard % operator
|
|
sqlite3.create_function(DB,'MOD',2,SQLITE_ANY,nil,InternalMod,nil,nil);
|
|
// register TIMELOG(), returning a ISO-8601 date/time from TTimeLog value
|
|
sqlite3.create_function(DB,'TIMELOG',1,SQLITE_ANY,nil,InternalTimeLog,nil,nil);
|
|
// register TIMELOGUNIX(), returning Unix Epoch seconds from TTimeLog value
|
|
sqlite3.create_function(DB,'TIMELOGUNIX',1,SQLITE_ANY,nil,InternalTimeLogUnix,nil,nil);
|
|
// some user functions
|
|
sqlite3.create_function(DB,'SOUNDEX',1,SQLITE_UTF8,nil,InternalSoundex,nil,nil);
|
|
sqlite3.create_function(DB,'SOUNDEXFR',1,SQLITE_UTF8,nil,InternalSoundexFr,nil,nil);
|
|
sqlite3.create_function(DB,'SOUNDEXES',1,SQLITE_UTF8,nil,InternalSoundexEs,nil,nil);
|
|
// rank() function as proposed in http://www.sqlite.org/fts3.html#appendix_a
|
|
sqlite3.create_function(DB,'RANK',-1,SQLITE_ANY,nil,InternalRank,nil,nil);
|
|
// CONCAT() function to process fast string concatenation
|
|
sqlite3.create_function(DB,'CONCAT',2,SQLITE_UTF8,nil,nil,
|
|
InternalConcatStep,InternalConcatFinal);
|
|
// functions to handle some standard dynamic array BLOB content in SQL
|
|
// IntegerDynArrayContains(BlobField,10) returning a boolean
|
|
sqlite3.create_function(DB,'INTEGERDYNARRAYCONTAINS',2,SQLITE_ANY,
|
|
nil,InternalIntegerDynArray,nil,nil);
|
|
// Byte/Word/Cardinal/Int64/CurrencyDynArrayContains(BlobField,I64)
|
|
sqlite3.create_function(DB,'BYTEDYNARRAYCONTAINS',2,SQLITE_ANY,
|
|
TypeInfo(TByteDynArray),InternalSimpleInt64DynArray,nil,nil);
|
|
sqlite3.create_function(DB,'WORDDYNARRAYCONTAINS',2,SQLITE_ANY,
|
|
TypeInfo(TWordDynArray),InternalSimpleInt64DynArray,nil,nil);
|
|
sqlite3.create_function(DB,'CARDINALDYNARRAYCONTAINS',2,SQLITE_ANY,
|
|
TypeInfo(TCardinalDynArray),InternalSimpleInt64DynArray,nil,nil);
|
|
sqlite3.create_function(DB,'INT64DYNARRAYCONTAINS',2,SQLITE_ANY,
|
|
TypeInfo(TInt64DynArray),InternalSimpleInt64DynArray,nil,nil);
|
|
sqlite3.create_function(DB,'CURRENCYDYNARRAYCONTAINS',2,SQLITE_ANY,
|
|
TypeInfo(TInt64DynArray),InternalSimpleInt64DynArray,nil,nil);
|
|
// RawUTF8DynArrayContainsCase/NoCase(BlobField,'Text') returning a boolean
|
|
sqlite3.create_function(DB,'RAWUTF8DYNARRAYCONTAINSCASE',2,SQLITE_ANY,nil,
|
|
InternalRawUTF8DynArray,nil,nil);
|
|
sqlite3.create_function(DB,'RAWUTF8DYNARRAYCONTAINSNOCASE',2,SQLITE_ANY,
|
|
@UTF8ILComp,InternalRawUTF8DynArray,nil,nil);
|
|
// JSON related functions (e.g. for ORM storing variants as JSON UTF-8 text)
|
|
sqlite3.create_function(DB,'JSONGET',2,SQLITE_ANY,nil,InternalJsonGet,nil,nil);
|
|
sqlite3.create_function(DB,'JSONHAS',2,SQLITE_ANY,nil,InternalJsonHas,nil,nil);
|
|
{$ifndef NOVARIANTS}
|
|
sqlite3.create_function(DB,'JSONSET',3,SQLITE_ANY,nil,InternalJsonSet,nil,nil);
|
|
{$endif}
|
|
// reallocate all TSQLDataBaseSQLFunction for re-Open (TSQLRestServerDB.Backup)
|
|
for i := 0 to fSQLFunctions.Count-1 do
|
|
TSQLDataBaseSQLFunction(fSQLFunctions.List[i]).CreateFunction(DB);
|
|
{$ifdef WITHLOG}
|
|
i := CacheSize;
|
|
if i<0 then
|
|
i := (-i) shr 10 else
|
|
i := PageSize*CacheSize;
|
|
if log<>nil then
|
|
log.Log(sllDB,'"%" database file (%) opened with PageSize=% CacheSize=% (%)',
|
|
[FileName,KB(GetFileSize),PageSize,CacheSize,KB(i)],self);
|
|
{$endif}
|
|
end;
|
|
|
|
function TSQLDataBase.GetUserVersion: cardinal;
|
|
begin
|
|
result := ExecuteNoExceptionInt64('PRAGMA user_version');
|
|
end;
|
|
|
|
procedure TSQLDataBase.SetUserVersion(const Value: cardinal);
|
|
begin
|
|
ExecuteNoException('PRAGMA user_version='+Int32ToUTF8(Value));
|
|
end;
|
|
|
|
function TSQLDataBase.GetCacheSize: cardinal;
|
|
begin
|
|
result := ExecuteNoExceptionInt64('PRAGMA cache_size');
|
|
end;
|
|
|
|
procedure TSQLDataBase.SetCacheSize(const Value: cardinal);
|
|
begin
|
|
ExecuteNoException('PRAGMA cache_size='+UInt32ToUTF8(Value));
|
|
end;
|
|
|
|
function TSQLDataBase.GetPageSize: cardinal;
|
|
begin
|
|
if fPageSize=0 then // can be cached, since not change once opened
|
|
fPageSize := ExecuteNoExceptionInt64('PRAGMA page_size');
|
|
result := fPageSize;
|
|
end;
|
|
|
|
procedure TSQLDataBase.SetPageSize(const Value: cardinal);
|
|
begin
|
|
if ExecuteNoException('PRAGMA page_size='+UInt32ToUTF8(Value)) then
|
|
fPageSize := Value;
|
|
end;
|
|
|
|
function TSQLDataBase.GetPageCount: cardinal;
|
|
begin
|
|
result := ExecuteNoExceptionInt64('PRAGMA page_count');
|
|
end;
|
|
|
|
function TSQLDataBase.GetFileSize: Int64;
|
|
begin
|
|
result := GetPageCount;
|
|
result := result*GetPageSize;
|
|
end;
|
|
|
|
procedure TSQLDataBase.SetSynchronous(const Value: TSQLSynchronousMode);
|
|
begin
|
|
ExecuteNoException('PRAGMA synchronous='+UInt32ToUTF8(ord(Value)));
|
|
end;
|
|
|
|
procedure TSQLDataBase.SetMemoryMappedMB(const Value: cardinal);
|
|
begin
|
|
ExecuteNoException('PRAGMA mmap_size='+Int64ToUTF8(Value shl 20));
|
|
end;
|
|
|
|
function TSQLDataBase.GetMemoryMappedMB: cardinal;
|
|
begin
|
|
result := ExecuteNoExceptionInt64('PRAGMA mmap_size') shr 20;
|
|
end;
|
|
|
|
function TSQLDataBase.GetSynchronous: TSQLSynchronousMode;
|
|
begin
|
|
result := TSQLSynchronousMode(ExecuteNoExceptionInt64('PRAGMA synchronous'));
|
|
end;
|
|
|
|
procedure TSQLDataBase.SetLockingMode(const Value: TSQLLockingMode);
|
|
const CMD: array[TSQLLockingMode] of RawUTF8 = ('NORMAL;','EXCLUSIVE;');
|
|
begin
|
|
ExecuteNoException('PRAGMA locking_mode='+CMD[value]);
|
|
end;
|
|
|
|
function TSQLDataBase.GetLockingMode: TSQLLockingMode;
|
|
var tmp: RawUTF8;
|
|
begin
|
|
tmp := ExecuteNoExceptionUTF8('PRAGMA locking_mode');
|
|
if IdemPropNameU(tmp,'EXCLUSIVE') then
|
|
result := lmExclusive else
|
|
result := lmNormal;
|
|
end;
|
|
|
|
procedure TSQLDataBase.SetWALMode(Value: Boolean);
|
|
const CMD: array[boolean] of RawUTF8 = ('DELETE;','WAL;');
|
|
begin
|
|
ExecuteNoException('PRAGMA journal_mode='+CMD[value]);
|
|
end;
|
|
|
|
function TSQLDataBase.GetWALMode: boolean;
|
|
begin
|
|
result := IdemPropNameU(ExecuteNoExceptionUTF8('PRAGMA journal_mode'),'wal');
|
|
end;
|
|
|
|
procedure TSQLDataBase.SetBusyTimeout(const ms: Integer);
|
|
begin
|
|
if (self=nil) or (fDB=0) then
|
|
exit;
|
|
sqlite3.busy_timeout(DB,ms);
|
|
fBusyTimeout := ms;
|
|
end;
|
|
|
|
function TSQLDataBase.GetLimit(Category: TSQLLimitCategory): integer;
|
|
begin
|
|
if (self=nil) or (fDB=0) or not Assigned(sqlite3.limit) then
|
|
result := 0 else
|
|
result := sqlite3.limit(fDB,ord(Category),-1);
|
|
end;
|
|
|
|
procedure TSQLDataBase.SetLimit(Category: TSQLLimitCategory; Value: integer);
|
|
begin
|
|
if (self<>nil) and Assigned(sqlite3.limit) then
|
|
sqlite3.limit(fDB,ord(Category),Value);
|
|
end;
|
|
|
|
procedure TSQLDataBase.CacheFlush;
|
|
begin
|
|
if self=nil then
|
|
exit;
|
|
if InternalState<>nil then
|
|
inc(InternalState^);
|
|
if fCache.Reset then
|
|
{$ifdef WITHLOG}
|
|
if fLog<>nil then
|
|
fLog.Add.Log(sllCache,'% cache flushed',[FileNameWithoutPath],self);
|
|
{$endif}
|
|
end;
|
|
|
|
procedure TSQLDataBase.RegisterSQLFunction(aFunction: TSQLDataBaseSQLFunction);
|
|
var i: integer;
|
|
begin
|
|
if (self=nil) or (aFunction=nil) then
|
|
exit;
|
|
for i := 0 to fSQLFunctions.Count-1 do
|
|
with TSQLDataBaseSQLFunction(fSQLFunctions.List[i]) do
|
|
if (FunctionParametersCount=aFunction.FunctionParametersCount) and
|
|
IdemPropNameU(FunctionName,aFunction.FunctionName) then begin
|
|
aFunction.Free;
|
|
exit; // already registered with the same name and parameters count
|
|
end;
|
|
{$ifdef WITHLOG}
|
|
if fLog<>nil then
|
|
fLog.Add.Log(sllDB,'% RegisterSQLFunction("%") %',
|
|
[FileNameWithoutPath,aFunction.FunctionName],self);
|
|
{$endif}
|
|
fSQLFunctions.Add(aFunction);
|
|
if DB<>0 then
|
|
// DB already opened -> register this custom function
|
|
aFunction.CreateFunction(DB);
|
|
end;
|
|
|
|
procedure TSQLDataBase.RegisterSQLFunction(aDynArrayTypeInfo: pointer;
|
|
aCompare: TDynArraySortCompare; const aFunctionName: RawUTF8);
|
|
begin
|
|
RegisterSQLFunction(
|
|
TSQLDataBaseSQLFunctionDynArray.Create(aDynArrayTypeInfo,aCompare,aFunctionName));
|
|
end;
|
|
|
|
procedure TSQLDataBase.RegisterSQLFunction(aFunction: TSQLFunctionFunc;
|
|
aFunctionParametersCount: Integer; const aFunctionName: RawUTF8);
|
|
begin
|
|
RegisterSQLFunction(TSQLDataBaseSQLFunction.Create(aFunction,aFunctionParametersCount,aFunctionName));
|
|
end;
|
|
|
|
|
|
{ TSQLRequest }
|
|
|
|
procedure TSQLRequest.Bind(Param: Integer; Value: Int64);
|
|
begin
|
|
sqlite3_check(RequestDB,sqlite3.bind_int64(Request,Param,Value),'bind_int64');
|
|
end;
|
|
|
|
procedure TSQLRequest.Bind(Param: Integer; Value: double);
|
|
begin
|
|
sqlite3_check(RequestDB,sqlite3.bind_double(Request,Param,Value),'bind_double');
|
|
end;
|
|
|
|
procedure TSQLRequest.Bind(Param: Integer; const Value: RawUTF8);
|
|
var tmp: pointer;
|
|
begin
|
|
// note that the official SQLite3 documentation is missleading:
|
|
// sqlite3.bind_text(Text_bytes) must EXCLUDE the null terminator, otherwise a
|
|
// #0 is appended to all column values -> so length(Value) is needed below
|
|
if pointer(Value)=nil then
|
|
// avoid to bind '' as null
|
|
sqlite3_check(RequestDB,sqlite3.bind_text(Request,Param,@NULCHAR,
|
|
0,SQLITE_STATIC)) else begin
|
|
// assign RawUTF8 value by reference, to avoid memory allocation
|
|
tmp := nil;
|
|
RawByteString(tmp) := Value;
|
|
// sqlite3InternalFreeRawByteString will decrease RefCount
|
|
sqlite3_check(RequestDB,sqlite3.bind_text(Request,Param,tmp,length(Value),
|
|
sqlite3InternalFreeRawByteString),'bind_text');
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLRequest.BindS(Param: Integer; const Value: string);
|
|
var P: PUTF8Char;
|
|
len: integer;
|
|
begin
|
|
if pointer(Value)=nil then begin // avoid to bind '' as null
|
|
sqlite3_check(RequestDB,sqlite3.bind_text(Request,Param,@NULCHAR,0,SQLITE_STATIC));
|
|
exit;
|
|
end;
|
|
len := length(Value);
|
|
GetMem(P,len*3+1);
|
|
{$ifdef UNICODE}
|
|
len := RawUnicodeToUtf8(P,len*3,pointer(Value),len,[]);
|
|
{$else}
|
|
len := CurrentAnsiConvert.AnsiBufferToUTF8(P,pointer(Value),len)-P;
|
|
{$endif}
|
|
sqlite3_check(RequestDB,sqlite3.bind_text(Request,Param,P,len,@sqlite3InternalFree),'bind_text');
|
|
end;
|
|
|
|
procedure TSQLRequest.Bind(Param: Integer; Data: pointer; Size: integer);
|
|
begin
|
|
sqlite3_check(RequestDB,sqlite3.bind_blob(Request,Param,Data,Size,
|
|
SQLITE_TRANSIENT),'bind_blob'); // make private copy of the data
|
|
end;
|
|
|
|
procedure TSQLRequest.BindBlob(Param: Integer; const Data: RawByteString);
|
|
var tmp: pointer;
|
|
begin
|
|
// assign RawByteString value by reference, to avoid memory allocation
|
|
tmp := nil;
|
|
RawByteString(tmp) := Data;
|
|
// sqlite3InternalFreeRawByteString will decrease RefCount
|
|
sqlite3_check(RequestDB,sqlite3.bind_blob(Request,Param,tmp,length(Data),
|
|
sqlite3InternalFreeRawByteString),'bind_blob');
|
|
end;
|
|
|
|
procedure TSQLRequest.Bind(Param: Integer; Data: TCustomMemoryStream);
|
|
begin
|
|
Bind(Param,Data.Memory,Data.Size);
|
|
end;
|
|
|
|
procedure TSQLRequest.BindNull(Param: Integer);
|
|
begin
|
|
sqlite3_check(RequestDB,sqlite3.bind_null(Request,Param));
|
|
end;
|
|
|
|
procedure TSQLRequest.BindReset;
|
|
begin
|
|
if Request<>0 then
|
|
sqlite3.clear_bindings(Request);
|
|
end;
|
|
|
|
procedure TSQLRequest.BindZero(Param, Size: integer);
|
|
begin
|
|
sqlite3_check(RequestDB,sqlite3.bind_zeroblob(Request,Param,Size));
|
|
end;
|
|
|
|
procedure TSQLRequest.Close;
|
|
begin
|
|
if Request=0 then
|
|
exit;
|
|
{$ifdef RESETFPUEXCEPTION}
|
|
with TSynFPUException.ForLibraryCode do
|
|
{$endif}
|
|
sqlite3.finalize(Request);
|
|
fRequest := 0;
|
|
fFieldCount := 0;
|
|
end;
|
|
|
|
procedure TSQLRequest.ExecuteAll;
|
|
begin
|
|
if RequestDB=0 then
|
|
raise ESQLite3Exception.Create(0,SQLITE_CANTOPEN,'ExecuteAll');
|
|
try
|
|
repeat
|
|
repeat
|
|
until Step<>SQLITE_ROW; // all steps of this statement
|
|
until PrepareNext=SQLITE_DONE; // all statements
|
|
finally
|
|
Close; // always release statement
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLRequest.Execute;
|
|
begin
|
|
if RequestDB=0 then
|
|
raise ESQLite3Exception.Create(0,SQLITE_CANTOPEN,'Execute');
|
|
try
|
|
repeat
|
|
until Step<>SQLITE_ROW; // Execute all steps of the first statement
|
|
finally
|
|
Close; // always release statement
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLRequest.ExecuteAll(aDB: TSQLite3DB; const aSQL: RawUTF8);
|
|
begin
|
|
try
|
|
Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
|
|
ExecuteAll;
|
|
finally
|
|
Close; // always release statement, even if done normally in EngineExecuteAll
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8);
|
|
begin
|
|
try
|
|
Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
|
|
Execute;
|
|
finally
|
|
Close; // always release statement, even if done normally in Execute
|
|
end;
|
|
end;
|
|
|
|
function TSQLRequest.ExecuteNoException(aDB: TSQLite3DB; const aSQL: RawUTF8): boolean;
|
|
begin // avoid sqlite3_check() calls for no ESQLite3Exception
|
|
result := false;
|
|
if (aDB<>0) and (aSQL<>'') then
|
|
try
|
|
if not(Prepare(aDB,aSQL,{noexcept=}true) in SQLITE_ERRORS) and (Request<>0) and
|
|
not(sqlite3.step(Request) in SQLITE_ERRORS) then
|
|
result := true;
|
|
finally
|
|
Close; // always release statement, even if done normally in Execute
|
|
end;
|
|
end;
|
|
|
|
function TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var ID: TInt64DynArray): integer;
|
|
var Res: integer;
|
|
begin
|
|
result := 0;
|
|
try
|
|
Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
|
|
if FieldCount>0 then
|
|
repeat
|
|
res := Step;
|
|
if res=SQLITE_ROW then
|
|
AddInt64(ID,result,sqlite3.column_int64(Request,0)); // first column value
|
|
until res=SQLITE_DONE;
|
|
finally
|
|
Close; // always release statement
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out ID: Int64);
|
|
begin
|
|
ID := 0;
|
|
try
|
|
Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
|
|
if FieldCount>0 then
|
|
if Step=SQLITE_ROW then
|
|
ID := sqlite3.column_int64(Request,0); // get first column value
|
|
finally
|
|
Close; // always release statement
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out Value: RawUTF8);
|
|
begin
|
|
Value := '';
|
|
try
|
|
Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
|
|
if FieldCount>0 then
|
|
if Step=SQLITE_ROW then
|
|
Value := sqlite3.column_text(Request,0); // get first column value
|
|
finally
|
|
Close; // always release statement
|
|
end;
|
|
end;
|
|
|
|
function TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var Values: TRawUTF8DynArray): integer;
|
|
var Res: integer;
|
|
begin
|
|
result := 0;
|
|
try
|
|
Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
|
|
if FieldCount>0 then
|
|
repeat
|
|
res := Step;
|
|
if res=SQLITE_ROW then
|
|
AddRawUTF8(Values,result,sqlite3.column_text(Request,0)); // first column value
|
|
until res=SQLITE_DONE;
|
|
finally
|
|
Close; // always release statement
|
|
end;
|
|
end;
|
|
|
|
function TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; JSON: TStream;
|
|
Expand: boolean): PtrInt;
|
|
// expand=true: [ {"col1":val11,"col2":"val12"},{"col1":val21,... ]
|
|
// expand=false: { "FieldCount":2,"Values":["col1","col2",val11,"val12",val21,..] }
|
|
var i: integer;
|
|
W: TJSONWriter;
|
|
tmp: TTextWriterStackBuffer;
|
|
begin
|
|
result := 0;
|
|
W := TJSONWriter.Create(JSON,Expand,false,nil,0,@tmp);
|
|
try
|
|
// prepare the SQL request
|
|
if aSQL<>'' then // if not already prepared, reset and bound by caller
|
|
Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
|
|
if FieldCount<=0 then begin
|
|
W.CancelAllVoid;
|
|
exit;
|
|
end;
|
|
// get col names and types
|
|
SetLength(W.ColNames,FieldCount);
|
|
for i := 0 to FieldCount-1 do
|
|
W.ColNames[i] := sqlite3.column_name(Request,i);
|
|
W.AddColumns; // write or init field names for appropriate JSON Expand
|
|
if Expand then
|
|
W.Add('[');
|
|
// write rows data
|
|
repeat
|
|
case Step of
|
|
SQLITE_ROW: begin
|
|
inc(result);
|
|
FieldsToJSON(W);
|
|
W.Add(',');
|
|
end;
|
|
SQLITE_DONE:
|
|
break;
|
|
end;
|
|
until false;
|
|
if (result=0) and W.Expand then begin
|
|
// we want the field names at least, even with no data: we allow RowCount=0
|
|
W.Expand := false; // {"FieldCount":2,"Values":["col1","col2"]}
|
|
W.CancelAll;
|
|
for i := 0 to FieldCount-1 do
|
|
W.ColNames[i] := sqlite3.column_name(Request,i);
|
|
W.AddColumns;
|
|
end;
|
|
W.EndJSONObject(0,result);
|
|
finally
|
|
try
|
|
if aSQL<>'' then
|
|
Close; // always release statement (if not prepared and closed by caller)
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLRequest.ExecuteDebug(aDB: TSQLite3DB; const aSQL: RawUTF8; var OutFile: Text);
|
|
var Res, i, n: integer;
|
|
begin
|
|
{$I-}
|
|
writeln;
|
|
try
|
|
Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
|
|
repeat
|
|
repeat
|
|
Res := Step;
|
|
if Res=SQLITE_ROW then begin
|
|
n := FieldCount-1;
|
|
for i := 0 to n do begin
|
|
write(OutFile,FieldA(i));
|
|
if i<n then
|
|
write(OutFile,'|');
|
|
end;
|
|
writeln(OutFile);
|
|
end;
|
|
until Res=SQLITE_DONE;
|
|
until PrepareNext=SQLITE_DONE;
|
|
finally
|
|
{$I+}ioresult;
|
|
Close; // always release statement
|
|
end;
|
|
end;
|
|
|
|
function TSQLRequest.ExecuteJSON(aDB: TSQLite3DB; const aSQL: RawUTF8;
|
|
Expand: boolean=false; aResultCount: PPtrInt=nil): RawUTF8;
|
|
var Stream: TRawByteStringStream;
|
|
RowCount: PtrInt;
|
|
begin
|
|
Stream := TRawByteStringStream.Create;
|
|
try
|
|
try
|
|
RowCount := Execute(aDB,aSQL,Stream,Expand); // create JSON data in Stream
|
|
if aResultCount<>nil then
|
|
aResultCount^ := RowCount;
|
|
result := Stream.DataString;
|
|
except
|
|
on ESQLite3Exception do
|
|
result := '';
|
|
end;
|
|
// Close has been called in Execute() above since aSQL<>''
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
function TSQLRequest.FieldA(Col: integer): WinAnsiString;
|
|
var P: PUTF8Char;
|
|
L,L2: integer;
|
|
begin
|
|
result := '';
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldA');
|
|
P := sqlite3.column_text(Request,Col);
|
|
L := SynCommons.StrLen(P); // faster than sqlite3.column_bytes(Request,Col)
|
|
if L>0 then begin
|
|
SetLength(result,L);
|
|
L2 := UTF8ToWinPChar(pointer(result),P,L);
|
|
if L2<>L then
|
|
SetLength(result,L2);
|
|
end;
|
|
end;
|
|
|
|
function TSQLRequest.FieldBlob(Col: integer): RawByteString;
|
|
var P: PAnsiChar;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldBlob');
|
|
P := sqlite3.column_blob(Request,Col);
|
|
SetString(result,P,sqlite3.column_bytes(Request,Col));
|
|
end;
|
|
|
|
function TSQLRequest.FieldBlobToStream(Col: integer): TStream;
|
|
begin
|
|
result := TRawByteStringStream.Create(FieldBlob(Col));
|
|
end;
|
|
|
|
function TSQLRequest.FieldDouble(Col: integer): double;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldDouble');
|
|
result := sqlite3.column_double(Request,Col);
|
|
end;
|
|
|
|
function TSQLRequest.FieldInt(Col: integer): Int64;
|
|
begin // internaly, SQLite always uses Int64 -> pure Integer function is useless
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldInt');
|
|
result := sqlite3.column_int64(Request,Col);
|
|
end;
|
|
|
|
function TSQLRequest.FieldName(Col: integer): RawUTF8;
|
|
var P: PUTF8Char;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldName');
|
|
P := sqlite3.column_name(Request,Col);
|
|
FastSetString(result,P,SynCommons.StrLen(P));
|
|
end;
|
|
|
|
function TSQLRequest.FieldIndex(const aColumnName: RawUTF8): integer;
|
|
begin
|
|
if Request=0 then
|
|
raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE,'FieldIndex');
|
|
for result := 0 to FieldCount-1 do
|
|
if StrIComp(pointer(aColumnName),sqlite3.column_name(Request,result))=0 then
|
|
exit;
|
|
result := -1; // not found
|
|
end;
|
|
|
|
function TSQLRequest.FieldNull(Col: Integer): Boolean;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldNull');
|
|
result := sqlite3.column_type(Request,Col)=SQLITE_NULL;
|
|
end;
|
|
|
|
function TSQLRequest.FieldType(Col: Integer): integer;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldType');
|
|
result := sqlite3.column_type(Request,Col);
|
|
end;
|
|
|
|
function TSQLRequest.FieldDeclaredType(Col: Integer): RawUTF8;
|
|
var P: PUTF8Char;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB,SQLITE_RANGE,'FieldDeclaredType');
|
|
P := pointer(sqlite3.column_decltype(Request,Col));
|
|
FastSetString(result,P,SynCommons.StrLen(P));
|
|
end;
|
|
|
|
function TSQLRequest.FieldDeclaredTypeS(Col: Integer): String;
|
|
var P: PUTF8Char;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB,SQLITE_RANGE,'FieldDeclaredTypeS');
|
|
P := pointer(sqlite3.column_decltype(Request,Col));
|
|
result := UTF8DecodeToString(P,SynCommons.StrLen(P));
|
|
end;
|
|
|
|
function TSQLRequest.FieldUTF8(Col: integer): RawUTF8;
|
|
var P: PUTF8Char;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldUTF8');
|
|
P := pointer(sqlite3.column_text(Request,Col));
|
|
FastSetString(result,P,SynCommons.StrLen(P));
|
|
end;
|
|
|
|
function TSQLRequest.FieldS(Col: integer): string;
|
|
{$ifdef UNICODE}
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldS');
|
|
result := sqlite3.column_text16(Request,Col);
|
|
end;
|
|
{$else}
|
|
var P: PUTF8Char;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldS');
|
|
P := pointer(sqlite3.column_text(Request,Col));
|
|
CurrentAnsiConvert.UTF8BufferToAnsi(P,SynCommons.StrLen(P),RawByteString(result));
|
|
end;
|
|
{$endif}
|
|
|
|
function TSQLRequest.FieldValue(Col: integer): TSQLite3Value;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldValue');
|
|
result := sqlite3.column_value(Request,Col);
|
|
end;
|
|
|
|
function TSQLRequest.FieldW(Col: integer): RawUnicode;
|
|
var P: PWideChar;
|
|
begin
|
|
if cardinal(Col)>=cardinal(FieldCount) then
|
|
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE,'FieldW');
|
|
P := sqlite3.column_text16(Request,Col);
|
|
SetString(result,PAnsiChar(pointer(P)),StrLenW(P)*2+1);
|
|
end;
|
|
|
|
function TSQLRequest.Prepare(DB: TSQLite3DB; const SQL: RawUTF8; NoExcept: boolean): integer;
|
|
begin
|
|
fDB := DB;
|
|
fRequest := 0;
|
|
if DB=0 then
|
|
raise ESQLite3Exception.Create(DB,SQLITE_CANTOPEN,SQL);
|
|
{$ifdef RESETFPUEXCEPTION} // safest to reset x87 exceptions
|
|
with TSynFPUException.ForLibraryCode do
|
|
{$endif}
|
|
begin
|
|
result := sqlite3.prepare_v2(RequestDB, pointer(SQL), length(SQL)+1,
|
|
fRequest, fNextSQL);
|
|
while (result=SQLITE_OK) and (Request=0) do begin // comment or white-space
|
|
if fNextSQL^ = #0 then // statement contains only comment
|
|
raise ESQLite3Exception.Create(DB,SQLITE_EMPTY,SQL);
|
|
result := sqlite3.prepare_v2(RequestDB, fNextSQL, -1, fRequest, fNextSQL);
|
|
end;
|
|
fFieldCount := sqlite3.column_count(fRequest);
|
|
if not NoExcept then
|
|
sqlite3_check(RequestDB,result,SQL);
|
|
end;
|
|
end;
|
|
|
|
function TSQLRequest.PrepareAnsi(DB: TSQLite3DB; const SQL: WinAnsiString): integer;
|
|
begin
|
|
result := Prepare(DB,WinAnsiToUtf8(SQL));
|
|
end;
|
|
|
|
function TSQLRequest.PrepareNext: integer;
|
|
begin
|
|
if (Request=0) or (fNextSQL^=#0) then
|
|
result := SQLITE_DONE else begin
|
|
Close; // free previous statement
|
|
result := sqlite3.prepare_v2(RequestDB, fNextSQL, -1, fRequest, fNextSQL);
|
|
while (result=SQLITE_OK) and (Request=0) and (fNextSQL^<>#0) do
|
|
// comment or white-space -> ignore
|
|
result := sqlite3.prepare_v2(RequestDB, fNextSQL, -1, fRequest, fNextSQL);
|
|
fFieldCount := sqlite3.column_count(fRequest);
|
|
sqlite3_check(RequestDB,result,'PrepareNext');
|
|
if Request=0 then
|
|
result := SQLITE_DONE; // nothing more to add
|
|
end;
|
|
end;
|
|
|
|
function TSQLRequest.Reset: integer;
|
|
begin
|
|
if Request=0 then
|
|
raise ESQLite3Exception.Create('TSQLRequest.Reset called with no previous Request');
|
|
{$ifdef RESETFPUEXCEPTION} // safest to reset x87 exceptions
|
|
with TSynFPUException.ForLibraryCode do
|
|
{$endif}
|
|
result := sqlite3.reset(Request); // no check here since it was PREVIOUS state
|
|
end;
|
|
|
|
function TSQLRequest.Step: integer;
|
|
{$ifdef RESETFPUEXCEPTION} // safest to reset x87 exceptions - inlined TSynFPUException
|
|
var cw87: word;
|
|
{$endif}
|
|
begin
|
|
if Request=0 then
|
|
raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE,'Step');
|
|
{$ifdef RESETFPUEXCEPTION}
|
|
cw87 := Get8087CW;
|
|
try
|
|
{$endif}
|
|
result := sqlite3_check(RequestDB,sqlite3.step(Request),'Step');
|
|
{$ifdef RESETFPUEXCEPTION}
|
|
finally
|
|
Set8087CW(cw87);
|
|
end;
|
|
{$endif}
|
|
end;
|
|
|
|
function TSQLRequest.GetReadOnly: Boolean;
|
|
begin
|
|
if Request=0 then
|
|
raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE,'IsReadOnly');
|
|
result := sqlite3.stmt_readonly(Request)<>0;
|
|
end;
|
|
|
|
procedure TSQLRequest.FieldsToJSON(WR: TJSONWriter; DoNotFetchBlobs: boolean);
|
|
var i: integer;
|
|
begin
|
|
if Request=0 then
|
|
raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE,'FieldsToJSON');
|
|
if WR.Expand then
|
|
WR.Add('{');
|
|
for i := 0 to FieldCount-1 do begin
|
|
if WR.Expand then
|
|
WR.AddString(WR.ColNames[i]); // '"'+ColNames[]+'":'
|
|
case sqlite3.column_type(Request,i) of // fast evaluation: type may vary
|
|
SQLITE_BLOB:
|
|
if DoNotFetchBlobs then
|
|
WR.AddShort('null') else
|
|
WR.WrBase64(sqlite3.column_blob(Request,i),
|
|
sqlite3.column_bytes(Request,i),true); // withMagic=true
|
|
SQLITE_NULL:
|
|
WR.AddShort('null'); // returned also for ""
|
|
SQLITE_INTEGER:
|
|
WR.Add(sqlite3.column_int64(Request,i));
|
|
SQLITE_FLOAT:
|
|
WR.AddDouble(sqlite3.column_double(Request,i));
|
|
SQLITE_TEXT: begin
|
|
WR.Add('"');
|
|
WR.AddJSONEscape(sqlite3.column_text(Request,i),0);
|
|
WR.Add('"');
|
|
end;
|
|
end; // case ColTypes[]
|
|
WR.Add(',');
|
|
end;
|
|
WR.CancelLastComma; // cancel last ','
|
|
if WR.Expand then
|
|
WR.Add('}');
|
|
end;
|
|
|
|
function TSQLRequest.GetParamCount: integer;
|
|
begin
|
|
if Request=0 then
|
|
result := 0 else
|
|
result := sqlite3.bind_parameter_count(Request);
|
|
end;
|
|
|
|
|
|
{ ESQLite3Exception }
|
|
|
|
constructor ESQLite3Exception.Create(aDB: TSQLite3DB; aErrorCode: integer;
|
|
const aSQL: RawUTF8);
|
|
var msg: RawUTF8;
|
|
begin
|
|
fErrorCode := aErrorCode;
|
|
fSQLite3ErrorCode := sqlite3_resultToErrorCode(aErrorCode);
|
|
FormatUTF8('Error % (%) [%] using %',
|
|
[ErrorCodeToText(SQLite3ErrorCode),aErrorCode,aSQL,sqlite3.VersionText],msg);
|
|
if aDB=0 then
|
|
msg := msg+' with aDB=nil' else begin
|
|
msg := FormatUTF8('% - %',[msg,sqlite3.errmsg(aDB)]);
|
|
if Assigned(sqlite3.extended_errcode) then
|
|
msg := FormatUTF8('%, extended_errcode=%',[msg,sqlite3.extended_errcode(aDB)]);
|
|
end;
|
|
DB := aDB;
|
|
inherited Create(UTF8ToString(msg));
|
|
end;
|
|
|
|
function sqlite3_check(DB: TSQLite3DB; aResult: integer; const SQL: RawUTF8): integer;
|
|
begin
|
|
if (DB=0) or (aResult in SQLITE_ERRORS) then // possible error codes
|
|
raise ESQLite3Exception.Create(DB,aResult,SQL);
|
|
result := aResult;
|
|
end;
|
|
|
|
function sqlite3_resultToErrorCode(aResult: integer): TSQLite3ErrorCode;
|
|
begin
|
|
case aResult of
|
|
SQLITE_OK..SQLITE_NOTADB:
|
|
result := TSQLite3ErrorCode(aResult+ord(secOK));
|
|
SQLITE_ROW..SQLITE_DONE:
|
|
result := TSQLite3ErrorCode(aResult+ord(secROW));
|
|
else
|
|
result := secUnknown;
|
|
end;
|
|
end;
|
|
|
|
function ErrorCodeToText(err: TSQLite3ErrorCode): RawUTF8;
|
|
begin
|
|
result := 'SQLITE_'+TrimLeftLowerCaseShort(GetEnumName(
|
|
TypeInfo(TSQLite3ErrorCode),ord(err)));
|
|
end;
|
|
|
|
function sqlite3_resultToErrorText(aResult: integer): RawUTF8;
|
|
begin
|
|
result := ErrorCodeToText(sqlite3_resultToErrorCode(aResult));
|
|
end;
|
|
|
|
|
|
{ TSQLBlobStream }
|
|
|
|
constructor TSQLBlobStream.Create(aDB: TSQLite3DB; const DBName, TableName,
|
|
ColumnName: RawUTF8; RowID: Int64; ReadWrite: boolean);
|
|
begin
|
|
fDB := aDB;
|
|
fWritable := ReadWrite;
|
|
sqlite3_check(aDB,sqlite3.blob_open(aDB,pointer(DBName),pointer(TableName),
|
|
pointer(ColumnName),RowID,integer(ReadWrite),fBlob),'blob_open');
|
|
fSize := sqlite3.blob_bytes(fBlob);
|
|
end;
|
|
|
|
destructor TSQLBlobStream.Destroy;
|
|
begin
|
|
sqlite3.blob_close(fBlob);
|
|
inherited;
|
|
end;
|
|
|
|
function TSQLBlobStream.Read(var Buffer; Count: Longint): Longint;
|
|
begin
|
|
result := fSize-fPosition; // bytes available left
|
|
if Count<result then // read only inside the Blob size
|
|
result := Count;
|
|
if result<>0 then begin
|
|
sqlite3_check(fDB,sqlite3.blob_read(fBlob,Buffer,result,fPosition));
|
|
inc(fPosition,result);
|
|
end;
|
|
end;
|
|
|
|
function TSQLBlobStream.Seek(Offset: Longint; Origin: Word): Longint;
|
|
begin
|
|
case Origin of
|
|
soFromBeginning: fPosition := Offset;
|
|
soFromCurrent: Inc(fPosition,Offset);
|
|
soFromEnd: fPosition := fSize+Offset;
|
|
end;
|
|
if fPosition>fSize then
|
|
fPosition := fSize;
|
|
Result := fPosition;
|
|
end;
|
|
|
|
procedure TSQLBlobStream.ChangeRow(RowID: Int64);
|
|
begin
|
|
if not Assigned(sqlite3.blob_reopen) then
|
|
raise ESQLite3Exception.Create('blob_reopen API not available');
|
|
sqlite3_check(fDB,sqlite3.blob_reopen(fBlob,RowID),'blob_reopen');
|
|
fPosition := 0;
|
|
fSize := sqlite3.blob_bytes(fBlob);
|
|
end;
|
|
|
|
function TSQLBlobStream.Write(const Buffer; Count: Longint): Longint;
|
|
begin
|
|
result := fSize-fPosition; // bytes available left
|
|
if Count<result then
|
|
result := Count; // write only inside the Blob size
|
|
if result<>0 then begin
|
|
sqlite3_check(fDB,sqlite3.blob_write(fBlob,Buffer,result,fPosition));
|
|
inc(fPosition,result);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TSQLDataBaseSQLFunction }
|
|
|
|
constructor TSQLDataBaseSQLFunction.Create(aFunction: TSQLFunctionFunc;
|
|
aFunctionParametersCount: Integer; const aFunctionName: RawUTF8);
|
|
begin
|
|
fInternalFunction := aFunction;
|
|
fFunctionParametersCount := aFunctionParametersCount;
|
|
if aFunctionName='' then
|
|
fSQLName := RawUTF8(copy(ClassName,2,maxInt)) else
|
|
fSQLName := aFunctionName;
|
|
end;
|
|
|
|
function TSQLDataBaseSQLFunction.CreateFunction(DB: TSQLite3DB): Integer;
|
|
begin
|
|
if self<>nil then
|
|
result := sqlite3.create_function(DB,pointer(fSQLName),
|
|
FunctionParametersCount,SQLITE_ANY,self,fInternalFunction,nil,nil) else
|
|
result := SQLITE_ERROR;
|
|
end;
|
|
|
|
|
|
{ TSQLDataBaseSQLFunctionDynArray }
|
|
|
|
procedure InternalSQLFunctionDynArrayBlob(Context: TSQLite3FunctionContext;
|
|
argc: integer; var argv: TSQLite3ValueArray); cdecl;
|
|
var P, item: PAnsiChar;
|
|
PLen, itemLen: PtrInt;
|
|
caller: TSQLDataBaseSQLFunctionDynArray;
|
|
begin
|
|
if argc<>2 then begin
|
|
ErrorWrongNumberOfArgs(Context);
|
|
exit; // two parameters expected
|
|
end;
|
|
P := sqlite3.value_blob(argv[0]);
|
|
PLen := sqlite3.value_bytes(argv[0]);
|
|
item := sqlite3.value_blob(argv[1]);
|
|
itemLen := sqlite3.value_bytes(argv[1]);
|
|
caller := sqlite3.user_data(Context);
|
|
if (P<>nil) and (PLen>0) and (item<>nil) and (itemLen>0) and (caller<>nil) then
|
|
with caller.fDummyDynArray do
|
|
try // temporary allocate all dynamic array content
|
|
try
|
|
if (LoadFrom(P,nil,{nohash=}true,P+PLen)=nil) or
|
|
(ElemLoadFind(item,item+itemLen)<0) then
|
|
P := nil; // not found
|
|
finally
|
|
Clear; // always release temporary array content
|
|
end;
|
|
except
|
|
on Exception do begin
|
|
sqlite3.result_error(Context,'Invalid BLOB content');
|
|
exit;
|
|
end;
|
|
end else
|
|
P := nil;
|
|
sqlite3.result_int64(Context,Int64(P<>nil));
|
|
end;
|
|
|
|
constructor TSQLDataBaseSQLFunctionDynArray.Create(aTypeInfo: pointer;
|
|
aCompare: TDynArraySortCompare; const aFunctionName: RawUTF8);
|
|
begin
|
|
fDummyDynArray.Init(aTypeInfo,fDummyDynArrayValue);
|
|
fDummyDynArray.Compare := aCompare;
|
|
inherited Create(InternalSQLFunctionDynArrayBlob,2,aFunctionName);
|
|
end;
|
|
|
|
|
|
{ TSQLStatementCached }
|
|
|
|
procedure TSQLStatementCached.Init(aDB: TSQLite3DB);
|
|
begin
|
|
Caches.InitSpecific(TypeInfo(TSQLStatementCacheDynArray),Cache,djRawUTF8,@Count);
|
|
DB := aDB;
|
|
end;
|
|
|
|
function TSQLStatementCached.Prepare(const GenericSQL: RawUTF8;
|
|
WasPrepared: PBoolean; ExecutionTimer: PPPrecisionTimer; ExecutionMonitor: PSynMonitor): PSQLRequest;
|
|
var added: boolean;
|
|
ndx: integer;
|
|
begin
|
|
ndx := Caches.FindHashedForAdding(GenericSQL,added);
|
|
with Cache[ndx] do begin
|
|
if added then begin
|
|
StatementSQL := GenericSQL;
|
|
Statement.Prepare(DB,GenericSQL);
|
|
Timer := TSynMonitor.Create;
|
|
if WasPrepared<>nil then
|
|
WasPrepared^ := true;
|
|
end else begin
|
|
if Timer=nil then // there was a Statement.Prepare exception on previous call
|
|
raise ESQLite3Exception.CreateUTF8('TSQLStatementCached.Prepare failed [%]', [GenericSQL]);
|
|
if Statement.Request<>0 then
|
|
Statement.Reset;
|
|
if WasPrepared<>nil then
|
|
WasPrepared^ := false;
|
|
end;
|
|
if ExecutionTimer<>nil then begin
|
|
Timer.ProcessStartTask;
|
|
ExecutionTimer^ := @Timer.InternalTimer;
|
|
if ExecutionMonitor<>nil then
|
|
ExecutionMonitor^ := Timer;
|
|
end;
|
|
result := @Statement;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLStatementCached.ReleaseAllDBStatements;
|
|
var i: integer;
|
|
begin
|
|
for i := 0 to Count-1 do begin
|
|
Cache[i].Statement.Close; // close prepared statement
|
|
Cache[i].Timer.Free;
|
|
end;
|
|
Caches.Clear;
|
|
Caches.ReHash; // need to refresh all hashs
|
|
end;
|
|
|
|
function StatementCacheTotalTimeCompare(const A,B): integer;
|
|
var i64: Int64;
|
|
begin
|
|
i64 := TSQLStatementCache(A).Timer.InternalTimer.TimeInMicroSec-
|
|
TSQLStatementCache(B).Timer.InternalTimer.TimeInMicroSec;
|
|
if i64<0 then
|
|
result := -1 else
|
|
if i64>0 then
|
|
result := 1 else
|
|
result := 0;
|
|
end;
|
|
|
|
procedure TSQLStatementCached.SortCacheByTotalTime(var aIndex: TIntegerDynArray);
|
|
begin
|
|
Caches.{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}CreateOrderedIndex(
|
|
aIndex,StatementCacheTotalTimeCompare);
|
|
end;
|
|
|
|
|
|
{ TSQLDatabaseBackupThread }
|
|
|
|
constructor TSQLDatabaseBackupThread.Create(Backup: TSQLite3Backup;
|
|
Source, Dest: TSQLDatabase; StepPageNumber, StepSleepMS: Integer;
|
|
SynLzCompress: boolean; OnProgress: TSQLDatabaseBackupEvent; OwnerDest: boolean);
|
|
begin
|
|
fTimer.Start;
|
|
fBackup := Backup;
|
|
fSourceDB := Source;
|
|
fDestDB := Dest;
|
|
fBackupDestFile := Dest.fFileName;
|
|
if StepPageNumber=0 then
|
|
fStepPageNumber := 1 else
|
|
fStepPageNumber := StepPageNumber;
|
|
if (cardinal(StepSleepMS)<=1000) and (StepPageNumber>0) then
|
|
fStepSleepMS := StepSleepMS;
|
|
fOnProgress := OnProgress;
|
|
fStepSynLzCompress := SynLzCompress;
|
|
fOwnerDest := OwnerDest;
|
|
FreeOnTerminate := true;
|
|
inherited Create(false);
|
|
end;
|
|
|
|
procedure TSQLDatabaseBackupThread.Execute;
|
|
{$ifdef WITHLOG}
|
|
var log: ISynLog;
|
|
{$endif}
|
|
procedure NotifyProgressAndContinue(aStep: TSQLDatabaseBackupEventStep);
|
|
begin
|
|
fStep := aStep;
|
|
{$ifdef WITHLOG}
|
|
if Assigned(log) then
|
|
log.Log(sllTrace,'%', [self]);
|
|
{$endif}
|
|
if Assigned(fOnProgress) then
|
|
if not fOnProgress(self) then
|
|
raise ESQLite3Exception.CreateUtf8('%.Execute aborted by OnProgress=false',[self]);
|
|
end;
|
|
var res: integer;
|
|
fn, fn2: TFileName;
|
|
begin
|
|
fn := fDestDB.FileName;
|
|
{$ifdef WITHLOG}
|
|
SetCurrentThreadName('% [%] [%]',[self,fSourceDB.FileName,fn]);
|
|
log := SynSQLite3Log.Enter(self{$ifndef DELPHI5OROLDER},'Execute'{$endif});
|
|
{$endif}
|
|
try
|
|
try
|
|
NotifyProgressAndContinue(backupStart);
|
|
repeat
|
|
fSourceDB.Lock; // naive multi-thread protection of main process
|
|
res := sqlite3.backup_step(fBackup,fStepPageNumber);
|
|
fSourceDB.UnLock;
|
|
fStepNumberToFinish := sqlite3.backup_remaining(fBackup);
|
|
fStepNumberTotal := sqlite3.backup_pagecount(fBackup);
|
|
case res of
|
|
SQLITE_OK:
|
|
NotifyProgressAndContinue(backupStepOk);
|
|
SQLITE_BUSY: begin
|
|
NotifyProgressAndContinue(backupStepBusy);
|
|
if fStepSleepMS=0 then
|
|
SleepHiRes(1);
|
|
end;
|
|
SQLITE_LOCKED:
|
|
NotifyProgressAndContinue(backupStepLocked);
|
|
SQLITE_DONE:
|
|
break;
|
|
else raise ESQLite3Exception.Create(fDestDB.DB,res,'Backup');
|
|
end;
|
|
if Terminated then
|
|
raise ESQLite3Exception.Create('Backup process forced to terminate');
|
|
SleepHiRes(fStepSleepMS);
|
|
until false;
|
|
if fDestDB<>nil then begin
|
|
sqlite3.backup_finish(fBackup);
|
|
// close destination backup database
|
|
if fOwnerDest then
|
|
FreeAndNil(fDestDB);
|
|
fDestDB := nil;
|
|
end;
|
|
if not IdemPChar(pointer(fn),SQLITE_MEMORY_DATABASE_NAME) then begin
|
|
if fStepSynLzCompress then begin
|
|
NotifyProgressAndContinue(backupStepSynLz);
|
|
fn2 := ChangeFileExt(fn, '.db.tmp');
|
|
DeleteFile(fn2);
|
|
if not RenameFile(fn,fn2) then
|
|
raise ESQLite3Exception.CreateUTF8('%.Execute: RenameFile(%,%) failed',
|
|
[self,fn,fn2]);
|
|
if not TSQLDatabase.BackupSynLZ(fn2,fn,true) then
|
|
raise ESQLite3Exception.CreateUTF8('%.Execute: BackupSynLZ(%,%) failed',
|
|
[self,fn,fn2]);
|
|
{$ifdef WITHLOG}
|
|
if Assigned(log) then
|
|
log.Log(sllTrace,'TSQLDatabase.BackupSynLZ into % %',
|
|
[KB(FileSize(fn)),fn],self);
|
|
{$endif}
|
|
end;
|
|
fSourceDB.fBackupBackgroundLastFileName := ExtractFileName(fn);
|
|
end;
|
|
NotifyProgressAndContinue(backupSuccess);
|
|
finally
|
|
if fDestDB<>nil then begin
|
|
sqlite3.backup_finish(fBackup);
|
|
if fOwnerDest then
|
|
fDestDB.Free; // close destination backup database if not already
|
|
end;
|
|
fSourceDB.fBackupBackgroundLastTime := fTimer.Stop;
|
|
fSourceDB.Lock;
|
|
fSourceDB.fBackupBackgroundInProcess := nil;
|
|
fSourceDB.Unlock;
|
|
end;
|
|
except
|
|
on E: Exception do begin
|
|
fError := E;
|
|
fStep := backupFailure;
|
|
if Assigned(fOnProgress) then
|
|
fOnProgress(self);
|
|
end;
|
|
end;
|
|
{$ifdef WITHLOG}
|
|
log := nil;
|
|
SynSQLite3Log.Add.NotifyThreadEnded;
|
|
{$endif}
|
|
end;
|
|
|
|
|
|
{ TSQLite3Library }
|
|
|
|
constructor TSQLite3Library.Create;
|
|
var V: PUTF8Char;
|
|
begin
|
|
if Assigned(libversion) then begin
|
|
V := libversion;
|
|
fVersionText := RawUTF8(V);
|
|
fVersionNumber := GetNextItemCardinal(V,'.')*1000000000+
|
|
GetNextItemCardinal(V,'.')*1000000+GetNextItemCardinal(V,'.')*1000+
|
|
GetNextItemCardinal(V,'.'); // convert into e.g. 3008003001
|
|
end;
|
|
end;
|
|
|
|
{$ifndef DELPHI5OROLDER}
|
|
|
|
{$ifdef FPC} // under FPC, MemSize() returns the value expected by xSize()
|
|
|
|
function xMalloc(size: integer): pointer; cdecl;
|
|
begin
|
|
result := GetMem(size);
|
|
end;
|
|
|
|
procedure xFree(ptr: pointer); cdecl;
|
|
begin
|
|
FreeMem(ptr);
|
|
end;
|
|
|
|
function xRealloc(ptr: pointer; size: integer): pointer; cdecl;
|
|
begin
|
|
result := ReAllocMem(ptr,size);
|
|
end;
|
|
|
|
function xSize(ptr: pointer): integer; cdecl;
|
|
begin
|
|
result := MemSize(ptr);
|
|
end;
|
|
|
|
{$else} // under Delphi, we need to store the size as 4 bytes header for xSize()
|
|
|
|
function xMalloc(size: integer): pointer; cdecl;
|
|
begin
|
|
GetMem(result,size+4);
|
|
PInteger(result)^ := size;
|
|
inc(PInteger(result));
|
|
end;
|
|
|
|
procedure xFree(ptr: pointer); cdecl;
|
|
begin
|
|
dec(PInteger(ptr));
|
|
FreeMem(ptr);
|
|
end;
|
|
|
|
function xRealloc(ptr: pointer; size: integer): pointer; cdecl;
|
|
begin
|
|
dec(PInteger(ptr));
|
|
ReallocMem(ptr,size+4);
|
|
PInteger(ptr)^ := size;
|
|
inc(PInteger(ptr));
|
|
result := ptr;
|
|
end;
|
|
|
|
function xSize(ptr: pointer): integer; cdecl;
|
|
begin
|
|
if ptr=nil then
|
|
result := 0 else begin
|
|
dec(PInteger(ptr));
|
|
result := PInteger(ptr)^;
|
|
end;
|
|
end;
|
|
|
|
{$endif FPC}
|
|
|
|
{$endif DELPHI5OROLDER}
|
|
|
|
function xRoundup(size: integer): integer; cdecl;
|
|
begin
|
|
result := size;
|
|
end;
|
|
function xInit(appData: pointer): integer; cdecl;
|
|
begin
|
|
result := SQLITE_OK;
|
|
end;
|
|
procedure xShutdown(appData: pointer); cdecl;
|
|
begin
|
|
end;
|
|
|
|
procedure TSQLite3Library.ForceToUseSharedMemoryManager;
|
|
{$ifdef DELPHI5OROLDER}
|
|
begin // varargs attribute was unsupported on Delphi 5
|
|
{$else}
|
|
// due to FPC's linker limitation, all wrapper functions should be defined outside
|
|
var mem: TSQLite3MemMethods;
|
|
res: integer;
|
|
{$ifdef FPC_X64}
|
|
mm: TMemoryManager;
|
|
{$endif FPC_X64}
|
|
begin
|
|
if not Assigned(config) then
|
|
exit;
|
|
{$ifdef FPC_X64} // SQLite3 prototypes match FPC RTL functions on x86_64 ABI
|
|
GetMemoryManager(mm);
|
|
mem.xMalloc := @mm.Getmem;
|
|
mem.xFree := @mm.Freemem;
|
|
mem.xSize := @mm.MemSize;
|
|
{$else}
|
|
mem.xMalloc := @xMalloc;
|
|
mem.xFree := @xFree;
|
|
mem.xSize := @xSize;
|
|
{$endif FPC_X64}
|
|
mem.xRealloc := @xRealloc;
|
|
mem.xRoundup := @xRoundup;
|
|
mem.xInit := @xInit;
|
|
mem.xShutdown := @xShutdown;
|
|
mem.pAppData := nil;
|
|
try
|
|
res := config(SQLITE_CONFIG_MALLOC,@mem);
|
|
except
|
|
res := SQLITE_INTERNAL;
|
|
end;
|
|
if res<>SQLITE_OK then begin
|
|
{$ifdef WITHLOG}
|
|
SynSQLite3Log.Add.Log(sllError,'SQLITE_CONFIG_MALLOC failed as %',[res]);
|
|
{$endif}
|
|
end else
|
|
fUseInternalMM := true;
|
|
{$endif DELPHI5OROLDER}
|
|
end;
|
|
|
|
function TSQLite3Library.GetVersion: RawUTF8;
|
|
const MM: array[boolean] of string[2] = ('ex','in');
|
|
begin
|
|
if self=nil then
|
|
result := 'No TSQLite3Library available' else
|
|
FormatUTF8('% % with %ternal MM',[self,fVersionText,MM[fUseInternalMM]],result);
|
|
end;
|
|
|
|
|
|
{ TSQLite3LibraryDynamic }
|
|
|
|
const
|
|
SQLITE3_ENTRIES: array[0..91] of TFileName =
|
|
('initialize','shutdown','open','open_v2','key','rekey','close',
|
|
'libversion','errmsg','extended_errcode',
|
|
'create_function','create_function_v2', 'create_window_function',
|
|
'create_collation','last_insert_rowid','busy_timeout','busy_handler',
|
|
'prepare_v2','finalize','next_stmt','reset','stmt_readonly','step',
|
|
'column_count','column_type','column_decltype','column_name','column_bytes',
|
|
'column_value','column_double','column_int','column_int64','column_text',
|
|
'column_text16','column_blob','value_type','value_numeric_type',
|
|
'value_bytes','value_double','value_int64','value_text','value_blob',
|
|
'result_null','result_int64','result_double','result_blob','result_text',
|
|
'result_value','result_error','user_data','context_db_handle',
|
|
'aggregate_context','bind_text','bind_blob','bind_zeroblob','bind_double',
|
|
'bind_int','bind_int64','bind_null','clear_bindings','bind_parameter_count',
|
|
'blob_open','blob_reopen','blob_close','blob_read','blob_write','blob_bytes',
|
|
'create_module_v2','declare_vtab','set_authorizer','update_hook',
|
|
'commit_hook','rollback_hook','changes','total_changes','malloc', 'realloc',
|
|
'free','memory_used','memory_highwater','trace_v2','limit',
|
|
'backup_init','backup_step','backup_finish','backup_remaining',
|
|
'backup_pagecount','serialize','deserialize','soft_heap_limit64',
|
|
'config','db_config');
|
|
|
|
constructor TSQLite3LibraryDynamic.Create(const LibraryName: TFileName);
|
|
var P: PPointerArray;
|
|
i: integer;
|
|
vers: PUTF8Char;
|
|
begin
|
|
fLibraryName := LibraryName;
|
|
{$ifdef MSWINDOWS}
|
|
fHandle := SafeLoadLibrary(LibraryName);
|
|
if fHandle=0 then
|
|
{$else}
|
|
{$ifdef BSDNOTDARWIN}
|
|
fHandle := TLibHandle(dlopen(PChar(LibraryName),0));
|
|
if fHandle=TLibHandle(nil) then
|
|
{$else}
|
|
fHandle := LoadLibrary({$ifndef FPC}pointer{$endif}(LibraryName));
|
|
if fHandle=0 then
|
|
{$endif}
|
|
{$endif MSWINDOWS}
|
|
raise ESQLite3Exception.CreateUTF8('%.Create: Unable to load % - %',
|
|
[self,LibraryName,SysErrorMessage(GetLastError)]);
|
|
P := @@initialize;
|
|
for i := 0 to High(SQLITE3_ENTRIES) do
|
|
P^[i] := {$ifdef BSDNOTDARWIN}dlsym{$else}GetProcAddress{$endif}(
|
|
fHandle,PChar('sqlite3_'+SQLITE3_ENTRIES[i]));
|
|
if not Assigned(initialize) or not Assigned(libversion) or
|
|
not Assigned(open) or not Assigned(close) or not Assigned(create_function) or
|
|
not Assigned(prepare_v2) or not Assigned(create_module_v2) then begin
|
|
if Assigned(libversion) then
|
|
vers := libversion else
|
|
vers := nil;
|
|
{$ifdef BSDNOTDARWIN}
|
|
dlclose(fHandle);
|
|
fHandle := TLibHandle(nil);
|
|
{$else}
|
|
FreeLibrary(fHandle);
|
|
fHandle := 0;
|
|
{$endif}
|
|
raise ESQLite3Exception.CreateUTF8('%.Create: TOO OLD % % - need 3.7 at least!',
|
|
[self,LibraryName,vers]);
|
|
end; // some APIs like config() key() or trace() may not be available
|
|
inherited Create; // set fVersionNumber/fVersionText
|
|
{$ifdef WITHLOG}
|
|
SynSQLite3Log.Add.Log(sllInfo,'Loaded external % version %',[LibraryName,Version]);
|
|
{$endif}
|
|
end;
|
|
|
|
destructor TSQLite3LibraryDynamic.Destroy;
|
|
begin
|
|
{$ifdef BSDNOTDARWIN}
|
|
if fHandle<>TLibHandle(nil) then
|
|
dlclose(fHandle);
|
|
{$else}
|
|
if fHandle<>0 then
|
|
FreeLibrary(fHandle);
|
|
{$endif}
|
|
inherited;
|
|
end;
|
|
|
|
|
|
initialization
|
|
|
|
finalization
|
|
FreeAndNil(sqlite3); // sqlite3.Free is not reintrant e.g. as .bpl in IDE
|
|
end.
|