Files
HeidiSQL/components/mORMot/SynSQLite3.pas

4650 lines
205 KiB
ObjectPascal

/// SQLite3 embedded 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.17
unit SynSQLite3;
{
This file is part of Synopse mORMot framework.
Synopse mORMot framework. Copyright (C) 2012 Arnaud Bouchez
Synopse Informatique - http://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) 2012
the Initial Developer. All Rights Reserved.
Contributor(s):
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.7.14 database engine
********************************
Brand new SQLite3 library to be used with Delphi
- 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 (no external dll required)
- 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
Initial version: 2008 March, by Arnaud Bouchez - as SQLite3.pas
Version 1.15
- first public release, corresponding to mORMot Framework 1.15
- new unit extracting the SQLite3 wrapper from the previous SQLite3 unit:
this unit can therefore be used with our SynDB classes (via SynDBSQLite3),
without SQLite3Commons overhead (and features)
- added TSQLRequest.BindNull method and associated sqlite3_bind_null function
- fixed issue with TSQLDataBase with UseCache=false
- new TSQLStatementCached object, for caching of prepared SQLite3 statements
- TSQLDatabase constructors now accepts an optional Password parameter,
associated to the supplied file name, in order to use database encryption
Version 1.16
- updated SQLite3 engine to version 3.7.12.1
- unit now includes FTS3/FTS4 by default (i.e. INCLUDE_FTS3 conditional is
set in both SQLite3.pas and SynSQLite3.pas units)
- added sqlite3_changes() and sqlite3_total_changes() function prototypes
- new TSQLDataBase.LastChangeCount method (wrapper around sqlite3_changes)
- new IsSQLite3FileEncrypted() function
- new TSQLRequest.FieldBlobToStream and Bind(TCustomMemoryStream) methods
- new parameter in TSQLDataBase.ExecuteJSON, LockJSON, UnLockJSON methods,
for an optional integer pointer, to return the count of row data
- added an optional behavior parameter to TSQLDataBase.TransactionBegin method
- reintroduce TSQLDataBaseSQLFunction.Create() constructor, and added some
TSQLDataBase.RegisterSQLFunction() overloaded methods
- fixed issue in TSQLRequest.Reset() which was triggered an error about the
latest statement execution
- fixed potential issue after TSQLStatementCached.ReleaseAllDBStatements
- fixed rounding issue when exporting DOUBLE columns into JSON
- fixed issue of unraised exception in TSQLRequest.PrepareNext
- TSQLRequest.Execute(JSON: TStream) now allows field names at least, even
with no data (as expected by TSQLRestClientURI.UpdateFromServer)
- renamed ESQLException into ESQLite3Exception
- engine is now compiled including tracing within the FTS3 extension - added
sqlite3_trace() function prototype to register your own tracing callback
Version 1.17
- updated SQLite3 engine to version 3.7.14
- allow compilation with Delphi 5
- added TSQLDataBase.CacheFlush method (needed by SQLite3DB)
- added TSQLDataBase.Synchronous and TSQLDataBase.WALMode properties
- added TSQLDataBase.ExecuteNoException() overloaded methods
- fixed ticket [8dc4d49ea9] in TSQLDataBase.GetFieldNames()about result array
truncated to 64
Todo:
- port to systems other than Delphi+Win32 (use external DLL?)
}
{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER
interface
uses
Windows,
SysUtils,
Classes,
{$ifndef LVCL}
Contnrs,
{$endif}
SynCommons;
{.$define ENHANCEDRTL}
{ define this if you DID install our Enhanced Runtime library or LVCL
- it's better to define this globaly in the Project/Options window }
{$ifdef UNICODE}
{$undef ENHANCEDRTL} // Delphi 2009.. don't have our Enhanced Runtime library
{$endif}
{$define INCLUDE_FTS3}
{ define this if you want to include the FTS3/FTS4 feature into the library
- FTS3 is an SQLite module implementing full-text search
- will include also FTS4 extension module since 3.7.4
- see http://www.sqlite.org/fts3.html for documentation
- is defined by default, but can be unset to save about 50 KB of code size
- should be defined for both SynSQLite3 and SQLite3 units }
{$ifdef INCLUDE_FTS3}
{$define INCLUDE_TRACE}
{ define this is you want to include the TRACE feature into the library
- our C source code custom header will define SQLITE_OMIT_TRACE if FTS3/FST4
is not defined }
{$endif}
{.$define USEFASTCALL}
{ use the fastcall calling convention to access the SQLite3 library
- BCC32 -pr fastcall (=Delphi resgister) is buggy, don't know why
(because of issues with BCC32 itself, or some obfuscated calls in source?)
- should be defined for both SynSQLite3 and SQLite3 units }
{ ************ direct access to sqlite3.c / sqlite3.obj consts and functions }
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 any array of SQLite3 value
TSQLite3ValueArray = array[0..63] of TSQLite3Value;
const
{/ 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_v2 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;
/// 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
SQLITE_TRANSIENT = pointer(-1);
{/ initialize the SQLite3 database code
- automaticaly called by the initialization block of this unit
- so sqlite3.c is compiled with SQLITE_OMIT_AUTOINIT defined }
function sqlite3_initialize: integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ shutdown the SQLite3 database core
- automaticaly called by the finalization block of this unit }
function sqlite3_shutdown: integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 otherwize - 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 }
function sqlite3_open(filename: PUTF8Char; var DB: TSQLite3DB): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
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 vale must be released via
// TObject(p).Free
TSQLDestroyPtr = procedure(p: pointer); {$ifndef USEFASTCALL}cdecl;{$endif}
/// SQLite3 collation (i.e. sort and comparaison) function prototype
// - this function MUST use s1Len and s2Len parameters during the comparaison:
// 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; {$ifndef USEFASTCALL}cdecl;{$endif}
/// 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); {$ifndef USEFASTCALL}cdecl;{$endif}
/// SQLite3 user final aggregate callback prototype
TSQLFunctionFinal = procedure(Context: TSQLite3FunctionContext); {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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;
{$ifndef USEFASTCALL}cdecl;{$endif}
{$A-}
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 = record
nPhrase: integer;
nCol: integer;
hits: array[1..9] of record
this_row: integer;
all_rows: integer;
docs_with_hits: integer;
end;
end;
{$A+}
{/ Function creation routine used to 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 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.
- If the tenth parameter is not NULL, 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().
- 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. }
function sqlite3_create_function_v2(DB: TSQLite3DB; FunctionName: PUTF8Char;
nArg, eTextRep: integer; pApp: pointer; xFunc, xStep: TSQLFunctionFunc;
xFinal: TSQLFunctionFinal; xDestroy: TSQLDestroyPtr): Integer;
{$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_create_collation(DB: TSQLite3DB; CollationName: PUTF8Char;
StringEncoding: integer; CollateParam: pointer; cmp: TSQLCollateFunc): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_close(DB: TSQLite3DB): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ Return the version of the SQLite database engine, in ascii format
- currently returns '3.7.14' }
function sqlite3_libversion: PUTF8Char; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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. }
function sqlite3_errmsg(DB: TSQLite3DB): PAnsiChar; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ Returns the rowid of the most recent successful INSERT into the database }
function sqlite3_last_insert_rowid(DB: TSQLite3DB): Int64; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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. }
function sqlite3_busy_timeout(DB: TSQLite3DB; Milliseconds: integer): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 NULL, then SQLITE_BUSY or SQLITE_IOERR_BLOCKED is
returned immediately upon encountering the lock. If the busy callback is not
NULL, then the callback might be invoked with two arguments.
- The default busy callback is NULL. }
function sqlite3_busy_handler(DB: TSQLite3DB;
CallbackPtr: TSQLBusyHandler; user: Pointer): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ Convenience Routines For Running Queries
- fill Table with all Row/Col for the SQL query
- use sqlite3_free_table() to release memory }
function sqlite3_get_table(DB: TSQLite3DB; SQL: PUTF8Char; var Table: PPUTF8CharArray;
var ResultRow, ResultCol: integer; var Error: PUTF8Char): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ release memory allocated for a sqlite3_get_table() result }
procedure sqlite3_free_table(Table: PPUTF8CharArray); {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ One-Step Query Execution Interface }
function sqlite3_exec(DB: TSQLite3DB; SQL: PUTF8Char; CallBack, Args: pointer; Error: PUTF8Char): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_prepare_v2(DB: TSQLite3DB; SQL: PUTF8Char; SQL_bytes: integer;
var S: TSQLite3Statement; var SQLtail: PUTF8Char): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_finalize(S: TSQLite3Statement): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_next_stmt(DB: TSQLite3DB; S: TSQLite3Statement): TSQLite3Statement; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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. }
function sqlite3_reset(S: TSQLite3Statement): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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. }
function sqlite3_stmt_readonly(S: TSQLite3Statement): boolean; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_step(S: TSQLite3Statement): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ number of columns in the result set for the statement }
function sqlite3_column_count(S: TSQLite3Statement): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_column_type(S: TSQLite3Statement; Col: integer): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ returns a zero-terminated UTF-8 string containing the declared datatype of a result column }
function sqlite3_column_decltype(S: TSQLite3Statement; Col: integer): PAnsiChar; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ returns the name of a result column as a zero-terminated UTF-8 string }
function sqlite3_column_name(S: TSQLite3Statement; Col: integer): PUTF8Char; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_column_bytes(S: TSQLite3Statement; Col: integer): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_column_value(S: TSQLite3Statement; Col: integer): TSQLite3Value; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_column_double(S: TSQLite3Statement; Col: integer): double; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_column_int(S: TSQLite3Statement; Col: integer): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_column_int64(S: TSQLite3Statement; Col: integer): int64; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_column_text(S: TSQLite3Statement; Col: integer): PUTF8Char; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_column_blob(S: TSQLite3Statement; Col: integer): PAnsiChar; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_value_type(Value: TSQLite3Value): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_value_numeric_type(Value: TSQLite3Value): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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) }
function sqlite3_value_bytes(Value: TSQLite3Value): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ converts a sqlite3_value object, specified by its handle,
into a floating point value and returns a copy of that value }
function sqlite3_value_double(Value: TSQLite3Value): double; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ converts a sqlite3_value object, specified by its handle,
into an integer value and returns a copy of that value }
function sqlite3_value_int64(Value: TSQLite3Value): Int64; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ converts a sqlite3_value object, specified by its handle,
into an UTF-8 encoded string, and returns a copy of that value }
function sqlite3_value_text(Value: TSQLite3Value): PUTF8Char; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ converts a sqlite3_value object, specified by its handle,
into a blob memory, and returns a copy of that value }
function sqlite3_value_blob(Value: TSQLite3Value): pointer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ sets the return value of the application-defined function to be NULL}
procedure sqlite3_result_null(Context: TSQLite3FunctionContext); {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ sets the return value of the application-defined function to be the 64-bit
signed integer value given in the 2nd argument }
procedure sqlite3_result_int64(Context: TSQLite3FunctionContext; Value: Int64); {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ sets the result from an application-defined function to be a floating point
value specified by its 2nd argument }
procedure sqlite3_result_double(Context: TSQLite3FunctionContext; Value: double); {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
procedure sqlite3_result_blob(Context: TSQLite3FunctionContext; Value: Pointer;
Value_bytes: Integer=0; DestroyPtr: TSQLDestroyPtr=SQLITE_TRANSIENT); {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ an internal function which calls Freemem(p)
- can be used to free some PUTF8Char pointer allocated by Delphi Getmem() }
procedure sqlite3InternalFree(p: pointer); {$ifndef USEFASTCALL}cdecl;{$endif}
{/ an internal function which calls TObject(p).Free
- can be used to free some Delphi class instance }
procedure sqlite3InternalFreeObject(p: pointer); {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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 }
procedure sqlite3_result_text(Context: TSQLite3FunctionContext; Value: PUTF8Char;
Value_bytes: Integer=-1; DestroyPtr: TSQLDestroyPtr=SQLITE_TRANSIENT); {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
procedure sqlite3_result_value(Context: TSQLite3FunctionContext; Value: TSQLite3Value); {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
procedure sqlite3_result_error(Context: TSQLite3FunctionContext; Msg: PUTF8Char; MsgLen: integer=-1); {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ wrapper around sqlite3_result_error() to be called if wrong number of arguments }
procedure ErrorWrongNumberOfArgs(Context: TSQLite3FunctionContext);
{/ returns a copy of the pointer that was the pUserData parameter (the 5th
parameter) of the sqlite3_create_function_v2() 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 }
function sqlite3_user_data(Context: TSQLite3FunctionContext): pointer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ returns a copy of the pointer to the database connection (the 1st parameter)
of the sqlite3_create_function_v2() routine that originally registered the
application defined function }
function sqlite3_context_db_handle(Context: TSQLite3FunctionContext): TSQLite3DB; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 NULL 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. }
function sqlite3_aggregate_context(Context: TSQLite3FunctionContext;
nBytes: integer): pointer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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() }
function sqlite3_bind_text(S: TSQLite3Statement; Param: integer;
Text: PUTF8Char; Text_bytes: integer=-1; DestroyPtr: TSQLDestroyPtr=SQLITE_TRANSIENT): integer;
{$ifndef USEFASTCALL}cdecl;{$endif} external;
// 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() }
function sqlite3_bind_blob(S: TSQLite3Statement; Param: integer; Buf: pointer; Buf_bytes: integer;
DestroyPtr: TSQLDestroyPtr=SQLITE_TRANSIENT): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_bind_zeroblob(S: TSQLite3Statement; Param: integer; Size: integer): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_bind_double(S: TSQLite3Statement; Param: integer; Value: double): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_bind_Int(S: TSQLite3Statement; Param: integer; Value: integer): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 }
function sqlite3_bind_Int64(S: TSQLite3Statement; Param: integer; Value: Int64): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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) }
function sqlite3_bind_null(S: TSQLite3Statement; Param: integer): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ Reset All Bindings On A Prepared Statement }
function sqlite3_clear_bindings(S: TSQLite3Statement): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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 are used, there may be gaps in the list. }
function sqlite3_bind_parameter_count(S: TSQLite3Statement): integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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; }
function sqlite3_blob_open(DB: TSQLite3DB; DBName, TableName, ColumnName: PUTF8Char;
RowID: Int64; Flags: Integer; var Blob: TSQLite3Blob): Integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ Close A BLOB Handle }
function sqlite3_blob_close(Blob: TSQLite3Blob): Integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ Read Data From a BLOB Incrementally }
function sqlite3_blob_read(Blob: TSQLite3Blob; const Data; Count, Offset: Integer): Integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ Write Data To a BLOB Incrementally }
function sqlite3_blob_write(Blob: TSQLite3Blob; const Data; Count, Offset: Integer): Integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ Return The Size Of An Open BLOB }
function sqlite3_blob_bytes(Blob: TSQLite3Blob): Integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
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;
type
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: boolean;
/// 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: boolean;
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: boolean;
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;
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: PAnsiChar;
/// 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: PAnsiChar): Integer; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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: PAnsiChar): Integer; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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;
{$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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;
{$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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;
{$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ Causes a virtual table transaction to commit }
xCommit: function(var pVTab: TSQLite3VTab): Integer; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ Causes a virtual table transaction to rollback }
xRollback: function(var pVTab: TSQLite3VTab): Integer; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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;
{$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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;
{$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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;
{$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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;
{$ifndef USEFASTCALL}cdecl;{$endif}
end;
{/ 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 }
function sqlite3_create_module_v2(DB: TSQLite3DB; const zName: PAnsiChar;
var p: TSQLite3Module; pClientData: Pointer; xDestroy: TSQLDestroyPtr): Integer;
{$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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. }
function sqlite3_declare_vtab(DB: TSQLite3DB; const zSQL: PAnsiChar): Integer;
{$ifndef USEFASTCALL}cdecl;{$endif} external;
type
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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 }
function sqlite3_set_authorizer(DB: TSQLite3DB; xAuth: TSQLAuthorizerCallback;
pUserData: Pointer): Integer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
type
{/ 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); {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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. }
function sqlite3_update_hook(DB: TSQLite3DB; xCallback: TSQLUpdateCallback;
pArg: pointer): pointer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
type
{/ 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; {$ifndef USEFASTCALL}cdecl;{$endif}
{/ 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(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. }
function sqlite3_commit_hook(DB: TSQLite3DB; xCallback: TSQLCommitCallback;
pArg: Pointer): Pointer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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. }
function sqlite3_rollback_hook(DB: TSQLite3DB; xCallback: TSQLCommitCallback;
pArg: Pointer): Pointer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{$ifdef INCLUDE_TRACE}
type
{/ Callback function registered by sqlite3_trace()
- this procedure will be invoked at various times when an SQL statement is
being run by sqlite3_step() }
TSQLTraceCallback = procedure(TraceArg: Pointer; Trace: PUTF8Char);
{$ifndef USEFASTCALL}cdecl;{$endif}
{/ Register callback function that can be used for tracing the execution of
SQL statements
- The callback function registered by sqlite3_trace() is invoked at various
times when an SQL statement is being run by sqlite3_step(). The sqlite3_trace()
callback is invoked with a UTF-8 rendering of the SQL statement text as the
statement first begins executing. Additional sqlite3_trace() callbacks might
occur as each triggered subprogram is entered. The callbacks for triggers
contain a UTF-8 SQL comment that identifies the trigger. }
function sqlite3_trace(DB: TSQLite3DB; tCallback: TSQLTraceCallback;
aUserData: Pointer): Pointer; {$ifndef USEFASTCALL}cdecl;{$endif} external;
{$endif INCLUDE_TRACE}
(* this function is considered experimental and is subject to change in future
versions of SQLite -> not declared yet (you should better use our generic
profiling feature as implemented with SynCommons.TSynLog class)
type
TSQLProfileCallback = procedure(ProfileArg: Pointer; Profile: PUTF8Char;
ElapsedNS: Int64); {$ifndef USEFASTCALL}cdecl;{$endif}
function sqlite3_profile(DB: TSQLite3DB; pCallback: TSQLProfileCallback;
aUserData: Pointer): Pointer; {$ifndef USEFASTCALL}cdecl;{$endif} external; *)
{/ 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. }
function sqlite3_changes(DB: TSQLite3DB): Integer;
{$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ 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. }
function sqlite3_total_changes(DB: TSQLite3DB): Integer;
{$ifndef USEFASTCALL}cdecl;{$endif} external;
const
SQLITE_DENY = 1;
SQLITE_IGNORE = 2;
const
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;
const
/// 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
/// custom SQLite3 dedicated Exception type
ESQLite3Exception = class(ESynException)
public
/// the DB which raised this exception
DB: TSQLite3DB;
/// the corresponding error code
ErrorCode: integer;
/// create the exception, getting the message from DB
constructor Create(aDB: TSQLite3DB; aErrorCode: integer); reintroduce; overload;
/// create the exception, getting the message from caller
constructor Create(const aMessage: string; aErrorCode: integer); reintroduce; overload;
end;
{/ test the result state of a sqlite3_*() function
- raise a ESQLite3Exception if the result state is an error
- return the result state otherwize (SQLITE_OK,SQLITE_ROW,SQLITE_DONE e.g.) }
function sqlite3_check(DB: TSQLite3DB; aResult: integer): integer;
{/ 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 }
function sqlite3_malloc(n: Integer): Pointer;
{$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ Attempts to resize a prior memory allocation
- should call native realloc() function, i.e. ReallocMem() in this unit }
function sqlite3_realloc(pOld: Pointer; n: Integer): Pointer;
{$ifndef USEFASTCALL}cdecl;{$endif} external;
{/ Releases memory previously returned by sqlite3_malloc() or sqlite3_realloc()
- should call native free() function, i.e. FreeMem() in this unit }
procedure sqlite3_free(p: Pointer);
{$ifndef USEFASTCALL}cdecl;{$endif} external;
/// Returns the number of bytes of memory currently outstanding (malloced but not freed)
function sqlite3_memory_used: Int64;
{$ifndef USEFASTCALL}cdecl;{$endif} external;
/// Returns the maximum value of sqlite3_memory_used() since the high-water mark
// was last reset
function sqlite3_memory_highwater(resetFlag: Integer): Int64;
{$ifndef USEFASTCALL}cdecl;{$endif} external;
{ ************ 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);
TSQLDatabase = class;
TSQLBlobStream = class;
PSQLRequest = ^TSQLRequest;
/// wrapper to a SQLite3 request
TSQLRequest = {$ifndef UNICODE}object{$else}record{$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 }
function Prepare(DB: TSQLite3DB; const SQL: RawUTF8): 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 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 }
procedure Bind(Param: Integer; const Value: RawUTF8); 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 }
procedure Bind(Param: Integer; Data: pointer; Size: integer); overload;
{/ 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 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;
{/ 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);
{/ 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;
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
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): PSQLRequest;
// used internaly to release all prepared statements from Cache[]
procedure ReleaseAllDBStatements;
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_v2() 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_v2() 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(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);
/// 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
private
fDB: TSQLite3DB;
fFileName: TFileName;
fCypherBlock: RawByteString;
fTransactionActive: boolean;
fLock: TRTLCriticalSection;
/// if not nil, cache is used - see UseCache property
fCache: TSynCache;
fInternalState: PCardinal;
fBusyTimeout: Integer;
fLog: TSynLog;
/// store TSQLDataBaseSQLFunction instances
fSQLFunctions: TObjectList;
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;
public
/// enter the TRTLCriticalSection: called before any DB access
// - provide the SQL statement about to be executed: handle proper caching
// - is the SQL statement is void, assume a SELECT statement (no cache flush)
procedure Lock(const aSQL: RawUTF8);
/// leave the TRTLCriticalSection: called after any DB access
procedure UnLock;
/// enter the TRTLCriticalSection: 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 TRTLCriticalSection: 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 TRTLCriticalSection: 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
function DBOpen: integer;
/// 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
procedure DBClose;
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)
- SYSTEMNOCASE collation is added (our custom fast UTF-8 case insensitive compare,
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
- initialize a TRTLCriticalSection to ensure that all access to the database is atomic
- raise an ESQLite3Exception on any error }
constructor Create(const aFileName: TFileName; const aPassword: RawUTF8='');
{/ 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); 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); 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; overload;
{/ 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 }
procedure ExecuteNoException(const aSQL: RawUTF8; out ID: Int64); overload;
{/ 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 }
procedure ExecuteNoException(const aSQL: RawUTF8; out ID: RawUTF8); overload;
/// 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;
{/ 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);
/// 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=-1, then the last inserted RowID is used
- will raise an ESQLite3Exception on any error }
function Blob(const DBName, TableName, ColumnName: RawUTF8;
RowID: Int64=-1; ReadWrite: boolean=false): TSQLBlobStream;
{/ backup of the opened Database into an external file name
- don't use the experimental SQLite Online Backup API
- database is closed, VACCUUMed, copied, then reopened: it's very fast }
function Backup(const BackupFileName: 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;
{/ access to the log instance associated with this SQLite3 database engine }
property Log: TSynLog read fLog;
{/ read-only access to the SQLite3 database filename opened }
property FileName: TFileName read fFileName;
/// 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;
/// access to the internal JSON cache, used by ExecuteJSON() method
// - see UseCache property and CacheFlush method
property Cache: TSynCache read fCache;
/// 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;
/// 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 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;
{/ 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;
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: longint;
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;
{/ read-only access to the BLOB object handle }
property Handle: TSQLite3Blob read fBlob;
end;
{$ifdef WITHLOG}
var
/// the TSynLog class used for logging for all our SynSQlite3 related functions
// - you may override it with TSQLLog, if available from SQLite3Commons
// - 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}
const
/// encryption XOR mask table size (in bytes)
// - must be a power of 2
// - bigger encryption table makes stronger encryption, but use more memory
// - it's faster when the mask table can stay in the CPU L1 cache
// - default size is therefore 16KB
SQLEncryptTableSize = $4000;
var
/// in order to allow file encryption on disk, initialize this pointer
// with SQLEncryptTableSize bytes of XOR tables
// - you can use fixed or custom (SHA+AES) generated table
// - using a fixed XOR table is very fast and provides strong enough encryption
// - the first page (first 1024 bytes) is not encrypted, since its content
// (mostly zero) can be used to easily guess the beginning of the key
// - if the key is not correct, a ESQLite3Exception will be raised with
// 'database disk image is malformed' (ErrorCode=SQLITE_CORRUPT)
// - this table is common to ALL files accessed by the database engine: you
// have maintain several XOR mask arrays, and set SQLEncryptTable before any
// sqlite3*() call, to mix passowords or crypted and uncrypted databases
// (see ChangeSQLEncryptTablePassWord() for multiple SQLEncryptTable use)
// - please note that this encryption is compatible only with SQlite3 files
// using the default page size of 1024
SQLEncryptTable: PByteArray = nil;
{type
/// this function prototype is called for on-the-fly encryption of a 1KB page
// - p is a buffer containing the 1024 bytes of data to be encrypted
// - PageIndex is the 1024 bytes length page number
// - Encrypt is true to cypher, false to uncypher
TSQLEncryptFunc: procedure(p: pByte; PageIndex: cardinal; Encrypt: boolean);
var
/// in order to allow file encryption on disk, initialize this pointer
// with a TSQLEncryptFunc procedure for per page encryption
// - use either SQLEncryptFunc, either SQLEncryptTable
// - SQLEncryptFunc has a page orientation, SQLEncryptTable a byte-orientation
SQLEncryptFunc: TSQLEncryptFunc;}
/// you can use this simple (and strong enough) procedure for easy SQL encryption
// - usage is global for ALL SQLite3 databases access
// - specify an ascii or binary password
// - a buffer is allocated and initialized in SQLEncryptTable
// - call with Password='' to end up encryption
// - you may use instead SynCrypto unit for more secure SHA-256 and AES-256 algos
// - please note that this encryption is compatible only with SQlite3 files
// using the default page size of 1024
procedure CreateSQLEncryptTable(const PassWord: RawUTF8);
/// use this procedure to change the password for an existing SQLite3 database file
// - conversion is done in-place, therefore this procedure can handle very big files
// - the OldPassWord must be correct, otherwize the resulting file will be corrupted
// - any password can be '' to mark no encryption
// - you may use instead SynCrypto unit for more secure SHA-256 and AES-256 algos
// - please note that this encryption is compatible only with SQlite3 files
// using the default page size of 1024
procedure ChangeSQLEncryptTablePassWord(const FileName: TFileName;
const OldPassWord, NewPassword: RawUTF8);
/// check from the file beginning if sounds like a valid SQLite3 file
// - since encryption starts only with the 2nd page, this function will
// return true if a database file is encrypted or not
function IsSQLite3File(const FileName: TFileName): boolean;
/// check if sounds like an encrypted SQLite3 file
// - this will check the 2nd file page beginning to be a valid B-TREE page
// - in some cases, may return false negatives (depending on the password used)
function IsSQLite3FileEncrypted(const FileName: TFileName): boolean;
implementation
{ ************ direct access to sqlite3.c / sqlite3.obj consts and functions }
{
Code below will link all database engine, from amalgamation source file:
- compiled with free Borland C++ compiler 5.5.1 from the command line:
\dev\bcc\bin\bcc32 -6 -O2 -c -d -u- sqlite3.c
- FastCall use must be set with defining USEFASTCALL above, and
int __cdecl fts3CompareElemByTerm(const void *lhs, const void *rhs)
\dev\bcc\bin\bcc32 -6 -O2 -c -d -pr -u- sqlite3.c
- the following defines must be added in the beginning of the sqlite3.c file:
//#define SQLITE_ENABLE_FTS3
// this unit is FTS3-ready, but not compiled with it by default
// if you don't use FTS3, dont define this conditional: you'll spare 50KB of code
// this conditional is defined at compile time, in order to create sqlite3fts3.obj
#define SQLITE_DEFAULT_MEMSTATUS 0
// don't need any debug here
#define SQLITE_THREADSAFE 2
// assuming multi-thread safety is made by caller - in our framework, there is
// only one thread using the database connection at the same time, but there could
// be multiple database connection at the same time (previous was 0 could be unsafe)
#define SQLITE_OMIT_SHARED_CACHE 1
// no need of shared cache in a threadsafe calling model
#define SQLITE_OMIT_AUTOINIT 1
// sqlite3_initialize() is done in initialization section below -> no AUTOINIT
#define SQLITE_OMIT_DEPRECATED 1
// spare some code size - is now defined only if compiled without FTS3/FTS4
#ifndef SQLITE_ENABLE_FTS3
#define SQLITE_OMIT_TRACE 1
#endif
// we don't need sqlite3_profile() and sqlite3_trace() interfaces
#define SQLITE_OMIT_LOAD_EXTENSION 1
// we don't need extension in an embedded engine
#define SQLITE_OMIT_COMPILEOPTION_DIAGS 1
// we don't need Compilation Options Diagnostics in our embedded engine
#define SQLITE_OMIT_PROGRESS_CALLBACK 1
// we don't need sqlite3_progress_handler() API function
#define SQLITE_ENABLE_RTREE 1
// the RTREE extension is now (from v.1.8/3.7) compiled into the engine
//#define SQLITE_OMIT_LOOKASIDE
// even if we use FastMM4/SynScaleMM, LookAside seems mandatory in c source
#define SQLITE_WITHOUT_MSIZE
// _msize() is not available (nor needed) with FastMM4 memory manager
and, in the sqlite3.c source file, the following functions are made external
in order to allow our proprietary but simple and efficient encryption system:
extern int winRead(
sqlite3_file *id, /* File to read from */
void *pBuf, /* Write content into this buffer */
int amt, /* Number of bytes to read */
sqlite3_int64 offset /* Begin reading at this offset */
);
extern int winWrite(
sqlite3_file *id, /* File to write into */
const void *pBuf, /* The bytes to be written */
int amt, /* Number of bytes to write */
sqlite3_int64 offset /* Offset into the file to begin writing at */
);
}
{$ifdef INCLUDE_FTS3}
{$L sqlite3fts3.obj} // link SQlite3 database engine with FTS3/FTS4 + TRACE
{$else}
{$L sqlite3.obj} // link SQlite3 database engine
{$endif}
// we then implement all needed Borland C++ runtime functions in pure pascal:
function _ftol: Int64;
// Borland C++ float to integer (Int64) conversion
asm
jmp System.@Trunc // FST(0) -> EDX:EAX, as expected by BCC32 compiler
end;
function _ftoul: Int64;
// Borland C++ float to integer (Int64) conversion
asm
jmp System.@Trunc // FST(0) -> EDX:EAX, as expected by BCC32 compiler
end;
function malloc(size: cardinal): Pointer; cdecl; { always cdecl }
// the SQLite3 database engine will use the FastMM4/SynScaleMM fast heap manager
begin
GetMem(Result, size);
end;
procedure free(P: Pointer); cdecl; { always cdecl }
// the SQLite3 database engine will use the FastMM4 very fast heap manager
begin
FreeMem(P);
end;
function realloc(P: Pointer; Size: Integer): Pointer; cdecl; { always cdecl }
// the SQLite3 database engine will use the FastMM4/SynScaleMM very fast heap manager
begin
result := P;
ReallocMem(result,Size);
end;
function memset(P: Pointer; B: Integer; count: Integer): pointer; cdecl; { always cdecl }
// a fast full pascal version of the standard C library function
begin
result := P;
FillChar(P^, count, B);
end;
procedure memmove(dest, source: pointer; count: Integer); cdecl; { always cdecl }
// a fast full pascal version of the standard C library function
begin
Move(source^, dest^, count); // move() is overlapping-friendly
end;
procedure memcpy(dest, source: Pointer; count: Integer); cdecl; { always cdecl }
// a fast full pascal version of the standard C library function
begin
Move(source^, dest^, count);
end;
function atol(P: pointer): integer; cdecl; { always cdecl }
// a fast full pascal version of the standard C library function
begin
result := GetInteger(P);
end;
var __turbofloat: word; { not used, but must be present for linking }
// Borland C++ and Delphi share the same low level Int64 _ll*() functions:
procedure _lldiv;
asm
jmp System.@_lldiv
end;
procedure _lludiv;
asm
jmp System.@_lludiv
end;
procedure _llmod;
asm
jmp System.@_llmod
end;
procedure _llmul;
asm
jmp System.@_llmul
end;
procedure _llumod;
asm
jmp System.@_llumod
end;
procedure _llshl;
asm
jmp System.@_llshl
end;
procedure _llshr;
asm
{$ifndef ENHANCEDRTL} // need this code for Borland/CodeGear default System.pas
shrd eax, edx, cl
sar edx, cl
cmp cl, 32
jl @@Done
cmp cl, 64
jge @@RetSign
mov eax, edx
sar edx, 31
ret
@@RetSign:
sar edx, 31
mov eax, edx
@@Done:
{$else}
// our customized System.pas didn't forget to put _llshr in its interface :)
jmp System.@_llshr
{$endif}
end;
procedure _llushr;
asm
jmp System.@_llushr
end;
function strlen(p: PAnsiChar): integer; cdecl; { always cdecl }
// a fast full pascal version of the standard C library function
begin // called only by some obscure FTS3 functions (normal code use dedicated functions)
result := SynCommons.StrLen(pointer(p));
end;
function memcmp(p1, p2: pByte; Size: integer): integer; cdecl; { always cdecl }
// a fast full pascal version of the standard C library function
begin
if (p1<>p2) and (Size<>0) then
if p1<>nil then
if p2<>nil then begin
repeat
if p1^<>p2^ then begin
result := p1^-p2^;
exit;
end;
dec(Size);
inc(p1);
inc(p2);
until Size=0;
result := 0;
end else
result := 1 else
result := -1 else
result := 0;
end;
function strncmp(p1, p2: PByte; Size: integer): integer; cdecl; { always cdecl }
// a fast full pascal version of the standard C library function
var i: integer;
begin
for i := 1 to Size do begin
result := p1^-p2^;
if (result<>0) or (p1^=0) then
exit;
inc(p1);
inc(p2);
end;
result := 0;
end;
// qsort() is used if SQLITE_ENABLE_FTS3 is defined
type // this function type is defined for calling termDataCmp() in sqlite3.c
qsort_compare_func = function(P1,P2: pointer): integer; cdecl; { always cdecl }
procedure QuickSort4(base: PPointerArray; L, R: Integer; comparF: qsort_compare_func);
var I, J, P: Integer;
PP, C: PAnsiChar;
begin
repeat // from SQLite (FTS), With=sizeof(PAnsiChar) AFAIK
I := L;
J := R;
P := (L+R) shr 1;
repeat
PP := @base[P];
while comparF(@base[I],PP)<0 do
inc(I);
while comparF(@base[J],PP)>0 do
dec(J);
if I<=J then begin
C := base[I];
base[I] := base[J];
base[J] := C; // fast memory exchange
if P=I then P := J else if P=J then P := I;
inc(I);
dec(J);
end;
until I>J;
if L<J then
QuickSort4(base, L, J, comparF);
L := I;
until I>=R;
end;
procedure QuickSort(baseP: PAnsiChar; Width: integer; L, R: Integer; comparF: qsort_compare_func);
// code below is very fast and optimized
procedure Exchg(P1,P2: PAnsiChar; Size: integer);
var B: AnsiChar;
i: integer;
begin
for i := 0 to Size-1 do begin
B := P1[i];
P1[i] := P2[i];
P2[i] := B;
end;
end;
var I, J, P: Integer;
PP, C: PAnsiChar;
begin
repeat // generic sorting algorithm
I := L;
J := R;
P := (L+R) shr 1;
repeat
PP := baseP+P*Width; // compute PP at every loop, since P may change
C := baseP+I*Width;
while comparF(C,PP)<0 do begin
inc(I);
inc(C,width); // avoid slower multiplication in loop
end;
C := baseP+J*Width;
while comparF(C,PP)>0 do begin
dec(J);
dec(C,width); // avoid slower multiplication in loop
end;
if I<=J then begin
Exchg(baseP+I*Width,baseP+J*Width,Width); // fast memory exchange
if P=I then P := J else if P=J then P := I;
inc(I);
dec(J);
end;
until I>J;
if L<J then
QuickSort(baseP, Width, L, J, comparF);
L := I;
until I>=R;
end;
procedure qsort(baseP: pointer; NElem, Width: integer; comparF: qsort_compare_func);
cdecl; { always cdecl }
// a fast full pascal version of the standard C library function
begin
if (cardinal(NElem)>1) and (Width>0) then
if Width=sizeof(pointer) then
QuickSort4(baseP, 0, NElem-1, comparF) else
QuickSort(baseP, Width, 0, NElem-1, comparF);
end;
var
{ as standard C library documentation states:
Statically allocated buffer, shared by the functions gmtime() and localtime().
Each call of these functions overwrites the content of this structure.
-> since timing is not thread-dependent, it's OK to share this buffer :) }
atm: packed record
tm_sec: Integer; { Seconds. [0-60] (1 leap second) }
tm_min: Integer; { Minutes. [0-59] }
tm_hour: Integer; { Hours. [0-23] }
tm_mday: Integer; { Day. [1-31] }
tm_mon: Integer; { Month. [0-11] }
tm_year: Integer; { Year - 1900. }
tm_wday: Integer; { Day of week. [0-6] }
tm_yday: Integer; { Days in year. [0-365] }
tm_isdst: Integer; { DST. [-1/0/1]}
__tm_gmtoff: Integer; { Seconds east of UTC. }
__tm_zone: ^Char; { Timezone abbreviation.}
end;
function localtime(t: PCardinal): pointer; cdecl; { always cdecl }
// a fast full pascal version of the standard C library function
var uTm: TFileTime;
lTm: TFileTime;
S: TSystemTime;
begin
Int64(uTm) := (Int64(t^) + 11644473600)*10000000; // unix time to dos file time
FileTimeToLocalFileTime(uTM,lTM);
FileTimeToSystemTime(lTM,S);
with atm do begin
tm_sec := S.wSecond;
tm_min := S.wMinute;
tm_hour := S.wHour;
tm_mday := S.wDay;
tm_mon := S.wMonth-1;
tm_year := S.wYear-1900;
tm_wday := S.wDayOfWeek;
end;
result := @atm;
end;
var
/// local encryption XOR table (size SQLEncryptTableSize=16KB) created
// by the CreateSQLEncryptTableBytes() procedure below
CreateSQLEncryptTablePtr: RawByteString;
procedure CreateSQLEncryptTableBytes(const PassWord: RawUTF8; Table: PByteArray);
// very fast table (private key) computation from a given password
// - use a simple prime-based random generator, strong enough for common use
// - execution speed and code size was the goal here
// - use our SynCrypto for SHA-256 and AES-256 for most secure encryption
var i, j, k, L: integer;
begin
L := length(Password)-1;
j := 0;
k := integer(L*ord(Password[1]))+134775813; // initial value, prime number derivated
for i := 0 to SQLEncryptTableSize-1 do begin
Table^[i] := (ord(PassWord[j+1])) xor byte(k);
k := Integer(k*3+i); // fast prime-based pseudo random generator
if j=L then
j := 0 else
inc(j);
end;
end;
procedure CreateSQLEncryptTable(const PassWord: RawUTF8);
// you can use this simple (and strong enough) procedure for easy SQL encryption
begin
if Password='' then
SQLEncryptTable := nil else begin
if CreateSQLEncryptTablePtr='' then
SetLength(CreateSQLEncryptTablePtr,SQLEncryptTableSize);
CreateSQLEncryptTableBytes(PassWord,pointer(CreateSQLEncryptTablePtr));
SQLEncryptTable := pointer(CreateSQLEncryptTablePtr);
end;
end;
procedure XorOffset(p: pByte; Index, Count: cardinal; SQLEncryptTable: PByteArray);
// XorOffset: fast and simple Cypher using Index (= offset in file):
procedure Xor64(PI, P: PPtrIntArray; Count: cardinal); // fast xor
{$ifdef PUREPASCAL}
var i: cardinal;
begin
for i := 0 to (Count div sizeof(PtrInt))-1 do
P^[i] := P^[i] xor PI^[i]; // this will compile fine for 64 bit CPU
end;
{$else}
asm // eax=PI edx=P ecx=bytes count
push ebx
push esi
shr ecx,3 // 64 bits = 8 bytes per loop
jz @z
@1:
mov ebx,[eax] // fast CPU-pipelined optimized loop
mov esi,[eax+4]
xor [edx],ebx
xor [edx+4],esi
dec ecx
lea eax,[eax+8]
lea edx,[edx+8]
jnz @1
@z:
pop esi
pop ebx
end;
{$endif}
var i, Len, L: integer;
begin
if Count>0 then
repeat
Index := Index and (SQLEncryptTableSize-1);
Len := SQLEncryptTableSize-Index;
if cardinal(Len)>cardinal(Count) then
Len := Count;
Xor64(@SQLEncryptTable^[Index],pointer(p),Len); // xor 8 bytes per loop
L := Len and (-8); // -8=$FFFFFFF8
inc(p,L);
inc(Index,L);
for i := 1 to (Len and 7) do begin // xor 0..7 remaining bytes
p^ := p^ xor SQLEncryptTable^[Index];
inc(p); inc(Index);
end;
Dec(Count,Len);
until Count=0;
end;
procedure ChangeSQLEncryptTablePassWord(const FileName: TFileName;
const OldPassWord, NewPassword: RawUTF8);
var F: THandle;
R: integer;
Buf: array[word] of byte; // temp buffer for read/write (64KB is enough)
Size, Posi: Int64Rec;
OldP, NewP: array[0..SQLEncryptTableSize-1] of byte; // 2x16KB tables
begin
if OldPassword=NewPassword then
exit;
F := FileOpen(FileName,fmOpenReadWrite);
if F=INVALID_HANDLE_VALUE then
exit;
Size.Lo := GetFileSize(F,@Size.Hi);
if (Size.Lo<=1024) and (Size.Hi=0) then begin
FileClose(F); // file is to small to be modified
exit;
end;
if OldPassword<>'' then
CreateSQLEncryptTableBytes(OldPassWord,@OldP);
if NewPassword<>'' then
CreateSQLEncryptTableBytes(NewPassWord,@NewP);
Int64(Posi) := 1024; // don't change first page, which is uncrypted
SetFilePointer(F,1024,nil,FILE_BEGIN); // move to first page after 1024
while Int64(Posi)<Int64(Size) do begin
R := FileRead(F,Buf,sizeof(Buf)); // read buffer
if R<0 then
break; // stop on any read error
if OldPassword<>'' then
XorOffset(@Buf,Posi.Lo,R,@OldP); // uncrypt with old key
if NewPassword<>'' then
XorOffset(@Buf,Posi.Lo,R,@NewP); // crypt with new key
SetFilePointer(F,Posi.Lo,@Posi.Hi,FILE_BEGIN); // rewind
FileWrite(F,Buf,R); // update buffer
inc(Int64(Posi),cardinal(R));
end;
FileClose(F);
end;
// we override default WinRead() and WinWrite() functions below, in order
// to add our proprietary (but efficient) encryption engine
// - should be modified to match other Operating Systems than Windows
type
{$ifndef DELPHI5OROLDER} // Delphi 5 is already aligning records by 4 bytes
{$A4} // bcc32 default alignment is 4 bytes
{$endif}
TSQLFile = record // called winFile (expand sqlite3_file) in sqlite3.c
pMethods: pointer; // sqlite3_io_methods_ptr
pVfs: pointer; // The VFS used to open this file (new in version 3.7)
h: THandle; // Handle for accessing the file
bulk: cardinal; // lockType+sharedLockByte are word-aligned
lastErrno: cardinal; // The Windows errno from the last I/O error
// asm code generated from c is [esi+16] for lastErrNo -> OK
end;
// those structures are used to retrieve the Windows file handle
TSQLPager = record
pVfs: pointer;
exclusiveMode, journalMode, useJournal, noSync, fullSync,
ckptSyncFlags, syncFlags, tempFile, readOnly, memDb: byte;
eState, eLock, changeCountDone, setMaster, doNotSpill, doNotSyncSpill,
subjInMemory: Byte;
dbSize, dbOrigSize, dbFileSize, dbHintSize, errCode, nRec, cksumInit,
nSubRec: cardinal;
pInJournal: pointer;
fd: ^TSQLFile; // File descriptor for database
jfd: ^TSQLFile; // File descriptor for main journal
sjfd: ^TSQLFile; // File descriptor for sub-journal
end;
TSQLBtShared = record
pPager: ^TSQLPager;
end;
TSQLBTree = record
db: TSQLite3DB;
pBt: ^TSQLBtShared;
end;
PSQLBTree = ^TSQLBTree;
TSQLDBOneStruct = record
zName: PAnsiChar;
Btree: PSQLBTree;
end;
// will map TSQLite3DB
PSQLDBStruct = ^TSQLDBStruct;
TSQLDBStruct = record
pVfs, pVdbe, pDfltColl, mutex: pointer;
DB0: ^TSQLDBOneStruct;
nDb: integer; // Number of backends currently in use
end;
{$A+}
// used to store all currently per-database encryption tables
TSQLCypher = record
Handle: THandle;
CypherBuf: RawByteString;
end;
TSQLCypherDynArray = array of TSQLCypher;
var
Cyphers: TSQLCypherDynArray;
CypherCount: integer;
Cypher: TDynArray;
function WinWrite(var F: TSQLFile; buf: PByte; buflen: integer; off: Int64): integer; {$ifndef USEFASTCALL}cdecl;{$endif}
// Write data from a buffer into a file. Return SQLITE_OK on success
// or some other error code on failure
var n, i: integer;
b: PByte;
offset: Int64Rec;
aSQLEncryptTable: pointer;
label err;
begin
//SynSQLite3Log.Add.Log(sllCustom2,'WinWrite % off=% len=%',[F.h,off,buflen]);
offset.Lo := Int64Rec(off).Lo;
offset.Hi := Int64Rec(off).Hi and $7fffffff; // offset must be positive (u64)
result := SetFilePointer(F.h,offset.Lo,@offset.Hi,FILE_BEGIN);
if result=-1 then begin
result := GetLastError;
if result<>NO_ERROR then begin
F.lastErrno := result;
result := SQLITE_FULL;
exit;
end;
end;
aSQLEncryptTable := SQLEncryptTable;
if Cyphers<>nil then
for i := 0 to CypherCount-1 do // (a bit) faster than Cypher.Find(F.h)
if Cyphers[i].Handle=F.h then begin
aSQLEncryptTable := Pointer(Cyphers[i].CypherBuf);
break;
end;
if (aSQLEncryptTable<>nil) and ((offset.Lo>=1024) or (offset.Hi<>0)) then begin
b := buf;
XorOffset(b,offset.Lo,buflen,aSQLEncryptTable); // crypt buf content after first page
end else
b := nil; // mark no encryption
n := buflen;
while n>0 do begin
if not WriteFile(F.h,buf^,n,cardinal(result),nil) then begin
err: F.lastErrno := GetLastError;
result := SQLITE_FULL;
if b<>nil then // restore buf content
XorOffset(b,offset.Lo,buflen,aSQLEncryptTable);
exit;
end;
if result=0 then break;
dec(n,result);
inc(buf,result);
end;
if n>result then
goto err;
result := SQLITE_OK;
if b<>nil then // restore buf content
XorOffset(b,offset.Lo,buflen,aSQLEncryptTable);
end;
const
SQLITE_IOERR_READ = $010A;
SQLITE_IOERR_SHORT_READ = $020A;
function SetFilePointerEx(hFile: THandle; liDistanceToMove: TLargeInteger;
const lpNewFilePointer: PLargeInteger; dwMoveMethod: DWORD): BOOL; stdcall;
external kernel32 name 'SetFilePointerEx'; // not defined with older Delphi
function WinRead(var F: TSQLFile; buf: PByte; buflen: integer; off: Int64): integer; {$ifndef USEFASTCALL}cdecl;{$endif}
// Read data from a file into a buffer. Return SQLITE_OK on success
// or some other error code on failure
var offset: Int64Rec;
aSQLEncryptTable: PByteArray;
i: integer;
begin
//SynSQLite3Log.Add.Log(sllCustom2,'WinRead % off=% len=%',[F.h,off,buflen]);
offset.Lo := Int64Rec(off).Lo;
offset.Hi := Int64Rec(off).Hi and $7fffffff; // offset must be positive (u64)
result := SetFilePointer(F.h,offset.Lo,@offset.Hi,FILE_BEGIN);
if result=-1 then begin
result := GetLastError;
if result<>NO_ERROR then begin
F.lastErrno := result;
result := SQLITE_FULL;
exit;
end;
end;
if not ReadFile(F.h,buf^,buflen,cardinal(result),nil) then begin
F.lastErrno := GetLastError;
result := SQLITE_IOERR_READ;
exit;
end;
aSQLEncryptTable := SQLEncryptTable;
if Cyphers<>nil then
for i := 0 to CypherCount-1 do // (a bit) faster than Cypher.Find(F.h)
if Cyphers[i].Handle=F.h then begin
aSQLEncryptTable := Pointer(Cyphers[i].CypherBuf);
break;
end;
if (aSQLEncryptTable<>nil) and ((offset.Lo>=1024) or (offset.Hi<>0)) then
XorOffset(buf,offset.Lo,result,aSQLEncryptTable); // uncrypt after first page
dec(buflen,result);
if buflen>0 then begin // remaining bytes are set to 0
inc(buf,result);
fillchar(buf^,buflen,0);
result := SQLITE_IOERR_SHORT_READ;
end else
result := SQLITE_OK;
end;
function IsSQLite3File(const FileName: TFileName): boolean;
var F: THandle;
Header: array[0..15] of AnsiChar;
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='SQLite format 3');
FileClose(F);
end;
end;
function IsSQLite3FileEncrypted(const FileName: TFileName): boolean;
var F: THandle;
Header: array[0..2047] of AnsiChar;
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='SQLite format 3') and not(Header[1024] in [#5,#10,#13]);
// B-tree leaf Type to be either 5 (interior) 10 (index) or 13 (table)
FileClose(F);
end;
end;
// ************ objects to access SQLite3 database engine
resourcestring
sErrorSQLite3NoDB = 'Invalid SQlite3 database handle (%d)';
{ 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;
if RowID<0 then
RowID := LastInsertRowID;
Lock('');
try
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 begin
Execute('ROLLBACK TRANSACTION;');
fTransactionActive := false;
end;
Execute('BEGIN '+TBTOKENS[aBehavior]+'TRANSACTION;');
fTransactionActive := true;
end;
procedure TSQLDataBase.Commit;
begin
if (Self<>nil) and fTransactionActive then begin
Execute('COMMIT TRANSACTION;');
fTransactionActive := false;
end;
end;
{ from WladiD about all 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; {$ifndef USEFASTCALL}cdecl;{$endif}
begin
if s1Len=0 then // see WladiD note above
s1 := nil;
if s2Len=0 then
s2 := nil;
result := CompareStringW(GetThreadLocale, 0, S1, S1len, S2, S2Len) - 2;
end;
function Utf16SQLCompNoCase(CollateParam: pointer; s1Len: integer; s1: pointer;
s2Len: integer; s2: pointer) : integer; {$ifndef USEFASTCALL}cdecl;{$endif}
begin
if s1Len=0 then // see WladiD note above
s1 := nil;
if s2Len=0 then
s2 := nil;
result := CompareStringW(GetThreadLocale, NORM_IGNORECASE, S1, S1len, S2, S2Len) - 2;
end;
function Utf8SQLCompNoCase(CollateParam: pointer; s1Len: integer; s1: pointer;
s2Len: integer; s2: pointer) : integer; {$ifndef USEFASTCALL}cdecl;{$endif}
begin
if (s1Len=0) and (s2Len=0) then // see WladiD note above
result := 0 else
result := UTF8ILComp(s1,s2,s1Len,s2Len); // properly handles individual s?Len=0
end;
function Utf8SQLDateTime(CollateParam: pointer; s1Len: integer; s1: pointer;
s2Len: integer; s2: pointer) : integer; {$ifndef USEFASTCALL}cdecl;{$endif}
var V1,V2: Int64; // faster than Iso8601ToDateTimePChar: uses integer math
begin
if s1Len=0 then // see WladiD note above
s1 := nil;
if s2Len=0 then
s2 := nil;
if s1=s2 then begin
result := 0;
exit;
end;
V1 := Iso8601ToSecondsPUTF8Char(s1,s1Len);
V2 := Iso8601ToSecondsPUTF8Char(s2,s2Len);
if (V1=0) or (V2=0) then // any invalid date -> compare as UTF-8 strings
result := UTF8ILComp(s1,s2,s1Len,s2Len) else
if V1<V2 then
result := -1 else
if V1=V2 then
result := 0 else
result := +1;
end;
procedure ErrorWrongNumberOfArgs(Context: TSQLite3FunctionContext);
begin
sqlite3_result_error(Context, 'wrong number of arguments');
end;
procedure InternalSoundex(Context: TSQLite3FunctionContext;
argc: integer; var argv: TSQLite3ValueArray); {$ifndef USEFASTCALL}cdecl;{$endif}
begin
if argc=1 then
sqlite3_result_int64(Context, SoundExUTF8(sqlite3_value_text(argv[0]))) else
ErrorWrongNumberOfArgs(Context);
end;
procedure InternalSoundexFr(Context: TSQLite3FunctionContext;
argc: integer; var argv: TSQLite3ValueArray); {$ifndef USEFASTCALL}cdecl;{$endif}
begin
if argc=1 then
sqlite3_result_int64(Context, SoundExUTF8(sqlite3_value_text(argv[0]),nil,sndxFrench)) else
ErrorWrongNumberOfArgs(Context);
end;
procedure InternalSoundexEs(Context: TSQLite3FunctionContext;
argc: integer; var argv: TSQLite3ValueArray); {$ifndef USEFASTCALL}cdecl;{$endif}
begin
if argc=1 then
sqlite3_result_int64(Context, SoundExUTF8(sqlite3_value_text(argv[0]),nil,sndxSpanish)) else
ErrorWrongNumberOfArgs(Context);
end;
procedure InternalMod(Context: TSQLite3FunctionContext;
argc: integer; var argv: TSQLite3ValueArray); {$ifndef USEFASTCALL}cdecl;{$endif}
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 InternalRank(Context: TSQLite3FunctionContext;
argc: integer; var argv: TSQLite3ValueArray); {$ifndef USEFASTCALL}cdecl;{$endif}
// 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]);
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); {$ifndef USEFASTCALL}cdecl;{$endif}
begin
Freemem(p);
end;
procedure sqlite3InternalFreeObject(p: pointer); {$ifndef USEFASTCALL}cdecl;{$endif}
begin
TObject(p).Free;
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); {$ifndef USEFASTCALL}cdecl;{$endif}
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);
Move(sep^,result[resultlen],seplen);
inc(resultlen,seplen);
end;
Move(txt^,result[resultlen],txtlen+1);
inc(resultlen,txtlen);
end else
ErrorWrongNumberOfArgs(Context);
end;
procedure InternalConcatFinal(Context: TSQLite3FunctionContext); {$ifndef USEFASTCALL}cdecl;{$endif}
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); {$ifndef USEFASTCALL}cdecl;{$endif}
var Blob: pointer;
PI: PIntegerArray;
Count: integer;
begin // SQL function: IntegerDynArrayContains(BlobField,10) returning a boolean
if argc<>2 then begin
ErrorWrongNumberOfArgs(Context);
exit; // two parameters expected
end;
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); {$ifndef USEFASTCALL}cdecl;{$endif}
var Blob: pointer;
Count, ElemSize, i: integer;
V: Int64;
begin // Byte/Word/Cardinal/Int64/CurrencyDynArrayContains(BlobField,I64)
// for currency, expect I64 value = aCurrency*10000 = PInt64(@aCurrency)^
if argc<>2 then begin
ErrorWrongNumberOfArgs(Context);
exit; // two parameters expected
end;
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);
if Blob<>nil then begin
V := sqlite3_value_int64(argv[1]);
sqlite3_result_int64(Context,Int64(true));
case ElemSize of
1: for i := 0 to Count-1 do if PByteArray(Blob)^[i]=byte(V) then exit;
2: for i := 0 to Count-1 do if PWordArray(Blob)^[i]=word(V) then exit;
4: if IntegerScanExists(Blob,Count,cardinal(V)) then exit;
8: for i := 0 to Count-1 do if PInt64Array(Blob)^[i]=V then exit;
end;
end;
end;
sqlite3_result_int64(Context,Int64(false));
end;
procedure InternalRawUTF8DynArray(Context: TSQLite3FunctionContext;
argc: integer; var argv: TSQLite3ValueArray); {$ifndef USEFASTCALL}cdecl;{$endif}
var Blob: pointer;
Value: PUTF8Char;
begin // SQL function: RawUTF8DynArrayContainsCase/NoCase(BlobField,'Text') returning a boolean
if argc<>2 then begin
ErrorWrongNumberOfArgs(Context);
exit; // two parameters expected
end;
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;
constructor TSQLDataBase.Create(const aFileName: TFileName; const aPassword: RawUTF8='');
var result: integer;
begin
{$ifdef WITHLOG}
fLog := SynSQLite3Log.Family.SynLog; // leave fLog=nil if no Logging wanted
{$endif}
InitializeCriticalSection(fLock);
fFileName := aFileName;
if (aPassword<>'') and (aFileName<>':memory:') and (aFileName<>'') then begin
SetLength(fCypherBlock,SQLEncryptTableSize);
CreateSQLEncryptTableBytes(aPassword,pointer(fCypherBlock));
end;
fSQLFunctions := TObjectList.Create;
result := DBOpen;
if result<>SQLITE_OK then
raise ESQLite3Exception.Create(fDB,result);
end;
destructor TSQLDataBase.Destroy;
{$ifndef INCLUDE_FTS3}
var S: TSQLite3Statement;
{$endif}
begin
if DB<>0 then
try
Rollback; // any unfinished transaction is rollbacked
finally
{$ifndef INCLUDE_FTS3}
{ 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 }
{$endif INCLUDE_FTS3}
DBClose;
end;
DeleteCriticalSection(fLock);
fCache.Free;
fSQLFunctions.Free;
inherited;
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}
Log := SynSQLite3Log.Enter(self);
Log.Log(sllSQL,aSQL);
{$endif}
Lock('ALTER'); // 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;
{$ifdef WITHLOG}
Log: ISynLog;
{$endif}
begin
if self=nil then
exit; // avoid GPF in case of call from a static-only server
{$ifdef WITHLOG}
Log := SynSQLite3Log.Enter(self);
Log.Log(sllSQL,aSQL);
{$endif}
Lock(aSQL); // run one statement -> we can trust isSelect()
try
R.Execute(DB,aSQL);
finally
UnLock;
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}
Log := SynSQLite3Log.Enter(self);
Log.Log(sllSQL,aSQL);
{$endif}
Lock(aSQL);
try
result := R.Execute(DB,aSQL,ID);
finally
UnLock;
end;
end;
procedure TSQLDataBase.Execute(const aSQL: RawUTF8; out ID: Int64);
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}
Log := SynSQLite3Log.Enter(self);
Log.Log(sllSQL,aSQL);
{$endif}
Lock(aSQL);
try
R.Execute(DB,aSQL,ID);
finally
UnLock;
end;
end;
procedure TSQLDataBase.Execute(const aSQL: RawUTF8; out ID: 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}
Log := SynSQLite3Log.Enter(self);
Log.Log(sllSQL,aSQL);
{$endif}
Lock(aSQL);
try
R.Execute(DB,aSQL,ID);
finally
UnLock;
end;
end;
function TSQLDataBase.ExecuteNoException(const aSQL: RawUTF8): boolean;
begin
try
Execute(aSQL);
result := true;
except
result := false;
end;
end;
procedure TSQLDataBase.ExecuteNoException(const aSQL: RawUTF8; out ID: Int64);
begin
try
Execute(aSQL,ID);
except
ID := 0;
end;
end;
procedure TSQLDataBase.ExecuteNoException(const aSQL: RawUTF8; out ID: RawUTF8);
begin
try
Execute(aSQL,ID);
except
ID := '';
end;
end;
function TSQLDataBase.ExecuteJSON(const aSQL: RawUTF8; Expand: boolean=false;
aResultCount: PPtrInt=nil): RawUTF8;
var R: TSQLRequest;
Count: PtrInt;
begin
if self=nil then
exit; // avoid GPF in case of call from a static-only server
{$ifdef WITHLOG}
SynSQLite3Log.Enter(self);
{$endif}
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);
end;
end;
function TSQLDataBase.Execute(const aSQL: RawUTF8; var Values: TRawUTF8DynArray): 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}
Log := SynSQLite3Log.Enter(self);
Log.Log(sllSQL,aSQL);
{$endif}
Lock(aSQL);
try
result := R.Execute(DB,aSQL,Values);
finally
UnLock;
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);
{$ifdef WITHLOG}
{$ifdef DELPHI5OROLDER}
fLog.Log(sllDB,'LastInsertRowID='+Int64ToUTF8(result),self);
{$else}
fLog.Log(sllDB,'LastInsertRowID=%',result,self);
{$endif}
{$endif}
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);
{$ifdef WITHLOG}
{$ifdef DELPHI5OROLDER}
fLog.Log(sllDB,'LastChangeCount='+Int64ToUTF8(result),self);
{$else}
fLog.Log(sllDB,'LastChangeCount=%',result,self);
{$endif}
{$endif}
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));
{$ifdef WITHLOG}
{$ifndef DELPHI5OROLDER}
fLog.Log(sllDebug,'TableNames',TypeInfo(TRawUTF8DynArray),Names,self);
{$endif}
{$endif}
end;
procedure TSQLDataBase.GetFieldNames(var Names: TRawUTF8DynArray; const TableName: RawUTF8);
var R: TSQLRequest;
n: integer;
begin
if self=nil 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;
repeat
if R.Step<>SQLITE_ROW then break;
if n=length(Names) then
SetLength(Names,n+MAX_SQLFIELDS);
Names[n] := sqlite3_column_text(R.Request,1); // cid,name,type,notnull,dflt_value,pk
inc(n);
until false;
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 else
FreeAndNil(fCache);
end;
procedure TSQLDataBase.Lock(const aSQL: RawUTF8);
begin
if self=nil then
exit; // avoid GPF in case of call from a static-only server
EnterCriticalSection(fLock); // on non-concurent calls, this API is very fast
if not isSelect(pointer(aSQL)) then
// UPDATE, INSERT or any non SELECT
CacheFlush;
end;
procedure TSQLDataBase.UnLock;
begin
if self<>nil then
LeaveCriticalSection(fLock); // on non-concurent calls, this API is very fast
end;
function TSQLDataBase.LockJSON(const aSQL: RawUTF8; aResultCount: PPtrInt): RawUTF8;
begin
if self=nil then
exit; // avoid GPF in case of call from a static-only server
EnterCriticalSection(fLock); // cache access is also protected by fLock
if isSelect(pointer(aSQL)) then begin
result := fCache.Find(aSQL,aResultCount); // try to get JSON result from cache
if result<>'' then begin
{$ifdef WITHLOG}
fLog.Log(sllSQL,aSQL,self);
fLog.Log(sllCache,'from cache',self);
fLog.Log(sllResult,result,self);
{$endif}
LeaveCriticalSection(fLock); // found in cache -> leave critical section
end
{$ifdef WITHLOG}
else
fLog.Log(sllCache,'not in cache',self);
{$endif}
end else begin
// UPDATE, INSERT or any non SELECT statement
CacheFlush;
result := '';
end;
end;
procedure TSQLDataBase.UnLockJSON(const aJSONResult: RawUTF8; aResultCount: PtrInt);
begin
if self=nil then
exit; // avoid GPF in case of call from a static-only server
{$ifdef WITHLOG}
fLog.Log(sllResult,aJSONResult,self);
{$endif}
fCache.Add(aJSONResult,aResultCount); // if a reset was made just before, Add() does nothing
UnLock; // leave fLock
end;
function TSQLDataBase.Backup(const BackupFileName: TFileName): boolean;
begin
fLog.Enter(self);
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;');
Lock(''); // lock read-only the data base
try
try
fLog.Log(sllTrace,'close',self);
DBClose;
fLog.Log(sllTrace,'copy file',self);
result := CopyFile(pointer(fFileName),pointer(BackupFileName),false);
finally
fLog.Log(sllTrace,'reopen',self);
DBOpen;
end;
finally
UnLock;
end;
end;
procedure TSQLDataBase.DBClose;
var i: integer;
begin
if (self=nil) or (fDB=0) then
exit;
fLog.Enter;
if Cyphers<>nil then
i := Cypher.Find(PSQLDBStruct(fDB)^.DB0^.Btree^.pBt^.pPager^.fd^.h) else
i := -1;
sqlite3_close(fDB);
if i>=0 then begin
Cypher.Delete(i); // do it after file closing
{$ifdef WITHLOG}
fLog.Log(sllDB,'end of encryption');
{$endif}
end;
fDB := 0;
end;
function TSQLDataBase.DBOpen: integer;
var utf8: RawUTF8;
i: integer;
Cyph: TSQLCypher;
begin
fLog.Enter;
utf8 := StringToUTF8(fFileName);
result := sqlite3_open(pointer(utf8),fDB);
if result<>SQLITE_OK then begin
{$ifdef WITHLOG}
fLog.Log(sllError,'open("'+utf8+'") failed',self);
{$endif}
sqlite3_close(fDB); // should always be closed, even on failure
fDB := 0;
exit;
end;
Cyph.Handle := PSQLDBStruct(fDB)^.DB0^.Btree^.pBt^.pPager^.fd^.h;
//fLog.Log(sllDB,'open("%") with handle=%',[utf8,Cyph.Handle],self);
if fCypherBlock<>'' then begin
if Cyphers=nil then begin
Cypher.Init(TypeInfo(TSQLCypherDynArray),Cyphers,@CypherCount);
Cypher.Compare := SortDynArrayInteger;
end;
i := Cypher.Find(Cyph.Handle);
if i>=0 then begin
{$ifdef WITHLOG}
fLog.Log(sllError,'Handle reused for '+utf8);
{$endif}
end else begin
Cyph.CypherBuf := fCypherBlock;
Cypher.Add(Cyph);
{$ifdef WITHLOG}
fLog.Log(sllDB,'encryption enabled');
{$endif}
end;
end;
// the SQLite3 standard NOCASE collation is used for AnsiString and is very fast
// our custom fast UTF-8 case insensitive compare, using NormToUpper[] for all 8 bits values
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 alwyas 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);
// register the MOD() user function, similar to the standard % operator
sqlite3_create_function_v2(DB,'MOD',2,SQLITE_ANY,nil,InternalMod,nil,nil,nil);
// some user functions
sqlite3_create_function_v2(DB,'SOUNDEX',1,SQLITE_UTF8,nil,InternalSoundex,nil,nil,nil);
sqlite3_create_function_v2(DB,'SOUNDEXFR',1,SQLITE_UTF8,nil,InternalSoundexFr,nil,nil,nil);
sqlite3_create_function_v2(DB,'SOUNDEXES',1,SQLITE_UTF8,nil,InternalSoundexEs,nil,nil,nil);
// rank() function as proposed in http://www.sqlite.org/fts3.html#appendix_a
sqlite3_create_function_v2(DB,'RANK',-1,SQLITE_ANY,nil,InternalRank,nil,nil,nil);
// CONCAT() function to process fast string concatenation
sqlite3_create_function_v2(DB,'CONCAT',2,SQLITE_UTF8,nil,nil,
InternalConcatStep,InternalConcatFinal,nil);
// functions to handle some standard dynamic array BLOB content in SQL
// IntegerDynArrayContains(BlobField,10) returning a boolean
sqlite3_create_function_v2(DB,'INTEGERDYNARRAYCONTAINS',2,SQLITE_ANY,nil,
InternalIntegerDynArray,nil,nil,nil);
// Byte/Word/Cardinal/Int64/CurrencyDynArrayContains(BlobField,I64)
sqlite3_create_function_v2(DB,'BYTEDYNARRAYCONTAINS',2,SQLITE_ANY,
TypeInfo(TByteDynArray),InternalSimpleInt64DynArray,nil,nil,nil);
sqlite3_create_function_v2(DB,'WORDDYNARRAYCONTAINS',2,SQLITE_ANY,
TypeInfo(TWordDynArray),InternalSimpleInt64DynArray,nil,nil,nil);
sqlite3_create_function_v2(DB,'CARDINALDYNARRAYCONTAINS',2,SQLITE_ANY,
TypeInfo(TCardinalDynArray),InternalSimpleInt64DynArray,nil,nil,nil);
sqlite3_create_function_v2(DB,'INT64DYNARRAYCONTAINS',2,SQLITE_ANY,
TypeInfo(TInt64DynArray),InternalSimpleInt64DynArray,nil,nil,nil);
sqlite3_create_function_v2(DB,'CURRENCYDYNARRAYCONTAINS',2,SQLITE_ANY,
TypeInfo(TInt64DynArray),InternalSimpleInt64DynArray,nil,nil,nil);
// RawUTF8DynArrayContainsCase/NoCase(BlobField,'Text') returning a boolean
sqlite3_create_function_v2(DB,'RAWUTF8DYNARRAYCONTAINSCASE',2,SQLITE_ANY,nil,
InternalRawUTF8DynArray,nil,nil,nil);
sqlite3_create_function_v2(DB,'RAWUTF8DYNARRAYCONTAINSNOCASE',2,SQLITE_ANY,
@UTF8ILComp,InternalRawUTF8DynArray,nil,nil,nil);
// reallocate all TSQLDataBaseSQLFunction for re-Open (TSQLRestServerDB.Backup)
for i := 0 to fSQLFunctions.Count-1 do
TSQLDataBaseSQLFunction(fSQLFunctions.List[i]).CreateFunction(DB);
end;
function TSQLDataBase.GetUserVersion: cardinal;
var tmp: Int64;
begin
ExecuteNoException('PRAGMA user_version',tmp);
result := tmp;
end;
procedure TSQLDataBase.SetUserVersion(const Value: cardinal);
begin
ExecuteNoException('PRAGMA user_version='+Int32ToUTF8(Value));
end;
procedure TSQLDataBase.SetSynchronous(const Value: TSQLSynchronousMode);
begin
ExecuteNoException('PRAGMA synchronous='+Int32ToUTF8(ord(Value)));
end;
function TSQLDataBase.GetSynchronous: TSQLSynchronousMode;
var tmp: Int64;
begin
ExecuteNoException('PRAGMA synchronous ',tmp);
result := TSQLSynchronousMode(tmp);
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;
var tmp: RawUTF8;
begin
ExecuteNoException('PRAGMA journal_mode',tmp);
result := IdemPropNameU(tmp,'wal');
end;
procedure TSQLDataBase.SetBusyTimeout(const ms: Integer);
begin
if self=nil then
exit;
{$ifdef WITHLOG}
{$ifdef DELPHI5OROLDER}
fLog.Log(sllDB,'SetBusyTimeout='+Int32ToUTF8(ms),self);
{$else}
fLog.Log(sllDB,'SetBusyTimeout=%',ms,self);
{$endif}
{$endif}
sqlite3_busy_timeout(DB,ms);
fBusyTimeout := ms;
end;
procedure TSQLDataBase.CacheFlush;
begin
if self=nil then
exit;
if InternalState<>nil then
inc(InternalState^);
if fCache.Reset then
{$ifdef WITHLOG}
fLog.Log(sllCache,'cache flushed',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}
fLog.Log(sllDB,'RegisterSQLFunction '+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));
end;
procedure TSQLRequest.Bind(Param: Integer; Value: double);
begin
sqlite3_check(RequestDB,sqlite3_bind_double(Request,Param,Value));
end;
procedure TSQLRequest.Bind(Param: Integer; const Value: RawUTF8);
begin
// 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 -> so length(Value) is needed here
sqlite3_check(RequestDB,sqlite3_bind_text(Request,Param,pointer(Value),
length(Value),SQLITE_TRANSIENT)); // make private copy of the data
end;
procedure TSQLRequest.Bind(Param: Integer; Data: pointer; Size: integer);
begin
sqlite3_check(RequestDB,sqlite3_bind_blob(Request,Param,Data,Size,
SQLITE_TRANSIENT)); // make private copy of the data
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;
sqlite3_finalize(Request);
fRequest := 0;
fFieldCount := 0;
end;
procedure TSQLRequest.ExecuteAll;
begin
if RequestDB=0 then
raise ESQLite3Exception.Create(0,SQLITE_CANTOPEN);
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);
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 normaly 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 normaly in Execute
end;
end;
function TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var ID: TInt64DynArray): integer;
var LID, Res: integer;
begin
result := 0;
LID := length(ID);
try
Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
if FieldCount>0 then
repeat
res := Step;
if res=SQLITE_ROW then begin
if result>=LID then begin
inc(LID,256);
SetLength(ID,LID);
end;
ID[result] := sqlite3_column_int64(Request,0); // get first column value
inc(result);
end;
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 LValues, Res: integer;
begin
result := 0;
LValues := length(Values);
try
Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
if FieldCount>0 then
repeat
res := Step;
if res=SQLITE_ROW then begin
if result>=LValues then begin
if LValues<256 then
inc(LValues,16) else
inc(LValues,256);
SetLength(Values,LValues);
end;
Values[result] := sqlite3_column_text(Request,0); // get first column value
inc(result);
end;
until res=SQLITE_DONE;
finally
Close; // always release statement
end;
end;
function TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; JSON: TStream;
Expand: boolean=false): PtrInt;
// expand=true: [ {"col1":val11,"col2":"val12"},{"col1":val21,... ]
// expand=false: { "FieldCount":2,"Values":["col1","col2",val11,"val12",val21,..] }
var i: integer;
W: TJSONWriter;
begin
result := 0;
W := TJSONWriter.Create(JSON,Expand,false);
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
Expand := false; // {"FieldCount":2,"Values":["col1","col2"]}
W.Expand := false;
W.CancelAll;
for i := 0 to FieldCount-1 do
W.ColNames[i] := sqlite3_column_name(Request,i);
W.AddColumns;
end;
W.CancelLastComma; // cancel last ','
W.Add(']');
if not Expand then
W.Add('}');
W.Add(#10);
W.Flush;
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);
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);
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);
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);
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);
P := sqlite3_column_name(Request,Col);
SetString(result,P,SynCommons.StrLen(P));
end;
function TSQLRequest.FieldIndex(const aColumnName: RawUTF8): integer;
begin
if Request=0 then
raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE);
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);
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);
result := sqlite3_column_type(Request,Col);
end;
function TSQLRequest.FieldUTF8(Col: integer): RawUTF8;
var P: PUTF8Char;
begin
if cardinal(Col)>=cardinal(FieldCount) then
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
P := pointer(sqlite3_column_text(Request,Col));
SetString(result,P,SynCommons.StrLen(P));
end;
function TSQLRequest.FieldValue(Col: integer): TSQLite3Value;
begin
if cardinal(Col)>=cardinal(FieldCount) then
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
result := sqlite3_column_value(Request,Col);
end;
function TSQLRequest.FieldW(Col: integer): RawUnicode;
begin
if cardinal(Col)>=cardinal(FieldCount) then
raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
result := Utf8DecodeToRawUnicode(sqlite3_column_text(Request,Col),0);
end;
function TSQLRequest.Prepare(DB: TSQLite3DB; const SQL: RawUTF8): integer;
begin
fDB := DB;
fRequest := 0;
if DB=0 then
raise ESQLite3Exception.Create(DB,SQLITE_CANTOPEN);
result := sqlite3_prepare_v2(RequestDB, pointer(SQL), length(SQL)+1, fRequest, fNextSQL);
while (result=SQLITE_OK) and (Request=0) do // comment or white-space
result := sqlite3_prepare_v2(RequestDB, fNextSQL, -1, fRequest, fNextSQL);
fFieldCount := sqlite3_column_count(fRequest);
sqlite3_check(RequestDB,result);
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);
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(RequestDB,SQLITE_MISUSE);
result := sqlite3_reset(Request); // no check here since it was PREVIOUS state
end;
function TSQLRequest.Step: integer;
begin
if Request=0 then
raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE);
result := sqlite3_check(RequestDB,sqlite3_step(Request));
end;
function TSQLRequest.GetReadOnly: Boolean;
begin
if Request=0 then
raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE);
result := sqlite3_stmt_readonly(Request);
end;
procedure TSQLRequest.FieldsToJSON(WR: TJSONWriter);
var i: integer;
begin
if Request=0 then
raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE);
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:
WR.WrBase64(sqlite3_column_blob(Request,i),
sqlite3_column_bytes(Request,i),true); // withMagic=true
SQLITE_NULL:
WR.AddNoJSONEscape(PAnsiChar('null'),4); // returned also for ""
SQLITE_INTEGER:
WR.Add(sqlite3_column_int64(Request,i));
SQLITE_FLOAT:
WR.Add(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);
begin
if aDB=0 then
CreateFmt(sErrorSQLite3NoDB,[aErrorCode]) else
Create(string(sqlite3_errmsg(aDB)),aErrorCode);
DB := aDB;
end;
constructor ESQLite3Exception.Create(const aMessage: string; aErrorCode: integer);
begin
ErrorCode := aErrorCode;
Create(aMessage);
end;
function sqlite3_check(DB: TSQLite3DB; aResult: integer): integer;
begin
if (DB=0) or (aResult in [SQLITE_ERROR..SQLITE_ROW-1]) then // possible error codes
raise ESQLite3Exception.Create(DB,aResult);
result := 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));
fSize := sqlite3_blob_bytes(fBlob);
end;
destructor TSQLBlobStream.Destroy;
begin
sqlite3_blob_close(fBlob);
inherited;
end;
function TSQLBlobStream.Read(var Buffer; Count: Integer): 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: Integer; 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;
function TSQLBlobStream.Write(const Buffer; Count: Integer): 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 begin
result := sqlite3_create_function_v2(DB,pointer(fSQLName),
FunctionParametersCount,SQLITE_ANY,self,fInternalFunction,nil,nil,nil);
{$ifdef WITHLOG}
if result<>SQLITE_OK then
SynSQLite3Log.Add.Log(sllError,'register SQL function failed: '+FunctionName,self);
{$endif}
end else
result := SQLITE_ERROR;
end;
{ TSQLDataBaseSQLFunctionDynArray }
procedure InternalSQLFunctionDynArrayBlob(Context: TSQLite3FunctionContext;
argc: integer; var argv: TSQLite3ValueArray); {$ifndef USEFASTCALL}cdecl;{$endif}
var DynArray, Elem: pointer;
Func: TSQLDataBaseSQLFunctionDynArray;
begin
if argc<>2 then begin
ErrorWrongNumberOfArgs(Context);
exit; // two parameters expected
end;
DynArray := sqlite3_value_blob(argv[0]);
Elem := sqlite3_value_blob(argv[1]);
Func := sqlite3_user_data(Context);
if (DynArray<>nil) and (Elem<>nil) and (Func<>nil) then
with Func.fDummyDynArray do
try
LoadFrom(DynArray); // temporary allocate all dynamic array content
try
if ElemLoadFind(Elem)<0 then
DynArray := nil;
finally
Clear; // release temporary array content in fDummyDynArrayValue
end;
except
on Exception do begin
sqlite3_result_error(Context,'Invalid BLOB content');
exit;
end;
end else
DynArray := nil;
sqlite3_result_int64(Context,Int64(DynArray<>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.Init(TypeInfo(TSQLStatementCacheDynArray),Cache,nil,nil,nil,@Count);
DB := aDB;
end;
function TSQLStatementCached.Prepare(const GenericSQL: RaWUTF8): PSQLRequest;
var added: boolean;
begin
with Cache[Caches.FindHashedForAdding(GenericSQL,added)] do begin
if added then begin
StatementSQL := GenericSQL;
Statement.Prepare(DB,GenericSQL);
end else begin
Statement.BindReset;
Statement.Reset;
end;
result := @Statement;
end;
end;
procedure TSQLStatementCached.ReleaseAllDBStatements;
var i: integer;
begin
for i := 0 to Count-1 do
Cache[i].Statement.Close; // close prepared statement
Caches.Clear;
Caches.ReHash; // need to refresh all hashs
end;
initialization
sqlite3_initialize; // so sqlite3.c is compiled with SQLITE_OMIT_AUTOINIT defined
finalization
sqlite3_shutdown;
end.