{$mode objfpc}
{$h+}

{$IFNDEF FPC_DOTTEDUNITS}
unit SQLiteDB;
{$ENDIF FPC_DOTTEDUNITS}

interface

{$IFDEF FPC_DOTTEDUNITS}
uses  System.Classes,System.Strings,Api.Sqlite;
{$ELSE FPC_DOTTEDUNITS}
uses  Classes,strings,sqlite;
{$ENDIF FPC_DOTTEDUNITS}

type
  TSQLiteExecCallback = function(Sender: pointer; Columns: Integer; ColumnValues: PPAnsiChar; ColumnNames: PPAnsiChar): integer of object; cdecl;
  TSQLiteBusyCallback = function(Sender: TObject; ObjectName: PAnsiChar; BusyCount: integer): integer of object; cdecl;
  TOnData = Procedure(Sender: TObject; Columns: Integer; ColumnNames, ColumnValues: String)  of object;
  TOnBusy = Procedure(Sender: TObject; ObjectName: String; BusyCount: integer; var Cancel: Boolean) of object;
  TOnQueryComplete = Procedure(Sender: TObject) of object;

  TSQLite = class(TObject)
  private
    fSQLite: Pointer;
    fMsg: String;
    fIsOpen: Boolean;
    fBusy: Boolean;
    fError: Integer;
    fVersion: String;
    fEncoding: String;
    fTable: TStrings;
    fLstName: TStringList;
    fLstVal: TStringList;
    fOnData: TOnData;
    fOnBusy: TOnBusy;
    fOnQueryComplete: TOnQueryComplete;
    fBusyTimeout: integer;
    fPMsg: PAnsiChar;
    fChangeCount: integer;
        fNb_Champ :  Integer;
        fList_FieldName : TStringList;
        fList_Field : TList;
    procedure SetBusyTimeout(Timeout: integer);
  public
    constructor Create(DBFileName: String);
    destructor Destroy; override;
    function Query(Sql: String; Table: TStrings ): boolean;
    function ErrorMessage(ErrNo: Integer): string;
    function IsComplete(Sql: String): boolean;
    function LastInsertRow: integer;
    function Cancel: boolean;
    function DatabaseDetails(Table: TStrings): boolean;
    property LastErrorMessage: string read fMsg;
    property LastError: Integer read fError;
    property Version: String read fVersion;
    property Encoding: String read fEncoding;
    property OnData: TOnData read fOnData write fOnData;
    property OnBusy: TOnBusy read fOnBusy write fOnBusy;
    property OnQueryComplete: TOnQueryComplete read fOnQueryComplete write fOnQueryComplete;
    property BusyTimeout: Integer read fBusyTimeout write SetBusyTimeout;
    property ChangeCount: Integer read fChangeCount;
    property List_FieldName: TStringList read fList_FieldName write fList_FieldName;
    property List_Field: TList read fList_Field write fList_Field;
    property Nb_Champ: integer read fNb_Champ write fNb_Champ;

  procedure SQLOnData(Sender: TObject; Columns: Integer; ColumnNames, ColumnValues: String);

  end;
  function Pas2SQLStr(const PasString: string): string;
  function SQL2PasStr(const SQLString: string): string;
  function QuoteStr(const s: string; QuoteChar: AnsiChar ): string;
  function UnQuoteStr(const s: string; QuoteChar: AnsiChar ): string;
  procedure ValueList(const ColumnNames, ColumnValues: String; NameValuePairs: TStrings);

implementation

Const
  DblQuote: AnsiChar    = '"';
  SngQuote: AnsiChar    = #39;
  Crlf: String      = #13#10;
  Tab: AnsiChar         = #9;

var
  MsgNoError: String;

function QuoteStr(const s: string; QuoteChar: AnsiChar ): string;
begin
  Result := Concat(QuoteChar, s, QuoteChar);
end;

function UnQuoteStr(const s: string; QuoteChar: AnsiChar ): string;
begin
  Result := s;
  if length(Result) > 1 then
  begin
    if Result[1] = QuoteChar then
      Delete(Result, 1, 1);
    if Result[Length(Result)] = QuoteChar then
      Delete(Result, Length(Result), 1);
  end;
end;

function Pas2SQLStr(const PasString: string): string;
var
  n: integer;
begin
  Result := SQL2PasStr(PasString);
  n := Length(Result);
  while n > 0 do
  begin
    if Result[n] = SngQuote then
      Insert(SngQuote, Result, n);
    dec(n);
  end;
  Result := QuoteStr(Result,#39);
end;

function SQL2PasStr(const SQLString: string): string;
const
  DblSngQuote: String = #39#39;
var
  p: integer;
begin
  Result := SQLString;
  p := pos(DblSngQuote, Result);
  while p > 0 do
  begin
    Delete(Result, p, 1);
    p := pos(DblSngQuote, Result);
  end;
  Result := UnQuoteStr(Result,#39);
end;

procedure ValueList(const ColumnNames, ColumnValues: String; NameValuePairs: TStrings);
var
  n: integer;
  lstName, lstValue: TStringList;
begin
  if NameValuePairs <> nil then
  begin
    lstName := TStringList.Create;
    lstValue := TStringList.Create;
    lstName.CommaText := ColumnNames;
    lstValue.CommaText := ColumnValues;
    NameValuePairs.Clear;
    if lstName.Count = LstValue.Count then
      if lstName.Count > 0 then
        for n := 0 to lstName.Count - 1 do
          NameValuePairs.Append(Concat(lstName.Strings[n], '=', lstValue.Strings[n]));
    lstValue.Free;
    lstName.Free;
  end;
end;



function SystemErrorMsg(ErrNo: Integer ): String;
var
  buf: PAnsiChar;
  size: Integer;
  MsgLen: Integer;
begin
{  size := 256;
  GetMem(buf, size);
  If ErrNo = - 1 then
    ErrNo := GetLastError;
  MsgLen := FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil, ErrNo, 0, buf, size, nil);
  if MsgLen = 0 then
    Result := 'ERROR'
  else
    Result := buf;}
end;

function BusyCallback(Sender: pointer; ObjectName: PAnsiChar; BusyCount: integer): integer; cdecl;
var
  sObjName: String;
  bCancel: Boolean;
begin
  Result := -1;
  with TObject(Sender) as TSQLite do
  begin
    if Assigned(fOnBusy) then
    begin
      bCancel := False;
      sObjName := ObjectName;
      fOnBusy(Tobject(Sender), sObjName, BusyCount, bCancel);
      if bCancel then
        Result := 0;
    end;
  end;
end;

function ExecCallback(Sender: Pointer; Columns: Integer; ColumnValues: PPAnsiChar; ColumnNames: PPAnsiChar): integer; cdecl;
var
  PVal, PName: ^PAnsiChar;
  n: integer;
  sVal, sName: String;
begin
  Result := 0;
  with TObject(Sender) as TSQLite do
  begin
    if (Assigned(fOnData) or Assigned(fTable)) then
    begin
      fLstName.Clear;
      fLstVal.Clear;
      if Columns > 0 then
      begin
        PName := ColumnNames;
        PVal := ColumnValues;
        for n := 0 to Columns - 1 do
        begin
          fLstName.Append(PName^);
          fLstVal.Append(PVal^);
          inc(PName);
          inc(PVal);
        end;
      end;
      sVal := fLstVal.CommaText;
      sName := fLstName.CommaText;
      if Assigned(fOnData) then
        fOnData(TObject(Sender), Columns, sName, sVal);
      if Assigned(fTable) then
      begin
        if fTable.Count = 0 then
          fTable.Append(sName);
        fTable.Append(sVal);
      end;
    end;
  end;
end;


procedure TSQLite.SQLOnData(Sender: TObject; Columns: Integer; ColumnNames, ColumnValues: String);
Var i : Integer;
          InterS,val : String;
          Field : TStringList;

          function Pos1(a: String ; s : AnsiChar) : integer;
          var i,j : Integer;

          begin
          j:=-1;
                for i:=1 to length(a) Do
                begin
                        if a[i] = s then
                        begin
                                j:=i;
                                break;
                        end;
                end;
                result:=j;
          end;
begin
        If Nb_Champ = -1 Then
        Begin // Put the fields name in List_FieldName
                Nb_Champ:=Columns;
                InterS:=ColumnNames;
                While (Pos1(InterS,',') > 0)  do
                begin
                        val:=copy(InterS,1,Pos1(InterS,',')-1);
                        InterS:=copy(InterS,Pos1(InterS,',')+1,length(InterS));
                        List_FieldName.add(val);
                end;
                if length(InterS) > 0 then List_FieldName.add(InterS);
        end;
        // Put the list of TStringList of value
        Field :=TStringList.Create;
        InterS:=ColumnValues;
        While (Pos1(InterS,',') > 0)  do
        begin
                val:=copy(InterS,1,Pos1(InterS,',')-1);
                InterS:=copy(InterS,Pos1(InterS,',')+1,length(InterS));
                Field.add(val);
        end;
        if length(InterS) > 0 then Field.add(InterS);
        List_Field.add(Field);
end;

constructor TSQLite.Create(DBFileName: String);
var
  fPMsg1: PAnsiChar;
  name : PAnsiChar;
begin
  inherited Create;
  List_FieldName := TStringList.Create;
  List_Field := TList.Create;
  fError := SQLITE_ERROR;
  fIsOpen := False;
  fLstName := TStringList.Create;
  fLstVal := TStringList.Create;
  fOnData := nil;
  fOnBusy := nil;
  fOnQueryComplete := nil;
  fChangeCount := 0;
   name:=StrAlloc (length(DBFileName)+1);
   strpcopy(name,DBFileName);
   OnData:=@SQLOnData;
    fSQLite := SQLite_Open(name, 1, @fPMsg);
    SQLite_FreeMem(fPMsg);
    if fSQLite <> nil then
    begin
      //fVersion := String(SQLite_Version);
      //fEncoding := SQLite_Encoding;
      fIsOpen := True;
      fError := SQLITE_OK;
    end;
  fMsg := ErrorMessage(fError);
end;

destructor TSQLite.Destroy;
begin
  if fIsOpen then
    SQLite_Close(fSQLite);
  fIsOpen := False;
  fLstName.Free;
  fLstVal.Free;
  fSQLite := nil;
  fOnData := nil;
  fOnBusy := nil;
  fOnQueryComplete := nil;
  fLstName := nil;
  fLstVal := nil;
  List_FieldName.destroy;
  List_Field.destroy;
  inherited Destroy;
end;

function TSQLite.Query(Sql: String; Table: TStrings ): boolean;
//var
//  fPMsg: PAnsiChar;
//var Psql : PAnsiChar;
begin
  fError := SQLITE_ERROR;
  if fIsOpen then
  begin
    fPMsg := nil;
    fBusy := True;
    fTable := Table;
    if fTable <> nil then
      fTable.Clear;
   List_FieldName.clear;
   List_Field.clear;
   Nb_Champ:=-1;
    fError := SQLite_Exec(fSQLite, PAnsiChar(sql), @ExecCallback, Self, @fPMsg);
    SQLite_FreeMem(fPMsg);
    fChangeCount := SQLite_Changes(fSQLite);
    fTable := nil;
    fBusy := False;
    if Assigned(fOnQueryComplete) then
      fOnQueryComplete(Self);
  end;
  fMsg := ErrorMessage(fError);
  Result := (fError = SQLITE_OK);
end;

function TSQLite.Cancel: boolean;
begin
  Result := False;
  if fBusy and fIsOpen then
  begin
    do_SQLite_interrupt(fSQLite);
    fBusy := false;
    Result := True;
  end;
end;

procedure TSQLite.SetBusyTimeout(Timeout: Integer);
begin
  fBusyTimeout := Timeout;
  if fIsOpen then
  begin
    SQLite_Busy_Timeout(fSQLite, fBusyTimeout);
    if fBusyTimeout > 0 then
      SQLite_Busy_Handler(fSQLite, @BusyCallback, Self)
    else
      SQLite_Busy_Handler(fSQLite, nil, nil);
  end;
end;

function TSQLite.LastInsertRow: integer;
begin
  if fIsOpen then
    Result := SQLite_Last_Insert_RowID(fSQLite)
  else
    Result := -1;
end;

function TSQLite.ErrorMessage(ErrNo: Integer): string;
begin
  if ErrNo = 0 then
    Result := MsgNoError
  else
    Result := SQLite_Error_String(ErrNo);
end;

function TSQLite.IsComplete(Sql: String): boolean;
var Psql : PAnsiChar;
begin
  Psql:=StrAlloc (length(Sql)+1);
  strpcopy(Psql,Sql);
  Result := SQLite_Complete(Psql)<>0;
  strdispose(Psql);
end;

function TSQLite.DatabaseDetails(Table: TStrings): boolean;
begin
  Result := Query('SELECT * FROM SQLITE_MASTER;', Table);
end;

initialization

finalization

end.
