From 51995630561a819b80bbf08039bd5d39ce0de564 Mon Sep 17 00:00:00 2001 From: tobigun Date: Thu, 7 Aug 2008 11:37:36 +0000 Subject: 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 --- Game/Code/lib/SQLite/SQLite3.pas | 171 ++++-- Game/Code/lib/SQLite/SQLiteTable3.pas | 800 ++++++++++++++++++++++++--- Game/Code/lib/SQLite/example/uTestSqlite.dfm | 4 +- Game/Code/lib/SQLite/readme.txt | 15 +- 4 files changed, 865 insertions(+), 125 deletions(-) (limited to 'Game/Code/lib') 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) -- cgit v1.2.3