--- D:/daten/SQLiteTable3.pas Mon Oct 13 12:38:52 2008 +++ D:/daten/Projekte/ultrastardx/linuxtrunk/src/lib/SQLite/SQLiteTable3.pas Mon Oct 13 12:56:30 2008 @@ -54,12 +54,20 @@ Adapted by Tim Anderson (tim@itwriting.com) Originally created by Pablo Pissanetzky (pablo@myhtpc.net) Modified and enhanced by Lukas Gebauer + Modified and enhanced by Tobias Gunkel } interface +{$IFDEF FPC} + {$MODE Delphi}{$H+} +{$ENDIF} + uses - Windows, SQLite3, Classes, SysUtils; + {$IFDEF WIN32} + Windows, + {$ENDIF} + SQLite3, Classes, SysUtils; const @@ -102,23 +110,29 @@ FOnQuery: THookQuery; procedure RaiseError(s: string; SQL: string); procedure SetParams(Stmt: TSQLiteStmt); - function getRowsChanged: integer; + procedure BindData(Stmt: TSQLiteStmt; const Bindings: array of const); + function GetRowsChanged: integer; protected procedure SetSynchronised(Value: boolean); procedure DoQuery(value: string); public constructor Create(const FileName: string); destructor Destroy; override; - function GetTable(const SQL: string): TSQLiteTable; + 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; - function GetTableValue(const SQL: string): int64; - function GetTableString(const SQL: string): string; + 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 GetTableStrings(const SQL: string; const Value: TStrings); procedure UpdateBlob(const SQL: string; BlobData: TStream); procedure BeginTransaction; @@ -128,7 +142,7 @@ 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; @@ -139,7 +153,7 @@ 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; @@ -163,7 +177,8 @@ 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; @@ -196,7 +211,6 @@ private fColCount: cardinal; fCols: TStringList; - fColTypes: TList; fRow: cardinal; fEOF: boolean; fStmt: TSQLiteStmt; @@ -207,10 +221,12 @@ function GetFieldByName(FieldName: string): string; function GetFieldIndex(FieldName: string): 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; + function FieldAsBlobPtr(I: cardinal; out iNumBytes: integer): Pointer; function FieldAsBlobText(I: cardinal): string; function FieldIsNull(I: cardinal): boolean; function FieldAsString(I: cardinal): string; @@ -227,8 +243,10 @@ procedure DisposePointer(ptr: pointer); cdecl; +{$IFDEF WIN32} function SystemCollate(Userdta: pointer; Buf1Len: integer; Buf1: pointer; Buf2Len: integer; Buf2: pointer): integer; cdecl; +{$ENDIF} implementation @@ -238,12 +256,14 @@ 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 @@ -347,7 +367,126 @@ 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; @@ -361,6 +500,8 @@ RaiseError('Could not prepare SQL statement', SQL); DoQuery(SQL); SetParams(Stmt); + BindData(Stmt, Bindings); + iStepResult := Sqlite3_step(Stmt); if (iStepResult <> SQLITE_DONE) then begin @@ -417,7 +558,7 @@ procedure TSQLiteDatabase.BindSQL(Query: TSQLiteQuery; const Index: Integer; const Value: Integer); begin if Assigned(Query.Statement) then - Sqlite3_BindInt(Query.Statement, Index, Value) + sqlite3_Bind_Int(Query.Statement, Index, Value) else RaiseError('Could not bind integer to prepared SQL statement', Query.SQL); end; @@ -514,17 +655,32 @@ 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: TSQLiteUniTable; begin Result := 0; - Table := self.GetUniTable(SQL); + Table := self.GetUniTable(SQL, Bindings); try if not Table.EOF then Result := Table.FieldAsInteger(0); @@ -534,11 +690,16 @@ end; 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: TSQLiteUniTable; begin Result := ''; - Table := self.GetUniTable(SQL); + Table := self.GetUniTable(SQL, Bindings); try if not Table.EOF then Result := Table.FieldAsString(0); @@ -609,7 +770,7 @@ SQLite3_BusyTimeout(self.fDB, Value); end; -function TSQLiteDatabase.version: string; +function TSQLiteDatabase.Version: string; begin Result := SQLite3_Version; end; @@ -622,7 +783,9 @@ procedure TSQLiteDatabase.AddSystemCollate; begin + {$IFDEF WIN32} sqlite3_create_collation(fdb, 'SYSTEM', SQLITE_UTF16LE, nil, @SystemCollate); + {$ENDIF} end; procedure TSQLiteDatabase.ParamsClear; @@ -709,7 +872,7 @@ end; //database rows that were changed (or inserted or deleted) by the most recent SQL statement -function TSQLiteDatabase.getRowsChanged: integer; +function TSQLiteDatabase.GetRowsChanged: integer; begin Result := SQLite3_Changes(self.fDB); end; @@ -725,6 +888,11 @@ //------------------------------------------------------------------------------ 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; @@ -753,6 +921,8 @@ DB.RaiseError('Could not prepare SQL statement', SQL); DB.DoQuery(SQL); DB.SetParams(Stmt); + DB.BindData(Stmt, Bindings); + iStepResult := Sqlite3_step(Stmt); while (iStepResult <> SQLITE_DONE) do begin @@ -1122,6 +1292,7 @@ end; end; +{$WARNINGS OFF} function TSQLiteTable.MoveTo(position: cardinal): boolean; begin Result := False; @@ -1131,13 +1302,18 @@ 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; @@ -1156,36 +1332,14 @@ DB.RaiseError('Could not prepare SQL statement', SQL); DB.DoQuery(SQL); DB.SetParams(fStmt); + DB.BindData(fStmt, Bindings); //get data types fCols := TStringList.Create; - fColTypes := TList.Create; fColCount := SQLite3_ColumnCount(fstmt); for i := 0 to Pred(fColCount) do fCols.Add(AnsiUpperCase(Sqlite3_ColumnName(fstmt, i))); - for i := 0 to Pred(fColCount) do - begin - new(thisColType); - DeclaredColType := Sqlite3_ColumnDeclType(fstmt, i); - if DeclaredColType = nil then - thisColType^ := Sqlite3_ColumnType(fstmt, i) //use the actual column type instead - //seems to be needed for last_insert_rowid - else - if (DeclaredColType = 'INTEGER') or (DeclaredColType = 'BOOLEAN') then - thisColType^ := dtInt - else - if (DeclaredColType = 'NUMERIC') or - (DeclaredColType = 'FLOAT') or - (DeclaredColType = 'DOUBLE') or - (DeclaredColType = 'REAL') then - thisColType^ := dtNumeric - else - if DeclaredColType = 'BLOB' then - thisColType^ := dtBlob - else - thisColType^ := dtStr; - fColTypes.Add(thiscoltype); - end; + Next; end; @@ -1197,10 +1351,6 @@ Sqlite3_Finalize(fstmt); if Assigned(fCols) then fCols.Free; - if Assigned(fColTypes) then - for i := 0 to fColTypes.Count - 1 do - dispose(fColTypes[i]); - fColTypes.Free; inherited; end; @@ -1217,6 +1367,12 @@ 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;