aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/lib/SQLite/SQLiteTable3.pas
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/Code/lib/SQLite/SQLiteTable3.pas
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 '')
-rw-r--r--Game/Code/lib/SQLite/SQLiteTable3.pas800
1 files changed, 737 insertions, 63 deletions
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.