aboutsummaryrefslogtreecommitdiffstats
path: root/Game
diff options
context:
space:
mode:
authortobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-08-07 11:37:36 +0000
committertobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-08-07 11:37:36 +0000
commit51995630561a819b80bbf08039bd5d39ce0de564 (patch)
tree8f60cd7f749625f22b698313382599e7d5bb2f99 /Game
parent57247ddc701c856e3bd0811566405ab4ac69e9ff (diff)
downloadusdx-51995630561a819b80bbf08039bd5d39ce0de564.tar.gz
usdx-51995630561a819b80bbf08039bd5d39ce0de564.tar.xz
usdx-51995630561a819b80bbf08039bd5d39ce0de564.zip
Update of SQLiteTable3. The file was additionally patched to support Format()-style bindings (BindData) and FieldAsBlobPtr.
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1225 b956fd51-792f-4845-bead-9b4dfca2ff2c
Diffstat (limited to 'Game')
-rw-r--r--Game/Code/lib/SQLite/SQLite3.pas171
-rw-r--r--Game/Code/lib/SQLite/SQLiteTable3.pas800
-rw-r--r--Game/Code/lib/SQLite/example/uTestSqlite.dfm4
-rw-r--r--Game/Code/lib/SQLite/readme.txt15
4 files changed, 865 insertions, 125 deletions
diff --git a/Game/Code/lib/SQLite/SQLite3.pas b/Game/Code/lib/SQLite/SQLite3.pas
index 397c086f..c37bac01 100644
--- a/Game/Code/lib/SQLite/SQLite3.pas
+++ b/Game/Code/lib/SQLite/SQLite3.pas
@@ -8,12 +8,18 @@ unit SQLite3;
which was based on SQLite.pas by Ben Hochstrasser (bhoc@surfeu.ch)
}
-interface
+{$IFDEF FPC}
+ {$MODE DELPHI}
+ {$H+} (* use AnsiString *)
+ {$PACKENUM 4} (* use 4-byte enums *)
+ {$PACKRECORDS C} (* C/C++-compatible record packing *)
+{$ELSE}
+ {$MINENUMSIZE 4} (* use 4-byte enums *)
+{$ENDIF}
-{$I switches.inc}
+interface
const
-
{$IFDEF MSWINDOWS}
SQLiteDLL = 'sqlite3.dll';
{$ENDIF}
@@ -27,50 +33,75 @@ const
// Return values for sqlite3_exec() and sqlite3_step()
- SQLITE_OK = 0; // Successful result
- SQLITE_ERROR = 1; // SQL error or missing database
- SQLITE_INTERNAL = 2; // An internal logic error in SQLite
- SQLITE_PERM = 3; // Access permission denied
- SQLITE_ABORT = 4; // Callback routine requested an abort
- SQLITE_BUSY = 5; // The database file is locked
- SQLITE_LOCKED = 6; // A table in the database is locked
- SQLITE_NOMEM = 7; // A malloc() failed
- SQLITE_READONLY = 8; // Attempt to write a readonly database
- SQLITE_INTERRUPT = 9; // Operation terminated by sqlite3_interrupt()
- SQLITE_IOERR = 10; // Some kind of disk I/O error occurred
- SQLITE_CORRUPT = 11; // The database disk image is malformed
- SQLITE_NOTFOUND = 12; // (Internal Only) Table or record not found
- SQLITE_FULL = 13; // Insertion failed because database is full
- SQLITE_CANTOPEN = 14; // Unable to open the database file
- SQLITE_PROTOCOL = 15; // Database lock protocol error
- SQLITE_EMPTY = 16; // Database is empty
- SQLITE_SCHEMA = 17; // The database schema changed
- SQLITE_TOOBIG = 18; // Too much data for one row of a table
- SQLITE_CONSTRAINT = 19; // Abort due to contraint violation
- SQLITE_MISMATCH = 20; // Data type mismatch
- SQLITE_MISUSE = 21; // Library used incorrectly
- SQLITE_NOLFS = 22; // Uses OS features not supported on host
- SQLITE_AUTH = 23; // Authorization denied
- SQLITE_FORMAT = 24; // Auxiliary database format error
- SQLITE_RANGE = 25; // 2nd parameter to sqlite3_bind out of range
- SQLITE_NOTADB = 26; // File opened that is not a database file
- SQLITE_ROW = 100; // sqlite3_step() has another row ready
- SQLITE_DONE = 101; // sqlite3_step() has finished executing
+const
+ SQLITE_OK = 0; // Successful result
+ (* beginning-of-error-codes *)
+ SQLITE_ERROR = 1; // SQL error or missing database
+ SQLITE_INTERNAL = 2; // An internal logic error in SQLite
+ SQLITE_PERM = 3; // Access permission denied
+ SQLITE_ABORT = 4; // Callback routine requested an abort
+ SQLITE_BUSY = 5; // The database file is locked
+ SQLITE_LOCKED = 6; // A table in the database is locked
+ SQLITE_NOMEM = 7; // A malloc() failed
+ SQLITE_READONLY = 8; // Attempt to write a readonly database
+ SQLITE_INTERRUPT = 9; // Operation terminated by sqlite3_interrupt()
+ SQLITE_IOERR = 10; // Some kind of disk I/O error occurred
+ SQLITE_CORRUPT = 11; // The database disk image is malformed
+ SQLITE_NOTFOUND = 12; // (Internal Only) Table or record not found
+ SQLITE_FULL = 13; // Insertion failed because database is full
+ SQLITE_CANTOPEN = 14; // Unable to open the database file
+ SQLITE_PROTOCOL = 15; // Database lock protocol error
+ SQLITE_EMPTY = 16; // Database is empty
+ SQLITE_SCHEMA = 17; // The database schema changed
+ SQLITE_TOOBIG = 18; // Too much data for one row of a table
+ SQLITE_CONSTRAINT = 19; // Abort due to contraint violation
+ SQLITE_MISMATCH = 20; // Data type mismatch
+ SQLITE_MISUSE = 21; // Library used incorrectly
+ SQLITE_NOLFS = 22; // Uses OS features not supported on host
+ SQLITE_AUTH = 23; // Authorization denied
+ SQLITE_FORMAT = 24; // Auxiliary database format error
+ SQLITE_RANGE = 25; // 2nd parameter to sqlite3_bind out of range
+ SQLITE_NOTADB = 26; // File opened that is not a database file
+ SQLITE_ROW = 100; // sqlite3_step() has another row ready
+ SQLITE_DONE = 101; // sqlite3_step() has finished executing
SQLITE_INTEGER = 1;
- SQLITE_FLOAT = 2;
- SQLITE_TEXT = 3;
- SQLITE_BLOB = 4;
- SQLITE_NULL = 5;
+ SQLITE_FLOAT = 2;
+ SQLITE_TEXT = 3;
+ SQLITE_BLOB = 4;
+ SQLITE_NULL = 5;
+
+ SQLITE_UTF8 = 1;
+ SQLITE_UTF16 = 2;
+ SQLITE_UTF16BE = 3;
+ SQLITE_UTF16LE = 4;
+ SQLITE_ANY = 5;
+
+ SQLITE_STATIC {: TSQLite3Destructor} = Pointer(0);
+ SQLITE_TRANSIENT {: TSQLite3Destructor} = Pointer(-1);
type
TSQLiteDB = Pointer;
TSQLiteResult = ^PChar;
TSQLiteStmt = Pointer;
-function SQLite3_Open(dbname: PChar; var db: TSqliteDB): integer; cdecl; external SQLiteDLL name 'sqlite3_open';
+type
+ PPCharArray = ^TPCharArray;
+ TPCharArray = array[0 .. (MaxInt div SizeOf(PChar))-1] of PChar;
+
+type
+ TSQLiteExecCallback = function(UserData: Pointer; NumCols: integer; ColValues:
+ PPCharArray; ColNames: PPCharArray): integer; cdecl;
+ TSQLiteBusyHandlerCallback = function(UserData: Pointer; P2: integer): integer; cdecl;
+
+ //function prototype for define own collate
+ TCollateXCompare = function(UserData: pointer; Buf1Len: integer; Buf1: pointer;
+ Buf2Len: integer; Buf2: pointer): integer; cdecl;
+
+
+function SQLite3_Open(filename: PChar; var db: TSQLiteDB): integer; cdecl; external SQLiteDLL name 'sqlite3_open';
function SQLite3_Close(db: TSQLiteDB): integer; cdecl; external SQLiteDLL name 'sqlite3_close';
-function SQLite3_Exec(db: TSQLiteDB; SQLStatement: PChar; CallbackPtr: Pointer; Sender: TObject; var ErrMsg: PChar): integer; cdecl; external SQLiteDLL name 'sqlite3_exec';
+function SQLite3_Exec(db: TSQLiteDB; SQLStatement: PChar; CallbackPtr: TSQLiteExecCallback; UserData: Pointer; var ErrMsg: PChar): integer; cdecl; external SQLiteDLL name 'sqlite3_exec';
function SQLite3_Version(): PChar; cdecl; external SQLiteDLL name 'sqlite3_libversion';
function SQLite3_ErrMsg(db: TSQLiteDB): PChar; cdecl; external SQLiteDLL name 'sqlite3_errmsg';
function SQLite3_ErrCode(db: TSQLiteDB): integer; cdecl; external SQLiteDLL name 'sqlite3_errcode';
@@ -80,54 +111,78 @@ procedure SQLite3_FreeTable(Table: TSQLiteResult); cdecl; external SQLiteDLL nam
function SQLite3_Complete(P: PChar): boolean; cdecl; external SQLiteDLL name 'sqlite3_complete';
function SQLite3_LastInsertRowID(db: TSQLiteDB): int64; cdecl; external SQLiteDLL name 'sqlite3_last_insert_rowid';
procedure SQLite3_Interrupt(db: TSQLiteDB); cdecl; external SQLiteDLL name 'sqlite3_interrupt';
-procedure SQLite3_BusyHandler(db: TSQLiteDB; CallbackPtr: Pointer; Sender: TObject); cdecl; external SQLiteDLL name 'sqlite3_busy_handler';
+procedure SQLite3_BusyHandler(db: TSQLiteDB; CallbackPtr: TSQLiteBusyHandlerCallback; UserData: Pointer); cdecl; external SQLiteDLL name 'sqlite3_busy_handler';
procedure SQLite3_BusyTimeout(db: TSQLiteDB; TimeOut: integer); cdecl; external SQLiteDLL name 'sqlite3_busy_timeout';
function SQLite3_Changes(db: TSQLiteDB): integer; cdecl; external SQLiteDLL name 'sqlite3_changes';
function SQLite3_TotalChanges(db: TSQLiteDB): integer; cdecl; external SQLiteDLL name 'sqlite3_total_changes';
function SQLite3_Prepare(db: TSQLiteDB; SQLStatement: PChar; nBytes: integer; var hStmt: TSqliteStmt; var pzTail: PChar): integer; cdecl; external SQLiteDLL name 'sqlite3_prepare';
+function SQLite3_Prepare_v2(db: TSQLiteDB; SQLStatement: PChar; nBytes: integer; var hStmt: TSqliteStmt; var pzTail: PChar): integer; cdecl; external SQLiteDLL name 'sqlite3_prepare_v2';
function SQLite3_ColumnCount(hStmt: TSqliteStmt): integer; cdecl; external SQLiteDLL name 'sqlite3_column_count';
-function Sqlite3_ColumnName(hStmt: TSqliteStmt; ColNum: integer): pchar; cdecl; external SQLiteDLL name 'sqlite3_column_name';
-function Sqlite3_ColumnDeclType(hStmt: TSqliteStmt; ColNum: integer): pchar; cdecl; external SQLiteDLL name 'sqlite3_column_decltype';
-function Sqlite3_Step(hStmt: TSqliteStmt): integer; cdecl; external SQLiteDLL name 'sqlite3_step';
+function SQLite3_ColumnName(hStmt: TSqliteStmt; ColNum: integer): pchar; cdecl; external SQLiteDLL name 'sqlite3_column_name';
+function SQLite3_ColumnDeclType(hStmt: TSqliteStmt; ColNum: integer): pchar; cdecl; external SQLiteDLL name 'sqlite3_column_decltype';
+function SQLite3_Step(hStmt: TSqliteStmt): integer; cdecl; external SQLiteDLL name 'sqlite3_step';
function SQLite3_DataCount(hStmt: TSqliteStmt): integer; cdecl; external SQLiteDLL name 'sqlite3_data_count';
-function Sqlite3_ColumnBlob(hStmt: TSqliteStmt; ColNum: integer): pointer; cdecl; external SQLiteDLL name 'sqlite3_column_blob';
-function Sqlite3_ColumnBytes(hStmt: TSqliteStmt; ColNum: integer): integer; cdecl; external SQLiteDLL name 'sqlite3_column_bytes';
-function Sqlite3_ColumnDouble(hStmt: TSqliteStmt; ColNum: integer): double; cdecl; external SQLiteDLL name 'sqlite3_column_double';
-function Sqlite3_ColumnInt(hStmt: TSqliteStmt; ColNum: integer): integer; cdecl; external SQLiteDLL name 'sqlite3_column_int';
-function Sqlite3_ColumnText(hStmt: TSqliteStmt; ColNum: integer): pchar; cdecl; external SQLiteDLL name 'sqlite3_column_text';
-function Sqlite3_ColumnType(hStmt: TSqliteStmt; ColNum: integer): integer; cdecl; external SQLiteDLL name 'sqlite3_column_type';
-function Sqlite3_ColumnInt64(hStmt: TSqliteStmt; ColNum: integer): Int64; cdecl; external SQLiteDLL name 'sqlite3_column_int64';
+function SQLite3_ColumnBlob(hStmt: TSqliteStmt; ColNum: integer): pointer; cdecl; external SQLiteDLL name 'sqlite3_column_blob';
+function SQLite3_ColumnBytes(hStmt: TSqliteStmt; ColNum: integer): integer; cdecl; external SQLiteDLL name 'sqlite3_column_bytes';
+function SQLite3_ColumnDouble(hStmt: TSqliteStmt; ColNum: integer): double; cdecl; external SQLiteDLL name 'sqlite3_column_double';
+function SQLite3_ColumnInt(hStmt: TSqliteStmt; ColNum: integer): integer; cdecl; external SQLiteDLL name 'sqlite3_column_int';
+function SQLite3_ColumnText(hStmt: TSqliteStmt; ColNum: integer): pchar; cdecl; external SQLiteDLL name 'sqlite3_column_text';
+function SQLite3_ColumnType(hStmt: TSqliteStmt; ColNum: integer): integer; cdecl; external SQLiteDLL name 'sqlite3_column_type';
+function SQLite3_ColumnInt64(hStmt: TSqliteStmt; ColNum: integer): Int64; cdecl; external SQLiteDLL name 'sqlite3_column_int64';
function SQLite3_Finalize(hStmt: TSqliteStmt): integer; cdecl; external SQLiteDLL name 'sqlite3_finalize';
function SQLite3_Reset(hStmt: TSqliteStmt): integer; cdecl; external SQLiteDLL name 'sqlite3_reset';
-//
+//
// In the SQL strings input to sqlite3_prepare() and sqlite3_prepare16(),
// one or more literals can be replace by a wildcard "?" or ":N:" where
// N is an integer. These value of these wildcard literals can be set
// using the routines listed below.
-//
+//
// In every case, the first parameter is a pointer to the sqlite3_stmt
// structure returned from sqlite3_prepare(). The second parameter is the
// index of the wildcard. The first "?" has an index of 1. ":N:" wildcards
// use the index N.
-//
- // The fifth parameter to sqlite3_bind_blob(), sqlite3_bind_text(), and
- //sqlite3_bind_text16() is a destructor used to dispose of the BLOB or
+//
+// The fifth parameter to sqlite3_bind_blob(), sqlite3_bind_text(), and
+//sqlite3_bind_text16() is a destructor used to dispose of the BLOB or
//text after SQLite has finished with it. If the fifth argument is the
// special value SQLITE_STATIC, then the library assumes that the information
// is in static, unmanaged space and does not need to be freed. If the
// fifth argument has the value SQLITE_TRANSIENT, then SQLite makes its
// own private copy of the data.
-//
+//
// The sqlite3_bind_* routine must be called before sqlite3_step() after
// an sqlite3_prepare() or sqlite3_reset(). Unbound wildcards are interpreted
// as NULL.
-//
+//
+
+type
+ TSQLite3Destructor = procedure(Ptr: Pointer); cdecl;
-function SQLite3_BindBlob(hStmt: TSqliteStmt; ParamNum: integer;
- ptrData: pointer; numBytes: integer; ptrDestructor: pointer): integer;
+function sqlite3_bind_blob(hStmt: TSqliteStmt; ParamNum: integer;
+ ptrData: pointer; numBytes: integer; ptrDestructor: TSQLite3Destructor): integer;
cdecl; external SQLiteDLL name 'sqlite3_bind_blob';
+function sqlite3_bind_text(hStmt: TSqliteStmt; ParamNum: integer;
+ Text: PChar; numBytes: integer; ptrDestructor: TSQLite3Destructor): integer;
+cdecl; external SQLiteDLL name 'sqlite3_bind_text';
+function sqlite3_bind_double(hStmt: TSqliteStmt; ParamNum: integer; Data: Double): integer;
+ cdecl; external SQLiteDLL name 'sqlite3_bind_double';
+function sqlite3_bind_int(hStmt: TSqLiteStmt; ParamNum: integer; Data: integer): integer;
+ cdecl; external 'sqlite3.dll' name 'sqlite3_bind_int';
+function sqlite3_bind_int64(hStmt: TSqliteStmt; ParamNum: integer; Data: int64): integer;
+ cdecl; external SQLiteDLL name 'sqlite3_bind_int64';
+function sqlite3_bind_null(hStmt: TSqliteStmt; ParamNum: integer): integer;
+ cdecl; external SQLiteDLL name 'sqlite3_bind_null';
+
+function sqlite3_bind_parameter_index(hStmt: TSqliteStmt; zName: PChar): integer;
+ cdecl; external SQLiteDLL name 'sqlite3_bind_parameter_index';
+
+function sqlite3_enable_shared_cache(Value: integer): integer; cdecl; external SQLiteDLL name 'sqlite3_enable_shared_cache';
+
+//user collate definiton
+function SQLite3_create_collation(db: TSQLiteDB; Name: Pchar; eTextRep: integer;
+ UserData: pointer; xCompare: TCollateXCompare): integer; cdecl; external SQLiteDLL name 'sqlite3_create_collation';
function SQLiteFieldType(SQLiteFieldTypeCode: Integer): AnsiString;
function SQLiteErrorStr(SQLiteErrorCode: Integer): AnsiString;
diff --git a/Game/Code/lib/SQLite/SQLiteTable3.pas b/Game/Code/lib/SQLite/SQLiteTable3.pas
index cbf868bd..8f33b115 100644
--- a/Game/Code/lib/SQLite/SQLiteTable3.pas
+++ b/Game/Code/lib/SQLite/SQLiteTable3.pas
@@ -6,30 +6,67 @@ unit SQLiteTable3;
TSQLiteDatabase wraps the calls to open and close an SQLite database.
It also wraps SQLite_exec for queries that do not return a result set
- TSQLiteTable wraps sqlite_get_table.
- It allows accessing fields by name as well as index and can step through a
- result set with the Next procedure.
-
- Adapted by Tim Anderson (tim@itwriting.com)
- Originally created by Pablo Pissanetzky (pablo@myhtpc.net)
- Modified and enhanced by Lukas Gebauer
+ TSQLiteTable wraps execution of SQL query.
+ It run query and read all returned rows to internal buffer.
+ It allows accessing fields by name as well as index and can move through a
+ result set forward and backwards, or randomly to any row.
+
+ TSQLiteUniTable wraps execution of SQL query.
+ It run query as TSQLiteTable, but reading just first row only!
+ You can step to next row (until not EOF) by 'Next' method.
+ You cannot step backwards! (So, it is called as UniDirectional result set.)
+ It not using any internal buffering, this class is very close to Sqlite API.
+ It allows accessing fields by name as well as index on actual row only.
+ Very good and fast for sequentional scanning of large result sets with minimal
+ memory footprint.
+
+ Warning! Do not close TSQLiteDatabase before any TSQLiteUniTable,
+ because query is closed on TSQLiteUniTable destructor and database connection
+ is used during TSQLiteUniTable live!
+
+ SQL parameter usage:
+ You can add named parameter values by call set of AddParam* methods.
+ Parameters will be used for first next SQL statement only.
+ Parameter name must be prefixed by ':', '$' or '@' and same prefix must be
+ used in SQL statement!
+ Sample:
+ table.AddParamText(':str', 'some value');
+ s := table.GetTableString('SELECT value FROM sometable WHERE id=:str');
+
+ Notes from Andrew Retmanski on prepared queries
+ The changes are as follows:
+
+ SQLiteTable3.pas
+ - Added new boolean property Synchronised (this controls the SYNCHRONOUS pragma as I found that turning this OFF increased the write performance in my application)
+ - Added new type TSQLiteQuery (this is just a simple record wrapper around the SQL string and a TSQLiteStmt pointer)
+ - Added PrepareSQL method to prepare SQL query - returns TSQLiteQuery
+ - Added ReleaseSQL method to release previously prepared query
+ - Added overloaded BindSQL methods for Integer and String types - these set new values for the prepared query parameters
+ - Added overloaded ExecSQL method to execute a prepared TSQLiteQuery
+
+ Usage of the new methods should be self explanatory but the process is in essence:
+
+ 1. Call PrepareSQL to return TSQLiteQuery 2. Call BindSQL for each parameter in the prepared query 3. Call ExecSQL to run the prepared query 4. Repeat steps 2 & 3 as required 5. Call ReleaseSQL to free SQLite resources
+
+ One other point - the Synchronised property throws an error if used inside a transaction.
+
+ Acknowledments
+ Adapted by Tim Anderson (tim@itwriting.com)
+ Originally created by Pablo Pissanetzky (pablo@myhtpc.net)
+ Modified and enhanced by Lukas Gebauer
}
interface
{$IFDEF FPC}
- {$MODE Delphi}
+ {$MODE Delphi}{$H+}
{$ENDIF}
-{$I switches.inc}
-
uses
- {$ifdef win32}
+ {$IFDEF WIN32}
Windows,
- {$endif}
- SQLite3,
- Classes,
- SysUtils;
+ {$ENDIF}
+ SQLite3, Classes, SysUtils;
const
@@ -44,30 +81,78 @@ type
ESQLiteException = class(Exception)
end;
+ TSQliteParam = class
+ public
+ name: string;
+ valuetype: integer;
+ valueinteger: int64;
+ valuefloat: double;
+ valuedata: string;
+ end;
+
+ TSQLiteQuery = record
+ SQL: String;
+ Statement: TSQLiteStmt;
+ end;
+
+
TSQLiteTable = class;
+ TSQLiteUniTable = class;
TSQLiteDatabase = class
private
fDB: TSQLiteDB;
fInTrans: boolean;
+ fSync: boolean;
+ fParams: TList;
procedure RaiseError(s: string; SQL: string);
+ procedure SetParams(Stmt: TSQLiteStmt);
+ procedure BindData(Stmt: TSQLiteStmt; const Bindings: array of const);
+ function GetRowsChanged: integer;
+ protected
+ procedure SetSynchronised(Value: boolean);
public
constructor Create(const FileName: string);
destructor Destroy; override;
- function GetTable(const SQL: string): TSQLiteTable;
- procedure ExecSQL(const SQL: string);
- function GetTableValue(const SQL: string): int64;
- function GetTableString(const SQL: string): string;
+ function GetTable(const SQL: string): TSQLiteTable; overload;
+ function GetTable(const SQL: string; const Bindings: array of const): TSQLiteTable; overload;
+ procedure ExecSQL(const SQL: string); overload;
+ procedure ExecSQL(const SQL: string; const Bindings: array of const); overload;
+ procedure ExecSQL(Query: TSQLiteQuery); overload;
+ function PrepareSQL(const SQL: string): TSQLiteQuery;
+ procedure BindSQL(Query: TSQLiteQuery; const Index: Integer; const Value: Integer); overload;
+ procedure BindSQL(Query: TSQLiteQuery; const Index: Integer; const Value: String); overload;
+ procedure ReleaseSQL(Query: TSQLiteQuery);
+ function GetUniTable(const SQL: string): TSQLiteUniTable; overload;
+ function GetUniTable(const SQL: string; const Bindings: array of const): TSQLiteUniTable; overload;
+ function GetTableValue(const SQL: string): int64; overload;
+ function GetTableValue(const SQL: string; const Bindings: array of const): int64; overload;
+ function GetTableString(const SQL: string): string; overload;
+ function GetTableString(const SQL: string; const Bindings: array of const): string; overload;
procedure UpdateBlob(const SQL: string; BlobData: TStream);
procedure BeginTransaction;
procedure Commit;
procedure Rollback;
function TableExists(TableName: string): boolean;
function GetLastInsertRowID: int64;
+ function GetLastChangedRows: int64;
procedure SetTimeout(Value: integer);
- function version: string;
+ function Version: string;
+ procedure AddCustomCollate(name: string; xCompare: TCollateXCompare);
+ //adds collate named SYSTEM for correct data sorting by user's locale
+ Procedure AddSystemCollate;
+ procedure ParamsClear;
+ procedure AddParamInt(name: string; value: int64);
+ procedure AddParamFloat(name: string; value: double);
+ procedure AddParamText(name: string; value: string);
+ procedure AddParamNull(name: string);
+ property DB: TSQLiteDB read fDB;
published
- property isTransactionOpen: boolean read fInTrans;
+ property IsTransactionOpen: boolean read fInTrans;
+ //database rows that were changed (or inserted or deleted) by the most recent SQL statement
+ property RowsChanged : integer read getRowsChanged;
+ property Synchronised: boolean read FSync write SetSynchronised;
+
end;
TSQLiteTable = class
@@ -87,7 +172,8 @@ type
function GetCount: integer;
function GetCountResult: integer;
public
- constructor Create(DB: TSQLiteDatabase; const SQL: string);
+ constructor Create(DB: TSQLiteDatabase; const SQL: string); overload;
+ constructor Create(DB: TSQLiteDatabase; const SQL: string; const Bindings: array of const); overload;
destructor Destroy; override;
function FieldAsInteger(I: cardinal): int64;
function FieldAsBlob(I: cardinal): TMemoryStream;
@@ -108,6 +194,7 @@ type
property Row: cardinal read fRow;
function MoveFirst: boolean;
function MoveLast: boolean;
+ function MoveTo(position:Integer): boolean;
property Count: integer read GetCount;
// The property CountResult is used when you execute count(*) queries.
// It returns 0 if the result set is empty or the value of the
@@ -115,8 +202,46 @@ type
property CountResult: integer read GetCountResult;
end;
+ TSQLiteUniTable = class
+ private
+ fColCount: cardinal;
+ fCols: TStringList;
+ fRow: cardinal;
+ fEOF: boolean;
+ fStmt: TSQLiteStmt;
+ fDB: TSQLiteDatabase;
+ fSQL: string;
+ function GetFields(I: cardinal): string;
+ function GetColumns(I: integer): string;
+ function GetFieldByName(FieldName: string): string;
+ function GetFieldIndex(FieldName: string): integer;
+ public
+ constructor Create(DB: TSQLiteDatabase; const SQL: string); overload;
+ constructor Create(DB: TSQLiteDatabase; const SQL: string; const Bindings: array of const); overload;
+ destructor Destroy; override;
+ function FieldAsInteger(I: cardinal): int64;
+ function FieldAsBlob(I: cardinal): TMemoryStream;
+ function FieldAsBlobPtr(I: cardinal; out iNumBytes: integer): Pointer;
+ function FieldAsBlobText(I: cardinal): string;
+ function FieldIsNull(I: cardinal): boolean;
+ function FieldAsString(I: cardinal): string;
+ function FieldAsDouble(I: cardinal): double;
+ function Next: boolean;
+ property EOF: boolean read FEOF;
+ property Fields[I: cardinal]: string read GetFields;
+ property FieldByName[FieldName: string]: string read GetFieldByName;
+ property FieldIndex[FieldName: string]: integer read GetFieldIndex;
+ property Columns[I: integer]: string read GetColumns;
+ property ColCount: cardinal read fColCount;
+ property Row: cardinal read fRow;
+ end;
+
procedure DisposePointer(ptr: pointer); cdecl;
+{$IFDEF WIN32}
+function SystemCollate(Userdta: pointer; Buf1Len: integer; Buf1: pointer;
+ Buf2Len: integer; Buf2: pointer): integer; cdecl;
+{$ENDIF}
implementation
@@ -126,6 +251,15 @@ begin
freemem(ptr);
end;
+{$IFDEF WIN32}
+function SystemCollate(Userdta: pointer; Buf1Len: integer; Buf1: pointer;
+ Buf2Len: integer; Buf2: pointer): integer; cdecl;
+begin
+ Result := CompareStringW(LOCALE_USER_DEFAULT, 0, PWideChar(Buf1), Buf1Len,
+ PWideChar(Buf2), Buf2Len) - 2;
+end;
+{$ENDIF}
+
//------------------------------------------------------------------------------
// TSQLiteDatabase
//------------------------------------------------------------------------------
@@ -134,14 +268,17 @@ constructor TSQLiteDatabase.Create(const FileName: string);
var
Msg: pchar;
iResult: integer;
+ utf8FileName: string;
begin
inherited Create;
+ fParams := TList.Create;
self.fInTrans := False;
Msg := nil;
try
- iResult := SQLite3_Open(PChar(FileName), Fdb);
+ utf8FileName := AnsiToUtf8(FileName);
+ iResult := SQLite3_Open(PChar(utf8FileName), Fdb);
if iResult <> SQLITE_OK then
if Assigned(Fdb) then
@@ -154,10 +291,12 @@ begin
raise ESqliteException.CreateFmt('Failed to open database "%s" : unknown error',
[FileName]);
- //set a few configs
- self.ExecSQL('PRAGMA SYNCHRONOUS=NORMAL;');
-// self.ExecSQL('PRAGMA full_column_names = 1;');
- self.ExecSQL('PRAGMA temp_store = MEMORY;');
+//set a few configs
+//L.G. Do not call it here. Because busy handler is not setted here,
+// any share violation causing exception!
+
+// self.ExecSQL('PRAGMA SYNCHRONOUS=NORMAL;');
+// self.ExecSQL('PRAGMA temp_store = MEMORY;');
finally
if Assigned(Msg) then
@@ -166,18 +305,16 @@ begin
end;
-
//..............................................................................
destructor TSQLiteDatabase.Destroy;
begin
-
if self.fInTrans then
- self.ExecSQL('ROLLBACK;'); //assume rollback
-
+ self.Rollback; //assume rollback
if Assigned(fDB) then
SQLite3_Close(fDB);
-
+ ParamsClear;
+ fParams.Free;
inherited;
end;
@@ -186,53 +323,263 @@ begin
Result := Sqlite3_LastInsertRowID(self.fDB);
end;
+function TSQLiteDatabase.GetLastChangedRows: int64;
+begin
+ Result := SQLite3_TotalChanges(self.fDB);
+end;
+
//..............................................................................
procedure TSQLiteDatabase.RaiseError(s: string; SQL: string);
//look up last error and raise an exception with an appropriate message
var
Msg: PChar;
+ ret : integer;
begin
Msg := nil;
- if sqlite3_errcode(self.fDB) <> SQLITE_OK then
+ ret := sqlite3_errcode(self.fDB);
+ if ret <> SQLITE_OK then
Msg := sqlite3_errmsg(self.fDB);
if Msg <> nil then
- raise ESqliteException.CreateFmt(s + ' "%s" : %s', [SQL, Msg])
+ raise ESqliteException.CreateFmt(s +'.'#13'Error [%d]: %s.'#13'"%s": %s', [ret, SQLiteErrorStr(ret),SQL, Msg])
else
raise ESqliteException.CreateFmt(s, [SQL, 'No message']);
end;
+procedure TSQLiteDatabase.SetSynchronised(Value: boolean);
+begin
+ if Value <> fSync then
+ begin
+ if Value then
+ ExecSQL('PRAGMA synchronous = ON;')
+ else
+ ExecSQL('PRAGMA synchronous = OFF;');
+ fSync := Value;
+ end;
+end;
+
+procedure TSQLiteDatabase.BindData(Stmt: TSQLiteStmt; const Bindings: array of const);
+var
+ BlobMemStream: TCustomMemoryStream;
+ BlobStdStream: TStream;
+ DataPtr: Pointer;
+ DataSize: integer;
+ AnsiStr: AnsiString;
+ AnsiStrPtr: PAnsiString;
+ I: integer;
+begin
+ for I := 0 to High(Bindings) do
+ begin
+ case Bindings[I].VType of
+ vtString,
+ vtAnsiString, vtPChar,
+ vtWideString, vtPWideChar,
+ vtChar, vtWideChar:
+ begin
+ case Bindings[I].VType of
+ vtString: begin // ShortString
+ AnsiStr := Bindings[I].VString^;
+ DataPtr := PChar(AnsiStr);
+ DataSize := Length(AnsiStr)+1;
+ end;
+ vtPChar: begin
+ DataPtr := Bindings[I].VPChar;
+ DataSize := -1;
+ end;
+ vtAnsiString: begin
+ AnsiStrPtr := PString(@Bindings[I].VAnsiString);
+ DataPtr := PChar(AnsiStrPtr^);
+ DataSize := Length(AnsiStrPtr^)+1;
+ end;
+ vtPWideChar: begin
+ DataPtr := PChar(UTF8Encode(WideString(Bindings[I].VPWideChar)));
+ DataSize := -1;
+ end;
+ vtWideString: begin
+ DataPtr := PChar(UTF8Encode(PWideString(@Bindings[I].VWideString)^));
+ DataSize := -1;
+ end;
+ vtChar: begin
+ DataPtr := PChar(String(Bindings[I].VChar));
+ DataSize := 2;
+ end;
+ vtWideChar: begin
+ DataPtr := PChar(UTF8Encode(WideString(Bindings[I].VWideChar)));
+ DataSize := -1;
+ end;
+ else
+ raise ESqliteException.Create('Unknown string-type');
+ end;
+ if (sqlite3_bind_text(Stmt, I+1, DataPtr, DataSize, SQLITE_STATIC) <> SQLITE_OK) then
+ RaiseError('Could not bind text', 'BindData');
+ end;
+ vtInteger:
+ if (sqlite3_bind_int(Stmt, I+1, Bindings[I].VInteger) <> SQLITE_OK) then
+ RaiseError('Could not bind integer', 'BindData');
+ vtInt64:
+ if (sqlite3_bind_int64(Stmt, I+1, Bindings[I].VInt64^) <> SQLITE_OK) then
+ RaiseError('Could not bind int64', 'BindData');
+ vtExtended:
+ if (sqlite3_bind_double(Stmt, I+1, Bindings[I].VExtended^) <> SQLITE_OK) then
+ RaiseError('Could not bind extended', 'BindData');
+ vtBoolean:
+ if (sqlite3_bind_int(Stmt, I+1, Integer(Bindings[I].VBoolean)) <> SQLITE_OK) then
+ RaiseError('Could not bind boolean', 'BindData');
+ vtPointer:
+ begin
+ if (Bindings[I].VPointer = nil) then
+ begin
+ if (sqlite3_bind_null(Stmt, I+1) <> SQLITE_OK) then
+ RaiseError('Could not bind null', 'BindData');
+ end
+ else
+ raise ESqliteException.Create('Unhandled pointer (<> nil)');
+ end;
+ vtObject:
+ begin
+ if (Bindings[I].VObject is TCustomMemoryStream) then
+ begin
+ BlobMemStream := TCustomMemoryStream(Bindings[I].VObject);
+ if (sqlite3_bind_blob(Stmt, I+1, @PChar(BlobMemStream.Memory)[BlobMemStream.Position],
+ BlobMemStream.Size-BlobMemStream.Position, SQLITE_STATIC) <> SQLITE_OK) then
+ begin
+ RaiseError('Could not bind BLOB', 'BindData');
+ end;
+ end
+ else if (Bindings[I].VObject is TStream) then
+ begin
+ BlobStdStream := TStream(Bindings[I].VObject);
+ DataSize := BlobStdStream.Size;
+
+ GetMem(DataPtr, DataSize);
+ if (DataPtr = nil) then
+ raise ESqliteException.Create('Error getting memory to save blob');
+
+ BlobStdStream.Position := 0;
+ BlobStdStream.Read(DataPtr^, DataSize);
+
+ if (sqlite3_bind_blob(stmt, I+1, DataPtr, DataSize, @DisposePointer) <> SQLITE_OK) then
+ RaiseError('Could not bind BLOB', 'BindData');
+ end
+ else
+ raise ESqliteException.Create('Unhandled object-type in binding');
+ end
+ else
+ begin
+ raise ESqliteException.Create('Unhandled binding');
+ end;
+ end;
+ end;
+end;
+
procedure TSQLiteDatabase.ExecSQL(const SQL: string);
+begin
+ ExecSQL(SQL, []);
+end;
+
+procedure TSQLiteDatabase.ExecSQL(const SQL: string; const Bindings: array of const);
var
Stmt: TSQLiteStmt;
NextSQLStatement: Pchar;
iStepResult: integer;
begin
try
-
- if Sqlite3_Prepare(self.fDB, PChar(SQL), -1, Stmt, NextSQLStatement) <>
+ if Sqlite3_Prepare_v2(self.fDB, PChar(SQL), -1, Stmt, NextSQLStatement) <>
SQLITE_OK then
RaiseError('Error executing SQL', SQL);
-
if (Stmt = nil) then
RaiseError('Could not prepare SQL statement', SQL);
- iStepResult := Sqlite3_step(Stmt);
+ SetParams(Stmt);
+ BindData(Stmt, Bindings);
+ iStepResult := Sqlite3_step(Stmt);
if (iStepResult <> SQLITE_DONE) then
+ begin
+ SQLite3_reset(stmt);
RaiseError('Error executing SQL statement', SQL);
-
+ end;
finally
-
if Assigned(Stmt) then
Sqlite3_Finalize(stmt);
+ end;
+end;
+
+{$WARNINGS OFF}
+procedure TSQLiteDatabase.ExecSQL(Query: TSQLiteQuery);
+var
+ iStepResult: integer;
+begin
+ if Assigned(Query.Statement) then
+ begin
+ iStepResult := Sqlite3_step(Query.Statement);
+ if (iStepResult <> SQLITE_DONE) then
+ begin
+ SQLite3_reset(Query.Statement);
+ RaiseError('Error executing prepared SQL statement', Query.SQL);
+ end;
+ Sqlite3_Reset(Query.Statement);
end;
end;
+{$WARNINGS ON}
+
+{$WARNINGS OFF}
+function TSQLiteDatabase.PrepareSQL(const SQL: string): TSQLiteQuery;
+var
+ Stmt: TSQLiteStmt;
+ NextSQLStatement: Pchar;
+begin
+ Result.SQL := SQL;
+ Result.Statement := nil;
+
+ if Sqlite3_Prepare(self.fDB, PChar(SQL), -1, Stmt, NextSQLStatement) <>
+ SQLITE_OK then
+ RaiseError('Error executing SQL', SQL)
+ else
+ Result.Statement := Stmt;
+
+ if (Result.Statement = nil) then
+ RaiseError('Could not prepare SQL statement', SQL);
+end;
+{$WARNINGS ON}
+
+{$WARNINGS OFF}
+procedure TSQLiteDatabase.BindSQL(Query: TSQLiteQuery; const Index: Integer; const Value: Integer);
+begin
+ if Assigned(Query.Statement) then
+ sqlite3_Bind_Int(Query.Statement, Index, Value)
+ else
+ RaiseError('Could not bind integer to prepared SQL statement', Query.SQL);
+end;
+{$WARNINGS ON}
+
+{$WARNINGS OFF}
+procedure TSQLiteDatabase.BindSQL(Query: TSQLiteQuery; const Index: Integer; const Value: String);
+begin
+ if Assigned(Query.Statement) then
+ Sqlite3_Bind_Text(Query.Statement, Index, PChar(Value), Length(Value), Pointer(SQLITE_STATIC))
+ else
+ RaiseError('Could not bind string to prepared SQL statement', Query.SQL);
+end;
+{$WARNINGS ON}
+
+{$WARNINGS OFF}
+procedure TSQLiteDatabase.ReleaseSQL(Query: TSQLiteQuery);
+begin
+ if Assigned(Query.Statement) then
+ begin
+ Sqlite3_Finalize(Query.Statement);
+ Query.Statement := nil;
+ end
+ else
+ RaiseError('Could not release prepared SQL statement', Query.SQL);
+end;
+{$WARNINGS ON}
procedure TSQLiteDatabase.UpdateBlob(const SQL: string; BlobData: TStream);
var
@@ -245,14 +592,13 @@ var
iBindResult: integer;
begin
//expects SQL of the form 'UPDATE MYTABLE SET MYFIELD = ? WHERE MYKEY = 1'
-
if pos('?', SQL) = 0 then
RaiseError('SQL must include a ? parameter', SQL);
Msg := nil;
try
- if Sqlite3_Prepare(self.fDB, PChar(SQL), -1, Stmt, NextSQLStatement) <>
+ if Sqlite3_Prepare_v2(self.fDB, PChar(SQL), -1, Stmt, NextSQLStatement) <>
SQLITE_OK then
RaiseError('Could not prepare SQL statement', SQL);
@@ -271,7 +617,7 @@ begin
BlobData.position := 0;
BlobData.Read(ptr^, iSize);
- iBindResult := SQLite3_BindBlob(stmt, 1, ptr, iSize, @DisposePointer);
+ iBindResult := SQLite3_Bind_Blob(stmt, 1, ptr, iSize, @DisposePointer);
if iBindResult <> SQLITE_OK then
RaiseError('Error binding blob to database', SQL);
@@ -279,7 +625,10 @@ begin
iStepResult := Sqlite3_step(Stmt);
if (iStepResult <> SQLITE_DONE) then
+ begin
+ SQLite3_reset(stmt);
RaiseError('Error executing SQL statement', SQL);
+ end;
finally
@@ -299,25 +648,54 @@ begin
Result := TSQLiteTable.Create(Self, SQL);
end;
+function TSQLiteDatabase.GetTable(const SQL: string; const Bindings: array of const): TSQLiteTable;
+begin
+ Result := TSQLiteTable.Create(Self, SQL, Bindings);
+end;
+
+function TSQLiteDatabase.GetUniTable(const SQL: string): TSQLiteUniTable;
+begin
+ Result := TSQLiteUniTable.Create(Self, SQL);
+end;
+
+function TSQLiteDatabase.GetUniTable(const SQL: string; const Bindings: array of const): TSQLiteUniTable;
+begin
+ Result := TSQLiteUniTable.Create(Self, SQL, Bindings);
+end;
+
function TSQLiteDatabase.GetTableValue(const SQL: string): int64;
+begin
+ Result := GetTableValue(SQL, []);
+end;
+
+function TSQLiteDatabase.GetTableValue(const SQL: string; const Bindings: array of const): int64;
var
- Table: TSQLiteTable;
+ Table: TSQLiteUniTable;
begin
- Table := self.GetTable(SQL);
+ Result := 0;
+ Table := self.GetUniTable(SQL, Bindings);
try
- Result := Table.FieldAsInteger(0);
+ if not Table.EOF then
+ Result := Table.FieldAsInteger(0);
finally
Table.Free;
end;
end;
-function TSQLiteDatabase.GetTableString(const SQL: string): string;
+function TSQLiteDatabase.GetTableString(const SQL: string): String;
+begin
+ Result := GetTableString(SQL, []);
+end;
+
+function TSQLiteDatabase.GetTableString(const SQL: string; const Bindings: array of const): String;
var
- Table: TSQLiteTable;
+ Table: TSQLiteUniTable;
begin
- Table := self.GetTable(SQL);
+ Result := '';
+ Table := self.GetUniTable(SQL, Bindings);
try
- Result := Table.FieldAsString(0);
+ if not Table.EOF then
+ Result := Table.FieldAsString(0);
finally
Table.Free;
end;
@@ -328,7 +706,7 @@ procedure TSQLiteDatabase.BeginTransaction;
begin
if not self.fInTrans then
begin
- self.ExecSQL('BEGIN TRANSACTION;');
+ self.ExecSQL('BEGIN TRANSACTION');
self.fInTrans := True;
end
else
@@ -337,13 +715,13 @@ end;
procedure TSQLiteDatabase.Commit;
begin
- self.ExecSQL('COMMIT;');
+ self.ExecSQL('COMMIT');
self.fInTrans := False;
end;
procedure TSQLiteDatabase.Rollback;
begin
- self.ExecSQL('ROLLBACK;');
+ self.ExecSQL('ROLLBACK');
self.fInTrans := False;
end;
@@ -353,8 +731,8 @@ var
ds: TSqliteTable;
begin
//returns true if table exists in the database
- sql := 'select [sql] from sqlite_master where [type] = "table" and lower(name) = "' +
- lowercase(TableName) + '"';
+ sql := 'select [sql] from sqlite_master where [type] = ''table'' and lower(name) = ''' +
+ lowercase(TableName) + ''' ';
ds := self.GetTable(sql);
try
Result := (ds.Count > 0);
@@ -368,17 +746,123 @@ begin
SQLite3_BusyTimeout(self.fDB, Value);
end;
-function TSQLiteDatabase.version: string;
+function TSQLiteDatabase.Version: string;
begin
Result := SQLite3_Version;
end;
+procedure TSQLiteDatabase.AddCustomCollate(name: string;
+ xCompare: TCollateXCompare);
+begin
+ sqlite3_create_collation(fdb, PChar(name), SQLITE_UTF8, nil, xCompare);
+end;
+
+procedure TSQLiteDatabase.AddSystemCollate;
+begin
+ {$IFDEF WIN32}
+ sqlite3_create_collation(fdb, 'SYSTEM', SQLITE_UTF16LE, nil, @SystemCollate);
+ {$ENDIF}
+end;
+
+procedure TSQLiteDatabase.ParamsClear;
+var
+ n: integer;
+begin
+ for n := fParams.Count - 1 downto 0 do
+ TSQliteParam(fparams[n]).free;
+ fParams.Clear;
+end;
+
+procedure TSQLiteDatabase.AddParamInt(name: string; value: int64);
+var
+ par: TSQliteParam;
+begin
+ par := TSQliteParam.Create;
+ par.name := name;
+ par.valuetype := SQLITE_INTEGER;
+ par.valueinteger := value;
+ fParams.Add(par);
+end;
+
+procedure TSQLiteDatabase.AddParamFloat(name: string; value: double);
+var
+ par: TSQliteParam;
+begin
+ par := TSQliteParam.Create;
+ par.name := name;
+ par.valuetype := SQLITE_FLOAT;
+ par.valuefloat := value;
+ fParams.Add(par);
+end;
+
+procedure TSQLiteDatabase.AddParamText(name: string; value: string);
+var
+ par: TSQliteParam;
+begin
+ par := TSQliteParam.Create;
+ par.name := name;
+ par.valuetype := SQLITE_TEXT;
+ par.valuedata := value;
+ fParams.Add(par);
+end;
+
+procedure TSQLiteDatabase.AddParamNull(name: string);
+var
+ par: TSQliteParam;
+begin
+ par := TSQliteParam.Create;
+ par.name := name;
+ par.valuetype := SQLITE_NULL;
+ fParams.Add(par);
+end;
+
+procedure TSQLiteDatabase.SetParams(Stmt: TSQLiteStmt);
+var
+ n: integer;
+ i: integer;
+ par: TSQliteParam;
+begin
+ try
+ for n := 0 to fParams.Count - 1 do
+ begin
+ par := TSQliteParam(fParams[n]);
+ i := sqlite3_bind_parameter_index(Stmt, PChar(par.name));
+ if i > 0 then
+ begin
+ case par.valuetype of
+ SQLITE_INTEGER:
+ sqlite3_bind_int64(Stmt, i, par.valueinteger);
+ SQLITE_FLOAT:
+ sqlite3_bind_double(Stmt, i, par.valuefloat);
+ SQLITE_TEXT:
+ sqlite3_bind_text(Stmt, i, pchar(par.valuedata),
+ length(par.valuedata), SQLITE_TRANSIENT);
+ SQLITE_NULL:
+ sqlite3_bind_null(Stmt, i);
+ end;
+ end;
+ end;
+ finally
+ ParamsClear;
+ end;
+end;
+
+//database rows that were changed (or inserted or deleted) by the most recent SQL statement
+function TSQLiteDatabase.GetRowsChanged: integer;
+begin
+ Result := SQLite3_Changes(self.fDB);
+end;
//------------------------------------------------------------------------------
// TSQLiteTable
//------------------------------------------------------------------------------
constructor TSQLiteTable.Create(DB: TSQLiteDatabase; const SQL: string);
+begin
+ Create(DB, SQL, []);
+end;
+
+constructor TSQLiteTable.Create(DB: TSQLiteDatabase; const SQL: string; const Bindings: array of const);
var
Stmt: TSQLiteStmt;
NextSQLStatement: Pchar;
@@ -395,15 +879,20 @@ var
ActualColType: integer;
ptrValue: Pchar;
begin
+ inherited create;
try
self.fRowCount := 0;
self.fColCount := 0;
//if there are several SQL statements in SQL, NextSQLStatment points to the
//beginning of the next one. Prepare only prepares the first SQL statement.
- if Sqlite3_Prepare(DB.fDB, PChar(SQL), -1, Stmt, NextSQLStatement) <> SQLITE_OK then
+ if Sqlite3_Prepare_v2(DB.fDB, PChar(SQL), -1, Stmt, NextSQLStatement) <> SQLITE_OK then
DB.RaiseError('Error executing SQL', SQL);
if (Stmt = nil) then
DB.RaiseError('Could not prepare SQL statement', SQL);
+
+ DB.SetParams(Stmt);
+ DB.BindData(Stmt, Bindings);
+
iStepResult := Sqlite3_step(Stmt);
while (iStepResult <> SQLITE_DONE) do
begin
@@ -493,7 +982,10 @@ begin
raise ESqliteException.CreateFmt('Could not prepare SQL statement',
[SQL, 'SQLite is Busy']);
else
+ begin
+ SQLite3_reset(stmt);
DB.RaiseError('Could not retrieve data', SQL);
+ end;
end;
iStepResult := Sqlite3_step(Stmt);
end;
@@ -584,7 +1076,6 @@ end;
function TSQLiteTable.GetFieldIndex(FieldName: string): integer;
begin
-
if (fCols = nil) then
begin
raise ESqliteException.Create('Field ' + fieldname + ' Not found. Empty dataset');
@@ -600,8 +1091,9 @@ begin
Result := fCols.IndexOf(AnsiUpperCase(FieldName));
if (result < 0) then
- begin raise ESqliteException.Create('Field not found in dataset: ' + fieldname) end;
-
+ begin
+ raise ESqliteException.Create('Field not found in dataset: ' + fieldname)
+ end;
end;
//..............................................................................
@@ -675,9 +1167,8 @@ end;
function TSqliteTable.FieldAsInteger(I: cardinal): int64;
begin
if EOF then
- //raise ESqliteException.Create('Table is at End of File');
- Result := 0
- else if (self.fResults[(self.frow * self.fColCount) + I] = nil) then
+ raise ESqliteException.Create('Table is at End of File');
+ if (self.fResults[(self.frow * self.fColCount) + I] = nil) then
Result := 0
else
if pInteger(self.fColTypes[I])^ = dtInt then
@@ -767,6 +1258,189 @@ begin
end;
end;
+{$WARNINGS OFF}
+function TSQLiteTable.MoveTo(position: Integer): boolean;
+begin
+ Result := False;
+ if (self.fRowCount > 0) and (self.fRowCount > position) then
+ begin
+ fRow := position;
+ Result := True;
+ end;
+end;
+{$WARNINGS ON}
+
+
+
+{ TSQLiteUniTable }
+
+constructor TSQLiteUniTable.Create(DB: TSQLiteDatabase; const SQL: string);
+begin
+ Create(DB, SQL, []);
+end;
+
+constructor TSQLiteUniTable.Create(DB: TSQLiteDatabase; const SQL: string; const Bindings: array of const);
+var
+ NextSQLStatement: Pchar;
+ thisColType: pInteger;
+ i: integer;
+ DeclaredColType: Pchar;
+begin
+ inherited create;
+ self.fDB := db;
+ self.fEOF := false;
+ self.fRow := 0;
+ self.fColCount := 0;
+ self.fSQL := SQL;
+ if Sqlite3_Prepare_v2(DB.fDB, PChar(SQL), -1, fStmt, NextSQLStatement) <> SQLITE_OK then
+ DB.RaiseError('Error executing SQL', SQL);
+ if (fStmt = nil) then
+ DB.RaiseError('Could not prepare SQL statement', SQL);
+
+ DB.SetParams(fStmt);
+ DB.BindData(fStmt, Bindings);
+
+ //get data types
+ fCols := TStringList.Create;
+ fColCount := SQLite3_ColumnCount(fstmt);
+ for i := 0 to Pred(fColCount) do
+ fCols.Add(AnsiUpperCase(Sqlite3_ColumnName(fstmt, i)));
+
+ Next;
+end;
+
+destructor TSQLiteUniTable.Destroy;
+var
+ i: integer;
+begin
+ if Assigned(fStmt) then
+ Sqlite3_Finalize(fstmt);
+ if Assigned(fCols) then
+ fCols.Free;
+ inherited;
+end;
+
+function TSQLiteUniTable.FieldAsBlob(I: cardinal): TMemoryStream;
+var
+ iNumBytes: integer;
+ ptr: pointer;
+begin
+ Result := TMemoryStream.Create;
+ iNumBytes := Sqlite3_ColumnBytes(fstmt, i);
+ if iNumBytes > 0 then
+ begin
+ ptr := Sqlite3_ColumnBlob(fstmt, i);
+ Result.writebuffer(ptr^, iNumBytes);
+ Result.Position := 0;
+ end;
+end;
+
+function TSQLiteUniTable.FieldAsBlobPtr(I: cardinal; out iNumBytes: integer): Pointer;
+begin
+ iNumBytes := Sqlite3_ColumnBytes(fstmt, i);
+ Result := Sqlite3_ColumnBlob(fstmt, i);
+end;
+
+function TSQLiteUniTable.FieldAsBlobText(I: cardinal): string;
+var
+ MemStream: TMemoryStream;
+ Buffer: PChar;
+begin
+ Result := '';
+ MemStream := self.FieldAsBlob(I);
+ if MemStream <> nil then
+ if MemStream.Size > 0 then
+ begin
+ MemStream.position := 0;
+ Buffer := stralloc(MemStream.Size + 1);
+ MemStream.readbuffer(Buffer[0], MemStream.Size);
+ (Buffer + MemStream.Size)^ := chr(0);
+ SetString(Result, Buffer, MemStream.size);
+ strdispose(Buffer);
+ end;
+end;
+
+function TSQLiteUniTable.FieldAsDouble(I: cardinal): double;
+begin
+ Result := Sqlite3_ColumnDouble(fstmt, i);
+end;
+
+function TSQLiteUniTable.FieldAsInteger(I: cardinal): int64;
+begin
+ Result := Sqlite3_ColumnInt64(fstmt, i);
+end;
+
+function TSQLiteUniTable.FieldAsString(I: cardinal): string;
+begin
+ Result := self.GetFields(I);
+end;
+
+function TSQLiteUniTable.FieldIsNull(I: cardinal): boolean;
+begin
+ Result := Sqlite3_ColumnText(fstmt, i) = nil;
+end;
+
+function TSQLiteUniTable.GetColumns(I: integer): string;
+begin
+ Result := fCols[I];
+end;
+
+function TSQLiteUniTable.GetFieldByName(FieldName: string): string;
+begin
+ Result := GetFields(self.GetFieldIndex(FieldName));
+end;
+
+function TSQLiteUniTable.GetFieldIndex(FieldName: string): integer;
+begin
+ if (fCols = nil) then
+ begin
+ raise ESqliteException.Create('Field ' + fieldname + ' Not found. Empty dataset');
+ exit;
+ end;
+
+ if (fCols.count = 0) then
+ begin
+ raise ESqliteException.Create('Field ' + fieldname + ' Not found. Empty dataset');
+ exit;
+ end;
+
+ Result := fCols.IndexOf(AnsiUpperCase(FieldName));
+
+ if (result < 0) then
+ begin
+ raise ESqliteException.Create('Field not found in dataset: ' + fieldname)
+ end;
+end;
+
+function TSQLiteUniTable.GetFields(I: cardinal): string;
+begin
+ Result := Sqlite3_ColumnText(fstmt, i);
+end;
+
+function TSQLiteUniTable.Next: boolean;
+var
+ iStepResult: integer;
+begin
+ fEOF := true;
+ iStepResult := Sqlite3_step(fStmt);
+ case iStepResult of
+ SQLITE_ROW:
+ begin
+ fEOF := false;
+ inc(fRow);
+ end;
+ SQLITE_DONE:
+ // we are on the end of dataset
+ // return EOF=true only
+ ;
+ else
+ begin
+ SQLite3_reset(fStmt);
+ fDB.RaiseError('Could not retrieve data', fSQL);
+ end;
+ end;
+ Result := not fEOF;
+end;
end.
diff --git a/Game/Code/lib/SQLite/example/uTestSqlite.dfm b/Game/Code/lib/SQLite/example/uTestSqlite.dfm
index b77ec2c7..6b4a2aaf 100644
--- a/Game/Code/lib/SQLite/example/uTestSqlite.dfm
+++ b/Game/Code/lib/SQLite/example/uTestSqlite.dfm
@@ -1,6 +1,6 @@
object Form1: TForm1
- Left = 242
- Top = 242
+ Left = 199
+ Top = 280
Width = 541
Height = 308
Caption = 'Test SQLite 3'
diff --git a/Game/Code/lib/SQLite/readme.txt b/Game/Code/lib/SQLite/readme.txt
index 0bfdd93e..7998d17f 100644
--- a/Game/Code/lib/SQLite/readme.txt
+++ b/Game/Code/lib/SQLite/readme.txt
@@ -1,3 +1,15 @@
+5 June 2008
+Updated DLL to version 3.5.9 (built with MSVC 6.0)
+Added code from Andrew Retmanski to support prepared queries (see comments in SQLIteTable3.pas
+Lukas added support for named parameters - see comments in code
+User nebula enhanced error message; also modified code to call sqlite3_reset before checking error message
+
+
+27 Aug 2007
+Amended TSQLiteDatabase constructor to convert filename to UTF8,for compatibility with latest SQLite3 DLL.
+
+Updated DLL to version 3.4.2 (built with MSVC 6.0).
+
14 Aug 2005
The following changes were made by Lukas Gebauer (geby@volny.cz). In addition, some changes from a previous D5-compatible version were merged, and the supplied sqlite3.dll is updated to version 3.2.2
@@ -10,8 +22,7 @@ Notes from Lukas:
- removed dependency on strutils
-- code is reformated to better look (official borland formationg
-rules)
+- code is reformatted to better look (official borland formatting rules)
- added some pragma's after database is open (temp is in memory)