{* * SQLite for Delphi and FreePascal/Lazarus * * This unit contains easy-to-use object wrapper over SQLite3 API functions * * Copyright 2010-2013 Yuri Plashenkov * * MIT License * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom * the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. *} unit SQLite3Wrap; {$IFDEF FPC} {$MODE DELPHI} {$ENDIF} interface uses SysUtils, Classes, SQLite3; type ESQLite3Error = class(Exception); TSQLite3Statement = class; TSQLite3BlobHandler = class; { TSQLite3Database class } TSQLite3Database = class(TObject) private FHandle: PSQLite3; FStatementList: TList; FBlobHandlerList: TList; FTransactionOpen: Boolean; procedure Check(const ErrCode: Integer); procedure CheckHandle; public constructor Create; destructor Destroy; override; procedure Open(const FileName: WideString; Flags: Integer = 0); procedure Close; procedure Execute(const SQL: WideString); function LastInsertRowID: Int64; function Prepare(const SQL: WideString): TSQLite3Statement; function BlobOpen(const Table, Column: WideString; const RowID: Int64; const WriteAccess: Boolean = True): TSQLite3BlobHandler; procedure BeginTransaction; procedure Commit; procedure Rollback; property Handle: PSQLite3 read FHandle; property TransactionOpen: Boolean read FTransactionOpen; end; { TSQLite3Statement class } TSQLite3Statement = class(TObject) private FHandle: PSQLite3Stmt; FOwnerDatabase: TSQLite3Database; function ParamIndexByName(const ParamName: WideString): Integer; public constructor Create(OwnerDatabase: TSQLite3Database; const SQL: WideString); destructor Destroy; override; procedure BindInt(const ParamIndex: Integer; const Value: Integer); overload; procedure BindInt64(const ParamIndex: Integer; const Value: Int64); overload; procedure BindDouble(const ParamIndex: Integer; const Value: Double); overload; procedure BindText(const ParamIndex: Integer; const Value: WideString); overload; procedure BindNull(const ParamIndex: Integer); overload; procedure BindBlob(const ParamIndex: Integer; Data: Pointer; const Size: Integer); overload; procedure BindZeroBlob(const ParamIndex: Integer; const Size: Integer); overload; procedure BindInt(const ParamName: WideString; const Value: Integer); overload; procedure BindInt64(const ParamName: WideString; const Value: Int64); overload; procedure BindDouble(const ParamName: WideString; const Value: Double); overload; procedure BindText(const ParamName: WideString; const Value: WideString); overload; procedure BindNull(const ParamName: WideString); overload; procedure BindBlob(const ParamName: WideString; Data: Pointer; const Size: Integer); overload; procedure BindZeroBlob(const ParamName: WideString; const Size: Integer); overload; procedure ClearBindings; function Step: Integer; procedure Reset; function StepAndReset: Integer; function ColumnCount: Integer; function ColumnName(const ColumnIndex: Integer): WideString; function ColumnType(const ColumnIndex: Integer): Integer; function ColumnInt(const ColumnIndex: Integer): Integer; function ColumnInt64(const ColumnIndex: Integer): Int64; function ColumnDouble(const ColumnIndex: Integer): Double; function ColumnText(const ColumnIndex: Integer): WideString; function ColumnBlob(const ColumnIndex: Integer): Pointer; function ColumnBytes(const ColumnIndex: Integer): Integer; property Handle: PSQLite3Stmt read FHandle; property OwnerDatabase: TSQLite3Database read FOwnerDatabase; end; { TSQLite3BlobHandler class } TSQLite3BlobHandler = class(TObject) private FHandle: PSQLite3Blob; FOwnerDatabase: TSQLite3Database; public constructor Create(OwnerDatabase: TSQLite3Database; const Table, Column: WideString; const RowID: Int64; const WriteAccess: Boolean = True); destructor Destroy; override; function Bytes: Integer; procedure Read(Buffer: Pointer; const Size, Offset: Integer); procedure Write(Buffer: Pointer; const Size, Offset: Integer); property Handle: PSQLite3Blob read FHandle; property OwnerDatabase: TSQLite3Database read FOwnerDatabase; end; implementation uses SQLite3Utils; resourcestring SErrorMessage = 'SQLite3 error: %s'; SDatabaseNotConnected = 'SQLite3 error: database is not connected.'; STransactionAlreadyOpen = 'Transaction is already opened.'; SNoTransactionOpen = 'No transaction is open'; { TSQLite3Database } procedure TSQLite3Database.BeginTransaction; begin if not FTransactionOpen then begin Execute('BEGIN TRANSACTION;'); FTransactionOpen := True; end else raise ESQLite3Error.Create(STransactionAlreadyOpen); end; function TSQLite3Database.BlobOpen(const Table, Column: WideString; const RowID: Int64; const WriteAccess: Boolean): TSQLite3BlobHandler; begin Result := TSQLite3BlobHandler.Create(Self, Table, Column, RowID, WriteAccess); end; procedure TSQLite3Database.Check(const ErrCode: Integer); begin if ErrCode <> SQLITE_OK then raise ESQLite3Error.CreateFmt(SErrorMessage, [UTF8ToStr(sqlite3_errmsg(FHandle))]); end; procedure TSQLite3Database.CheckHandle; begin if FHandle = nil then raise ESQLite3Error.Create(SDatabaseNotConnected); end; procedure TSQLite3Database.Close; var I: Integer; begin if FHandle <> nil then begin if FTransactionOpen then Rollback; // Delete all statements for I := FStatementList.Count - 1 downto 0 do TSQLite3Statement(FStatementList[I]).Free; // Delete all blob handlers for I := FBlobHandlerList.Count - 1 downto 0 do TSQLite3BlobHandler(FBlobHandlerList[I]).Free; sqlite3_close(FHandle); FHandle := nil; end; end; procedure TSQLite3Database.Commit; begin if FTransactionOpen then begin Execute('COMMIT;'); FTransactionOpen := False; end else raise ESQLite3Error.Create(SNoTransactionOpen); end; constructor TSQLite3Database.Create; begin FHandle := nil; FStatementList := TList.Create; FBlobHandlerList := TList.Create; end; destructor TSQLite3Database.Destroy; begin Close; FBlobHandlerList.Free; FStatementList.Free; inherited; end; procedure TSQLite3Database.Execute(const SQL: WideString); begin CheckHandle; Check(sqlite3_exec(FHandle, PAnsiChar(StrToUTF8(SQL)), nil, nil, nil)); end; function TSQLite3Database.LastInsertRowID: Int64; begin CheckHandle; Result := sqlite3_last_insert_rowid(FHandle); end; procedure TSQLite3Database.Open(const FileName: WideString; Flags: Integer); begin Close; if Flags = 0 then Check(sqlite3_open(PAnsiChar(StrToUTF8(FileName)), FHandle)) else Check(sqlite3_open_v2(PAnsiChar(StrToUTF8(FileName)), FHandle, Flags, nil)); end; function TSQLite3Database.Prepare(const SQL: WideString): TSQLite3Statement; begin Result := TSQLite3Statement.Create(Self, SQL); end; procedure TSQLite3Database.Rollback; begin if FTransactionOpen then begin Execute('ROLLBACK;'); FTransactionOpen := False; end else raise ESQLite3Error.Create(SNoTransactionOpen); end; { TSQLite3Statement } procedure TSQLite3Statement.BindBlob(const ParamIndex: Integer; Data: Pointer; const Size: Integer); begin FOwnerDatabase.Check(sqlite3_bind_blob(FHandle, ParamIndex, Data, Size, SQLITE_TRANSIENT)); end; procedure TSQLite3Statement.BindDouble(const ParamIndex: Integer; const Value: Double); begin FOwnerDatabase.Check(sqlite3_bind_double(FHandle, ParamIndex, Value)); end; procedure TSQLite3Statement.BindInt(const ParamIndex, Value: Integer); begin FOwnerDatabase.Check(sqlite3_bind_int(FHandle, ParamIndex, Value)); end; procedure TSQLite3Statement.BindInt64(const ParamIndex: Integer; const Value: Int64); begin FOwnerDatabase.Check(sqlite3_bind_int64(FHandle, ParamIndex, Value)); end; procedure TSQLite3Statement.BindNull(const ParamIndex: Integer); begin FOwnerDatabase.Check(sqlite3_bind_null(FHandle, ParamIndex)); end; procedure TSQLite3Statement.BindText(const ParamIndex: Integer; const Value: WideString); var S: AnsiString; { UTF-8 string } begin S := StrToUTF8(Value); FOwnerDatabase.Check( sqlite3_bind_text(FHandle, ParamIndex, PAnsiChar(S), Length(S), SQLITE_TRANSIENT) ); end; procedure TSQLite3Statement.BindZeroBlob(const ParamIndex, Size: Integer); begin FOwnerDatabase.Check(sqlite3_bind_zeroblob(FHandle, ParamIndex, Size)); end; procedure TSQLite3Statement.ClearBindings; begin FOwnerDatabase.Check(sqlite3_clear_bindings(FHandle)); end; function TSQLite3Statement.ColumnBlob(const ColumnIndex: Integer): Pointer; begin Result := sqlite3_column_blob(FHandle, ColumnIndex); end; function TSQLite3Statement.ColumnBytes(const ColumnIndex: Integer): Integer; begin Result := sqlite3_column_bytes(FHandle, ColumnIndex); end; function TSQLite3Statement.ColumnCount: Integer; begin Result := sqlite3_column_count(FHandle); end; function TSQLite3Statement.ColumnDouble(const ColumnIndex: Integer): Double; begin Result := sqlite3_column_double(FHandle, ColumnIndex); end; function TSQLite3Statement.ColumnInt(const ColumnIndex: Integer): Integer; begin Result := sqlite3_column_int(FHandle, ColumnIndex); end; function TSQLite3Statement.ColumnInt64(const ColumnIndex: Integer): Int64; begin Result := sqlite3_column_int64(FHandle, ColumnIndex); end; function TSQLite3Statement.ColumnName(const ColumnIndex: Integer): WideString; begin Result := UTF8ToStr(sqlite3_column_name(FHandle, ColumnIndex)); end; function TSQLite3Statement.ColumnText(const ColumnIndex: Integer): WideString; var Len: Integer; begin Len := ColumnBytes(ColumnIndex); Result := UTF8ToStr(sqlite3_column_text(FHandle, ColumnIndex), Len); end; function TSQLite3Statement.ColumnType(const ColumnIndex: Integer): Integer; begin Result := sqlite3_column_type(FHandle, ColumnIndex); end; constructor TSQLite3Statement.Create(OwnerDatabase: TSQLite3Database; const SQL: WideString); begin FOwnerDatabase := OwnerDatabase; FOwnerDatabase.CheckHandle; FOwnerDatabase.Check( sqlite3_prepare_v2(FOwnerDatabase.Handle, PAnsiChar(StrToUTF8(SQL)), -1, FHandle, nil) ); FOwnerDatabase.FStatementList.Add(Self); end; destructor TSQLite3Statement.Destroy; begin FOwnerDatabase.FStatementList.Remove(Self); sqlite3_finalize(FHandle); inherited; end; function TSQLite3Statement.ParamIndexByName(const ParamName: WideString): Integer; begin Result := sqlite3_bind_parameter_index(FHandle, PAnsiChar(StrToUTF8(ParamName))); end; procedure TSQLite3Statement.Reset; begin sqlite3_reset(FHandle); end; function TSQLite3Statement.Step: Integer; begin Result := sqlite3_step(FHandle); end; function TSQLite3Statement.StepAndReset: Integer; begin Result := Step; Reset; end; procedure TSQLite3Statement.BindBlob(const ParamName: WideString; Data: Pointer; const Size: Integer); begin BindBlob(ParamIndexByName(ParamName), Data, Size); end; procedure TSQLite3Statement.BindDouble(const ParamName: WideString; const Value: Double); begin BindDouble(ParamIndexByName(ParamName), Value); end; procedure TSQLite3Statement.BindInt(const ParamName: WideString; const Value: Integer); begin BindInt(ParamIndexByName(ParamName), Value); end; procedure TSQLite3Statement.BindInt64(const ParamName: WideString; const Value: Int64); begin BindInt64(ParamIndexByName(ParamName), Value); end; procedure TSQLite3Statement.BindNull(const ParamName: WideString); begin BindNull(ParamIndexByName(ParamName)); end; procedure TSQLite3Statement.BindText(const ParamName, Value: WideString); begin BindText(ParamIndexByName(ParamName), Value); end; procedure TSQLite3Statement.BindZeroBlob(const ParamName: WideString; const Size: Integer); begin BindZeroBlob(ParamIndexByName(ParamName), Size); end; { TSQLite3BlobHandler } function TSQLite3BlobHandler.Bytes: Integer; begin Result := sqlite3_blob_bytes(FHandle); end; constructor TSQLite3BlobHandler.Create(OwnerDatabase: TSQLite3Database; const Table, Column: WideString; const RowID: Int64; const WriteAccess: Boolean); begin FOwnerDatabase := OwnerDatabase; FOwnerDatabase.CheckHandle; FOwnerDatabase.Check( sqlite3_blob_open(FOwnerDatabase.FHandle, 'main', PAnsiChar(StrToUTF8(Table)), PAnsiChar(StrToUTF8(Column)), RowID, Ord(WriteAccess), FHandle) ); FOwnerDatabase.FBlobHandlerList.Add(Self); end; destructor TSQLite3BlobHandler.Destroy; begin FOwnerDatabase.FBlobHandlerList.Remove(Self); sqlite3_blob_close(FHandle); inherited; end; procedure TSQLite3BlobHandler.Read(Buffer: Pointer; const Size, Offset: Integer); begin FOwnerDatabase.Check(sqlite3_blob_read(FHandle, Buffer, Size, Offset)); end; procedure TSQLite3BlobHandler.Write(Buffer: Pointer; const Size, Offset: Integer); begin FOwnerDatabase.Check(sqlite3_blob_write(FHandle, Buffer, Size, Offset)); end; end.