
//////////////////////////////////////////////////
//  SQLite Data Access Components
//  Copyright © 2008-2014 Devart. All right reserved.
//////////////////////////////////////////////////

{$IFNDEF CLR}

{$I LiteDac.inc}
unit LiteClassesUni;
{$ENDIF}

interface

uses
{$IFDEF MSWINDOWS}
  Windows,
{$ENDIF}
{$IFDEF CLR}
  System.Text, System.Runtime.InteropServices,
{$ELSE}
  CLRClasses,
{$ENDIF}
  Classes, SysUtils, Variants, SyncObjs, FMTBcd,
{$IFDEF VER12P}
{$IFNDEF NEXTGEN}
  AnsiStrings,
{$ENDIF}
{$ENDIF}
  CRTypes, MemData, CRAccess, CRParser,
{$IFNDEF LITE}
  CRDataTypeMap,
{$ENDIF}
{$IFNDEF UNIDACPRO}
  {$IFNDEF LITE}LiteFunction,{$ENDIF} LiteCall, LiteError, LiteParser, LiteCollation;
{$ELSE}
  {$IFNDEF LITE}LiteFunctionUni,{$ENDIF} LiteCallUni, LiteErrorUni, LiteParserUni, LiteCollationUni;
{$ENDIF}

const
  prLiteBase = 1000;
  prUseUnicode = prLiteBase + 1;
  prClientLibrary = prLiteBase + 2;
  prDumpData = prLiteBase + 3;
  prEncryptionKey = prLiteBase + 4;
  prASCIIDataBase = prLiteBase + 5;
{$IFDEF MSWINDOWS}
  prEnableSharedCache = prLiteBase + 6;
{$ENDIF}
  prBusyTimeout =  prLiteBase + 7;
  prReadUncommitted = prLiteBase + 8;
{$IFDEF LITE}
  prNewEncryptionKey = prLiteBase + 9;
{$ENDIF}
  prDefaultCollations = prLiteBase + 10;
  prDateFormat = prLiteBase + 11;
  prTimeFormat = prLiteBase + 12;
  prForeignKeys = prLiteBase + 13;
{$IFNDEF NOSTATIC}
  prStaticLibrary = prLiteBase + 14;
{$ENDIF}
  prForceCreateDatabase = prLiteBase + 15;
{$IFDEF CODEC}
  prEncryptionAlgorithm = prLiteBase + 16;
{$ENDIF}
  prAutoCommitRowCount = prLiteBase + 17;
  prNativeDate = prLiteBase + 18;
  prEnableLoadExtension = prLiteBase + 19;

type
  TSQLiteCommand = class;
  TSQLiteTypes = class;

  TSQLiteConnection = class(TCRConnection)
  private
    FCommand: TSQLiteCommand;
    FLiteTypes: TSQLiteTypes;
    FCollationManager: TSQLiteCollationManager;
  {$IFNDEF LITE}
    FFunctionManager: TSQLiteFunctionManager;
  {$ENDIF}
    FDatabase: string;
    FClientLibrary: string;
    FUseUnicode: boolean;
    FASCIIDataBase: boolean;
    FEncryptionKey: string;
    FDateFormat: string;
    FTimeFormat: string;
    FNativeDate: boolean;
  {$IFDEF LITE}
    FChangeEncryptionKey: boolean; // if need change encryption key
    FNewEncryptionKey: string; // for change password in dbExpres
  {$ENDIF}
  {$IFDEF MSWINDOWS}
    FEnableSharedCache: boolean;
  {$ENDIF}
    FEnableLoadExtension: boolean;
    FBusyTimeout: integer;
    FReadUncommitted: boolean;
    FDefaultCollations: boolean;
    FLastInsertId: int64;
    FForeignKeys : boolean;
  {$IFNDEF NOSTATIC}
    FStaticLibrary: boolean;
  {$ENDIF}
    FForceCreateDatabase: boolean;
  {$IFDEF CODEC}
    FEncryptionAlgorithm: TLiteEncryptionAlgorithm;
  {$ENDIF}
    FAPI: TSQLite3API;

    procedure SetASCIIDataBase(const Value: boolean);
    procedure SetBusyTimeout(const Value: integer);
    procedure SetReadUncommitted(const Value: boolean);
    procedure SetDefaultCollations(const Value: boolean);
    procedure SetForeignKeys(const Value: boolean);
  {$IFNDEF NOSTATIC}
    procedure SetUseStaticLibrary(const Value: boolean);
  {$ENDIF}
    procedure CheckCommand;
  protected
    procedure InternalSetBusyTimeout(const Value: integer);
    procedure InternalSetReadUncommitted(const Value: boolean);
    procedure InternalSetDefaultCollations(const Value: boolean);
    procedure InternalSetForeignKeys(const Value: boolean);

    property EnableBCD;
  {$IFDEF VER6P}
  {$IFNDEF FPC}
    property EnableFMTBCD;
  {$ENDIF}
  {$ENDIF}

    procedure DecryptDatabase(Key: string);
  public
    constructor Create; override;
    destructor Destroy; override;

    function GetCommandClass: TCRCommandClass; override;
    function GetTransactionClass: TCRTransactionClass; override;
    procedure ExecCommand(const SQL: string);

    function SetProp(Prop: integer; const Value: variant): boolean; override;
    function GetProp(Prop: integer; out Value: variant): boolean; override;

    procedure Check(ErrorCode: integer);
    procedure ProcessError(ErrorCode: integer; ErrorMsg: string; Component: TObject); virtual;

    procedure Connect(const ConnectString: string); override;
    procedure Disconnect; override;
    procedure Ping; override;
    function CheckIsValid: boolean; override;

    procedure Assign(Source: TCRConnection); override;
    procedure AssignConnect(Source: TCRConnection); override;

    function GetServerVersion: string; override;
    function GetServerVersionFull: string; override;
    function GetClientVersion: string; override;
    function CanChangeDatabase: boolean; override;
    function GetTypes: TSQLiteTypes;
    function GetCollationManager: TSQLiteCollationManager;
  {$IFNDEF LITE}
    function GetFunctionManager: TSQLiteFunctionManager;
  {$ENDIF}

    { String encoding}
    function IsUnicodeDataBase: boolean;
    function EncodeString(AString: AnsiString): AnsiString; overload;
    function EncodeString(AString: AnsiString; UseUTF8: boolean): AnsiString; overload;
    function EncodeString(WString: WideString): AnsiString; overload;
    function EncodeString(WString: WideString; UseUTF8: boolean): AnsiString; overload;
    function DecodeStringA(AString: AnsiString): AnsiString; overload;
    function DecodeStringA(AString: AnsiString; UseUTF8: boolean): AnsiString; overload;
    function DecodeStringW(AString: AnsiString): WideString; overload;
    function DecodeStringW(AString: AnsiString; UseUTF8: boolean): WideString; overload;

    { SQLite encryption }
    procedure EncryptDatabase(NewKey: string);

    property ASCIIDataBase: boolean read FASCIIDataBase;
    property UseUnicode: boolean read FUseUnicode;
    property DateFormat: string read FDateFormat;
    property TimeFormat: string read FTimeFormat;
    property NativeDate: boolean read FNativeDate;
    property ForeignKeys: boolean read FForeignKeys;
  {$IFNDEF NOSTATIC}
    property UseStaticLibrary: Boolean read FStaticLibrary;
  {$ENDIF}
  {$IFDEF CODEC}
    property EncryptionAlgorithm: TLiteEncryptionAlgorithm read FEncryptionAlgorithm;
  {$ENDIF}
    property API: TSQLite3API read FAPI;
  end;

  TSQLiteTransaction = class(TCRTransaction)
  public
    procedure StartTransaction; override;
    procedure Commit; override;
    procedure Rollback; override;

    procedure Savepoint(const Name: string); override;
    procedure ReleaseSavepoint(const Name: string); override;
    procedure RollbackToSavepoint(const Name: string); override;
  end;

  TSQLiteTypes = class
  private
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FConnection: TSQLiteConnection;

    procedure GetTypeAttr(DataType: Integer; TypeModifier: Integer;
      var Length, Scale: integer);
  protected
  public
    constructor Create(Connection: TSQLiteConnection);
    destructor Destroy; override;

    procedure DetectDataType(TypeOID, TypeModifier: integer;
      var DataType, SubDataType: word; var Length, Scale, Size: integer;
      var Fixed: boolean; var ObjectType: TObjectType;
      LongStrings, FlatBuffers, EnableBCD, EnableFMTBCD,
      CursorAsString, FieldsAsText: boolean);
    function GetInternalType(TypeCode: integer): word; overload;
    function GetInternalType(TypeCode: integer; var ObjectType: TObjectType): word; overload;

    function GetSQLiteType(DataType: word; var NeedConvertToText: boolean): word;
    function GetVarType(VarType: TVarType): word;
    function ConvertToText(DataType: word; Value: Variant): AnsiString;
    function ConvertBlob(Value: Variant; var DestrType: IntPtr): TBytes;
    function ConvertMemo(Value: Variant): AnsiString;
  end;

  TSQLiteCommand = class(TCRCommand)
  private
    FCommandSQL: AnsiString;
    FPCommandSQL,
    FCurrentCommand,
    FCommandTail: IntPtr;
    FCommandLength: integer;
  protected
    procedure BindParams;
    procedure BindBlob(Index: integer; const Value: variant);
    procedure BindMemo(Index: integer; const Value: variant);
  private
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FConnection: TSQLiteConnection;
    FStmt: pSQLite3Stmt;
    FCursorState: TCursorState;
    FExecResult: integer;
    FRowsAffected: integer;
    FLockAfterExecute: boolean;
    FDateSeparator: Char;
    FShortDateFormat: string;
    FTimeSeparator: Char;
    FShortTimeFormat: string;
  protected
    procedure Check(ErrorCode: integer);

    property EnableBCD;
  {$IFDEF VER6P}
  {$IFNDEF FPC}
    property EnableFMTBCD;
  {$ENDIF}
  {$ENDIF}

    procedure InitFormatSettings;
    procedure ResetFormatSettings;

  public
    constructor Create; override;
    destructor Destroy; override;

    class function GetSQLInfoClass: TSQLInfoClass; override;
    class function GetTableInfoClass: TTableInfoClass; override;
    class function GetParserClass: TSQLParserClass; override;
  {$IFNDEF LITE}
    class function GetMapRulesClass: TCRMapRulesClass; override;
  {$ENDIF}

    function SetProp(Prop: integer; const Value: variant): boolean; override;
    function GetProp(Prop: integer; out Value: variant): boolean; override;

    procedure SetConnection(Value: TCRConnection); override;

    procedure Prepare; override;
    procedure Unprepare; override;
    function GetPrepared: boolean; override;
    procedure Execute(Iters: integer = 1); override;
    procedure SetSQL(const Value: string); override;
    function ParseSQL(const SQL: string; Params: TParamDescs; ReplaceAll: boolean = True; const RenamePrefix: string = ''): string; override;
    procedure InitProcParams(const Name: string; Overload: integer);
    function CreateProcCall(const Name: string; NeedDescribe: boolean; IsQuery: boolean): string; override;

    function GetCursorState: TCursorState; override;
    procedure SetCursorState(Value: TCursorState); override;
  end;

  TSQLiteRecordSet = class(TCRRecordSet)
  private
    FCommand: TSQLiteCommand;

    FDumpData: boolean;

    procedure DescribeFieldDesc(Field: TCRFieldDesc; Index: integer);
    procedure ReadFieldValues(RecBuf: IntPtr);
    procedure ReadFieldValuesForDump(RecBuf: IntPtr);

  protected
    procedure Check(ErrorCode: integer);
    procedure CreateCommand; override;
    procedure SetCommand(Value: TCRCommand); override;
    procedure InternalPrepare; override;
    procedure InternalUnPrepare; override;
    procedure CreateFieldDescs; override;
    procedure InternalClose; override;
    function IdentityFieldIsData: boolean; override;

  { Fetch }
    procedure FetchBlock(Block: PBlockHeader; FetchBack: boolean; out RowsObtained: Integer); override;
    function NeedUnPrepareAfterFetch: boolean; override;

  public
    constructor Create; override;
    destructor Destroy; override;

    function SetProp(Prop: integer; const Value: variant): boolean; override;
    function GetProp(Prop: integer; out Value: variant): boolean; override;

    procedure DetectIdentityField; override;
    procedure ExecCommand; override;
    function IsFullReopen: boolean; override;
    procedure Reopen; override;
    procedure SetToEnd; override;
  end;

  TSQLiteTableInfo = class(TCRTableInfo)
  protected
    FTableNameFull: string;
    function GetTableNameFull: string; override;
    procedure SetTableNameFull(const Value: string); override;
  end;

{ TSQLiteMetaData }

  TColumnInfo = record
    Name: string;
    DataType: string;
    IsAutoincrement: boolean;
    Default: string;
  end;

  TIndexColumnInfo = record
    ColumnIndex: integer;
    IsDesc: boolean;
  end;

  TIndexType = (itPrimaryKey, itUnique, itNonUnique);

  TIndexInfo = record
    IndexType: TIndexType;
    ColumnInfo: array of TIndexColumnInfo;
  end;

  TTableMembers = record
    Columns: array of TColumnInfo;
    Indexes: array of TIndexInfo;
  end;

{$IFDEF LITE}
  {$DEFINE DBX_METADATA}
{$ENDIF}

  TSQLiteMetaData = class (TCRMetaData)
  protected
    function GetConnection: TSQLiteConnection;
    function GetTypesForSQL(const ObjectTypes: string; AllTypes: array of string): string;
    function CreateRecordSet: TCRRecordSet; override;
    procedure ParseTableSQL(const SQL: string; var Members: TTableMembers);
    procedure ParseIndexSQL(const SQL: string; var Members: TTableMembers);

    function GetTables(Restrictions: TStrings): TData; override;
    function GetColumns(Restrictions: TStrings): TData; override;
    function GetProcedures(Restrictions: TStrings): TData; override;
    function GetProcedureParameters(Restrictions: TStrings): TData; override;
    function GetIndexes(Restrictions: TStrings): TData; override;
    function GetIndexColumns(Restrictions: TStrings): TData; override;
    function GetConstraints(Restrictions: TStrings): TData; override;

  public
    constructor Create; override;
    destructor Destroy; override;
  end;

  TSQLiteInfo = class (TSQLInfo)
  public
    function LeftQuote: Char; override;
    function RightQuote: Char; override;
    function IdentCase: TIdentCase; override;
    function ParamQuoteAllowed: boolean; override;
    function IsQuoted(const Value: string): boolean; override;
    procedure ParseTablesInfo(const SQL: string; TablesInfo: TCRTablesInfo); override;
  end;

{$IFNDEF LITE}
  TSQLiteLoader = class(TCRSimpleLoader)
  private
    FAutoCommit,
    FAutoCommitIsUsed: boolean;
    FAutoCommitRowCount: integer;
  protected
    procedure CreateCommand; override;
    class function GetRecordSetClass: TCRRecordSetClass; override;
    procedure DoPrepare; override;
    procedure DoLoadRow; override;
    function UsedConnection: TSQLiteConnection;
    function UsedTransaction: TSQLiteTransaction;
  public
    constructor Create; override;
    destructor Destroy; override;

    function SetProp(Prop: integer; const Value: variant): boolean; override;
    function GetProp(Prop: integer; var Value: variant): boolean; override;
  end;
{$ENDIF}

var
  SQLiteInfo: TSQLiteInfo;

const
  DACProductName = 'LiteDAC';


implementation

uses
  Math,
{$IFDEF MSWINDOWS}
  Registry,
{$ENDIF}
  StrUtils, CRFunctions, MemUtils, DAConsts, DateUtils,
{$IFNDEF UNIDACPRO}
  LiteConsts{$IFNDEF LITE}, LiteDataTypeMap{$ENDIF}{$IFNDEF NOSTATIC}, LiteStatic{$ENDIF};
{$ELSE}
  LiteConstsUni, LiteDataTypeMapUni{$IFNDEF NOSTATIC}, LiteStaticUni{$ENDIF};
{$ENDIF}

var
  //ActiveConnectionsCount: integer;
  hLockConnectCount: TCriticalSection;


{$IFDEF FPC}
{$IFNDEF UNIX}
function FileExists(const Filename: string): boolean;
var
  Attr: Longint;
begin
  Attr := Integer(Windows.GetFileAttributesW(PWideChar(CRFunctions.UTF8Decode(FileName))));
  if Attr <> -1 then
    Result:= (Attr and FILE_ATTRIBUTE_DIRECTORY) = 0
  else
    Result:=False;
end;
{$ENDIF}
{$ENDIF}

{ TSQLiteConnection }

constructor TSQLiteConnection.Create;
begin
  inherited;

  FBusyTimeout := 0;
{$IFDEF MSWINDOWS}
  FEnableSharedCache := False;
{$ENDIF}
  FEnableLoadExtension := False;
  FEncryptionKey := '';
{$IFDEF LITE}
  FNewEncryptionKey := '';
{$ENDIF}
  FReadUncommitted := False;
  FCommand := nil;
  FLiteTypes := nil;
  FCollationManager := nil;
  FForeignKeys := True;
  FDefaultCollations := True;
{$IFNDEF LITE}
  FFunctionManager := nil;
{$ENDIF}
{$IFNDEF NOSTATIC}
  FStaticLibrary := False;
{$ENDIF}
  FForceCreateDatabase := False;
  FAPI := TSQLite3API.Create;
  FNativeDate := True;
end;

destructor TSQLiteConnection.Destroy;
begin
  Disconnect;
{$IFNDEF LITE}
  FFunctionManager.Free;
{$ENDIF}
  FCollationManager.Free;
  FLiteTypes.Free;
  FCommand.Free;
  FAPI.Free;

  inherited;
end;

procedure TSQLiteConnection.SetASCIIDataBase(const Value: boolean);
begin
  if FASCIIDataBase <> Value then
    Disconnect;

  FASCIIDataBase := Value;
end;

procedure TSQLiteConnection.SetBusyTimeout(const Value: integer);
begin
  FBusyTimeout := Value;
  if FConnected then
    InternalSetBusyTimeout(FBusyTimeout);
end;

procedure TSQLiteConnection.SetReadUncommitted(const Value: boolean);
begin
  FReadUncommitted := Value;
  if FConnected then
    InternalSetReadUncommitted(FReadUncommitted);
end;

{$IFNDEF NOSTATIC}
procedure TSQLiteConnection.SetUseStaticLibrary(const Value: boolean);
begin
  if Value <> FStaticLibrary then begin
    Disconnect;

    if FAPI.Direct <> Value then
      if FAPI.Initialized then
        FAPI.UnInitialize;

    FStaticLibrary := Value;

{$IFDEF CODEC}
    if not Value then
      if EncryptionAlgorithm <> DefaultEncryptionAlgorithm then
        SetProp(prEncryptionAlgorithm, DefaultEncryptionAlgorithm);
{$ENDIF}
  end;
end;
{$ENDIF}

procedure TSQLiteConnection.SetDefaultCollations(const Value: boolean);
begin
  FDefaultCollations := Value;
  if FConnected then
    InternalSetDefaultCollations(Value);
end;

procedure TSQLiteConnection.SetForeignKeys(const Value: boolean);
begin
  FForeignKeys := Value;
  if FConnected then
    InternalSetForeignKeys(FForeignKeys);
end;

procedure TSQLiteConnection.CheckCommand;
begin
  if FCommand = nil then begin
    FCommand := TSQLiteCommand.Create;
    FCommand.SetConnection(Self);
  end;
end;

procedure TSQLiteConnection.ExecCommand(const SQL: string);
begin
  CheckCommand;

  FCommand.SetSQL(SQL);
  FCommand.Execute;
end;

procedure TSQLiteConnection.InternalSetBusyTimeout(const Value: integer);
begin
  Check(FAPI.sqlite3_busy_timeout(FAPI.SQLite, FBusyTimeout));
end;

procedure TSQLiteConnection.InternalSetReadUncommitted(const Value: boolean);
begin
  if Value then
    ExecCommand('PRAGMA read_uncommitted = true')
  else
    ExecCommand('PRAGMA read_uncommitted = false');
end;

procedure TSQLiteConnection.InternalSetDefaultCollations(const Value: boolean);
begin
  if Value then
    GetCollationManager.RegisterDefaultCollations
  else
    GetCollationManager.UnRegisterDefaultCollations
end;

procedure TSQLiteConnection.InternalSetForeignKeys(const Value: boolean);
begin
  if Value then
    ExecCommand('PRAGMA foreign_keys = ON')
  else
    ExecCommand('PRAGMA foreign_keys = OFF');
end;

function TSQLiteConnection.SetProp(Prop: integer; const Value: variant): boolean;
begin
  Result := True;
  case Prop of
    prDatabase:
      FDatabase := Value;
    prClientLibrary:
      FClientLibrary := Value;
    prUseUnicode:
      FUseUnicode := Value;
    prASCIIDataBase:
      SetASCIIDataBase(Value);
    prEncryptionKey:
      FEncryptionKey := Value;
{$IFDEF LITE}
    prNewEncryptionKey: begin
      FChangeEncryptionKey := True;
      FNewEncryptionKey := Value;
    end;
{$ENDIF}
{$IFDEF MSWINDOWS}
    prEnableSharedCache:
      FEnableSharedCache:= Value;
{$ENDIF}
    prEnableLoadExtension:
      FEnableLoadExtension := Value;
    prBusyTimeout:
      SetBusyTimeout(Value);
    prReadUncommitted:
      SetReadUncommitted(Value);
    prDefaultCollations:
      SetDefaultCollations(Value);
    prDateFormat:
      FDateFormat := Value;
    prTimeFormat:
      FTimeFormat := Value;
    prNativeDate:
      FNativeDate := Value;
    prForeignKeys:
      SetForeignKeys(Value);
{$IFNDEF NOSTATIC}
    prStaticLibrary:
      SetUseStaticLibrary(Value);
{$ENDIF}
    prForceCreateDatabase:
      FForceCreateDatabase := Value;
{$IFDEF CODEC}
    prEncryptionAlgorithm:
      FEncryptionAlgorithm := Value;
{$ENDIF}
  else
    Result := inherited SetProp(Prop, Value);
  end;
end;

function TSQLiteConnection.GetProp(Prop: integer; out Value: variant): boolean;
begin
  Result := True;
  case Prop of
    prDatabase:
      Value := FDatabase;
    prClientLibrary:
      Value := FClientLibrary;
    prUseUnicode:
      Value := FUseUnicode;
    prASCIIDataBase:
      Value := FASCIIDataBase;
    prLastInsertId:
      Value := FLastInsertId;
{$IFDEF MSWINDOWS}
    prEnableSharedCache:
      Value := FEnableSharedCache;
{$ENDIF}
    prEnableLoadExtension:
      Value := FEnableLoadExtension;
    prEncryptionKey:
      Value := FEncryptionKey;
{$IFDEF LITE}
    prNewEncryptionKey:
      Value := FNewEncryptionKey;
{$ENDIF}
    prBusyTimeout:
      Value := FBusyTimeout;
    prReadUncommitted:
      Value := FReadUncommitted;
    prDefaultCollations:
      Value := FDefaultCollations;
    prDateFormat:
      Value := FDateFormat;
    prTimeFormat:
      Value := FTimeFormat;
    prNativeDate:
      Value := FNativeDate;
    prForeignKeys:
      Value := FForeignKeys;
{$IFNDEF NOSTATIC}
    prStaticLibrary:
      Value := FStaticLibrary;
{$ENDIF}
    prForceCreateDatabase:
      Value := FForceCreateDatabase;
{$IFDEF CODEC}
    prEncryptionAlgorithm:
      Value := FEncryptionAlgorithm;
{$ENDIF}
  else
    Result := inherited GetProp(Prop, Value);
  end;
end;

procedure TSQLiteConnection.Check(ErrorCode: integer);
var
  liteErrorCode: integer;
  ErrorMsg: string;
begin
  if ErrorCode = 0 then
    exit;

  // check if SQLite can return error
  FAPI.GetLiteErrorCode(liteErrorCode);
  if liteErrorCode <> 0 then
    // if SQLite can return error then use standard method
    FAPI.GetLiteErrorMsg(ErrorMsg)
  else begin
    // if SQLite cannot return error then try get internal error code and msg
    liteErrorCode := ErrorCode;
    FAPI.GetPredefinedErrorMsg(liteErrorCode, ErrorMsg);
  end;
  ProcessError(liteErrorCode, ErrorMsg, Component);
end;

procedure TSQLiteConnection.ProcessError(ErrorCode: integer; ErrorMsg: string; Component: TObject);
var
  Error: ESQLiteError;
  Fail, NeedFreeError: boolean;
begin
  NeedFreeError := True;
  Error := ESQLiteError.Create(ErrorCode, ErrorMsg);
  try
  {$IFNDEF LITE}
    Error.Component := Component;
  {$ENDIF}
    Fail := True;
    DoError(Error, Fail);
    if Fail then
      NeedFreeError := False;
  finally
    if NeedFreeError then
      Error.Free;
  end;

  if Fail then
    raise Error
  else
    Abort;
end;

procedure TSQLiteConnection.Connect(const ConnectString: string);
{$IFDEF CLR}
var
  pDatabase: IntPtr;
{$ENDIF}
begin
  if FConnected then
    Exit;

  if not FForceCreateDatabase then
    if (FDatabase <> SInMemoryDB) and (FDatabase <> STempDB) then begin
      if not FileExists(FDatabase) then
        raise Exception.Create(Format(SDatabaseDoesntExist, [FDatabase]));
    end;

{$IFNDEF NOSTATIC}
{$IFDEF CODEC}
  if FStaticLibrary then begin
    if FEncryptionAlgorithm = DefaultEncryptionAlgorithm then
      if FEncryptionKey <> '' then
        raise Exception.Create(SEncryptionKeyMustBeEmpty);
  end;
{$ENDIF}
{$ENDIF}

{  hLockConnectCount.Enter;
  try
    if (FClientLibrary <> '') and (FClientLibrary <> SQLiteDLL) then begin
      if ActiveConnectionsCount > 0 then
        raise Exception.Create(SClientLibraryDiffers)

      else begin
        FreeSQLite;
{$IFNDEF CLR}
{        SQLiteDLL := FClientLibrary;
{$ENDIF}
{      end;
    end;

    Inc(ActiveConnectionsCount);
  finally
    hLockConnectCount.Leave;
  end;}

  try
    if not FAPI.Initialized then begin
{$IFNDEF NOSTATIC}
      FAPI.Direct := UseStaticLibrary;
{$ELSE}
      FAPI.Direct := False;
{$ENDIF}
      FAPI.ClientLibrary := FClientLibrary;
      FAPI.Initialize;

    end;

    FAPI.SQLite := nil;
    try
      // shared cache should be enabled or disabled before open connection
{$IFDEF MSWINDOWS}
{$IFNDEF NOSTATIC}
      if not UseStaticLibrary then
{$ENDIF}
        if FEnableSharedCache then
          Check(FAPI.sqlite3_enable_shared_cache(1))
        else
          Check(FAPI.sqlite3_enable_shared_cache(0));
{$ENDIF}

{$IFNDEF CLR}
      Check(FAPI.sqlite3_open(PAnsiChar(CRFunctions.UTF8Encode(FDatabase)), FAPI.SQLite));
{$ELSE}
      pDatabase := Marshal.StringToHGlobalAnsi(AnsiString(CRFunctions.UTF8Encode(FDatabase)));
      try
        Check(FAPI.sqlite3_open(pDatabase, FAPI.SQLite));
      finally
        Marshal.FreeCoTaskMem(pDatabase);
      end;
{$ENDIF}

      // extended error codes are supported since sqlite ver. 3.3.8
      Check(FAPI.sqlite3_extended_result_codes(FAPI.SQLite, 1));
      if FEnableLoadExtension then
        Check(FAPI.sqlite3_enable_load_extension(FAPI.SQLite, integer(FEnableLoadExtension)));

      if FEncryptionKey <> '' then
        DecryptDatabase(FEncryptionKey);

      // when the client library provides encryption (or in the Direct mode),
      // force database to perform page read operation to check if the
      // encryption algorithm and encryption key are valid
      ExecCommand('select * from sqlite_master');

{$IFDEF LITE}
    if FChangeEncryptionKey then
      try
        EncryptDatabase(FNewEncryptionKey);
        FEncryptionKey := FNewEncryptionKey;
{$IFDEF CODEC}
        if FAPI.Direct then begin
          Sqlite3CodecSetKey(FAPI.SQLite, 0, PAnsiChar(AnsiString(FEncryptionKey)), True);
          Sqlite3CodecSetKey(FAPI.SQLite, 0, PAnsiChar(AnsiString(FEncryptionKey)), False);
        end;
{$ENDIF}
      finally
        FChangeEncryptionKey := false;
        FNewEncryptionKey := '';
      end;
{$ENDIF}

      FConnected := True;

      InternalSetBusyTimeout(FBusyTimeout);
      InternalSetReadUncommitted(FReadUncommitted);
      InternalSetForeignKeys(FForeignKeys);
      if FDefaultCollations then
        InternalSetDefaultCollations(FDefaultCollations);

      FNativeConnection := True;

{$IFNDEF LITE}
      GetFunctionManager.RegistrAllFunctions;
{$ENDIF}

      inherited;

    except
      on EFailOver do;
      else begin
        if FAPI.SQLite <> nil then begin
          FAPI.sqlite3_close(FAPI.SQLite);
          FAPI.SQLite := nil;
        end;
        FConnected := False;
        raise;
      end;
    end;

  except
    //InterlockedDecrement(ActiveConnectionsCount);
    raise;
  end;
end;

procedure TSQLiteConnection.Disconnect;
begin
  if not FConnected then
    exit;

  FConnected := False;
  try
    if FNativeConnection then
      Check(FAPI.sqlite3_close(FAPI.SQLite));
  finally
    FAPI.SQLite := nil;
    FNativeConnection := True;
    //InterlockedDecrement(ActiveConnectionsCount);
  end;

  inherited;
end;

procedure TSQLiteConnection.Ping;
begin
  ExecCommand('SELECT 1');
end;

function TSQLiteConnection.CheckIsValid: boolean;
begin
  Result := True;
end;

procedure TSQLiteConnection.Assign(Source: TCRConnection);
var
  Src: TSQLiteConnection;
begin
  inherited;

  Src := TSQLiteConnection(Source);
  FDatabase := Src.FDatabase;
  FClientLibrary := Src.FClientLibrary;
  FASCIIDataBase := Src.FASCIIDataBase;
  FBusyTimeout := Src.FBusyTimeout;
  FDefaultCollations := Src.FDefaultCollations;
  FEncryptionKey := Src.FEncryptionKey;
{$IFDEF MSWINDOWS}
  FEnableSharedCache := Src.FEnableSharedCache;
{$ENDIF}
  FEnableLoadExtension := Src.FEnableLoadExtension;;
  FReadUncommitted := Src.FReadUncommitted;
  FUseUnicode := Src.FUseUnicode;
  FForeignKeys := Src.FForeignKeys;
  FAPI.UnInitialize;
  FAPI.Assign(Src.FAPI);
  FAPI.SQLite := nil;
end;

procedure TSQLiteConnection.AssignConnect(Source: TCRConnection);
var
  Src: TSQLiteConnection;
begin
  if Source <> Self then begin
    Disconnect;
    if Source <> nil then begin
      Src := TSQLiteConnection(Source);
      Assign(Src);
      FAPI.Assign(Src.FAPI);
      FInternalTransaction.AssignConnect(Src.FInternalTransaction);
      FConnected := FAPI.SQLite <> nil;
      FNativeConnection := False;
    end;
  end;
end;

function TSQLiteConnection.GetServerVersion: string;
begin
  Result := GetClientVersion;
end;

function TSQLiteConnection.GetServerVersionFull: string;
begin
  Result := GetClientVersion;
{$IFNDEF NOSTATIC}
  if UseStaticLibrary then
    Result := Result + ' (direct access)';
{$ENDIF}
end;

function TSQLiteConnection.GetClientVersion: string;
begin
  Result := string(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FAPI.sqlite3_libversion)));
end;

function TSQLiteConnection.CanChangeDatabase: boolean;
begin
  Result := False;
end;

function TSQLiteConnection.GetTypes: TSQLiteTypes;
begin
  if FLiteTypes = nil then
    FLiteTypes := TSQLiteTypes.Create(self);
  Result := FLiteTypes;
end;

function TSQLiteConnection.GetCollationManager: TSQLiteCollationManager;
begin
  if FCollationManager = nil then
    FCollationManager := TSQLiteCollationManager.Create(self);

  Result := FCollationManager;
end;

{$IFNDEF LITE}
function TSQLiteConnection.GetFunctionManager: TSQLiteFunctionManager;
begin
  if FFunctionManager = nil then
    FFunctionManager := TSQLiteFunctionManager.Create(self);

  Result := FFunctionManager;
end;
{$ENDIF}

function TSQLiteConnection.GetCommandClass: TCRCommandClass;
begin
  Result := TSQLiteCommand;
end;

function TSQLiteConnection.GetTransactionClass: TCRTransactionClass;
begin
  Result := TSQLiteTransaction;
end;

function TSQLiteConnection.IsUnicodeDataBase: boolean;
begin
  Result := FUseUnicode or not FASCIIDataBase;
end;

function TSQLiteConnection.EncodeString(AString: AnsiString): AnsiString;
begin
  Result := EncodeString(AString, IsUnicodeDataBase);
end;

function TSQLiteConnection.EncodeString(AString: AnsiString; UseUTF8: boolean): AnsiString;
begin
  if UseUTF8 then
    Result := CRFunctions.UTF8Encode(WideString(AString))
  else
    Result := AString;
end;

function TSQLiteConnection.EncodeString(WString: WideString): AnsiString;
begin
  Result := EncodeString(WString, IsUnicodeDataBase);
end;

function TSQLiteConnection.EncodeString(WString: WideString; UseUTF8: boolean): AnsiString;
begin
  if UseUTF8 then
    Result := CRFunctions.UTF8Encode(WString)
  else
    Result := AnsiString(WString);
end;

function TSQLiteConnection.DecodeStringA(AString: AnsiString): AnsiString;
begin
  Result := DecodeStringA(AString, IsUnicodeDataBase);
end;

function TSQLiteConnection.DecodeStringA(AString: AnsiString; UseUTF8: boolean): AnsiString;
begin
  if UseUTF8 then
    Result := AnsiString(CRFunctions.UTF8Decode(AString))
  else
    Result := AString;
end;

function TSQLiteConnection.DecodeStringW(AString: AnsiString): WideString;
begin
  Result := DecodeStringW(AString, IsUnicodeDataBase);
end;

function TSQLiteConnection.DecodeStringW(AString: AnsiString; UseUTF8: boolean): WideString;
begin
  if UseUTF8 then
    Result := CRFunctions.UTF8Decode(AString)
  else
    Result := WideString(AString);
end;

procedure TSQLiteConnection.EncryptDatabase(NewKey: string);
var
  {$IFDEF CODEC}pKey, {$ENDIF}pNewKey: {$IFNDEF CLR}PAnsiChar{$ELSE}IntPtr{$ENDIF};
begin
{$IFDEF CODEC}
  if FStaticLibrary then
    if FEncryptionAlgorithm = leDefault then
      raise Exception.Create(SDefaultAlgorithm);

  pKey := Marshal.StringToHGlobalAnsi(AnsiString(FEncryptionKey));
{$ENDIF}
  pNewKey := Marshal.StringToHGlobalAnsi(AnsiString(NewKey));

  try
{$IFNDEF NOSTATIC}
  if FStaticLibrary then begin
  {$IFDEF CODEC}
    Sqlite3CodecSetKey(FAPI.SQLite, 0, pKey, True, FEncryptionAlgorithm);
  {$ELSE}
    NoStaticEncryption;
  {$ENDIF}
  end;
{$ENDIF}
  Check(FAPI.sqlite3_rekey(FAPI.SQLite, pNewKey, Length(NewKey)));

  finally
  {$IFDEF CODEC}
    Marshal.FreeCoTaskMem(pKey);
  {$ENDIF}
    Marshal.FreeCoTaskMem(pNewKey);
  end;
end;

procedure TSQLiteConnection.DecryptDatabase(Key: String);
var
  pKey: {$IFNDEF CLR}PAnsiChar{$ELSE}IntPtr{$ENDIF};
begin
  pKey := Marshal.StringToHGlobalAnsi(AnsiString(Key));

  try
{$IFNDEF NOSTATIC}
  if FStaticLibrary then begin
  {$IFDEF CODEC}
    Sqlite3CodecSetKey(FAPI.SQLite, 0, pKey, True, FEncryptionAlgorithm);
    Sqlite3CodecSetKey(FAPI.SQLite, 0, pKey, False, FEncryptionAlgorithm);
  {$ELSE}
    NoStaticEncryption;
  {$ENDIF}
  end;
{$ENDIF}
  Check(FAPI.sqlite3_key(FAPI.SQLite, pKey, Length(Key)));

  finally
    Marshal.FreeCoTaskMem(pKey);
  end;
end;

procedure TSQLiteTransaction.StartTransaction;
var
  CommandText: string;
  Connection: TSQLiteConnection;
begin
  if FConnections.Count = 0 then
    raise Exception.Create(SNoConnectionsInTransaction);

  Connection := TSQLiteConnection(FConnections[0]);

  if not Connection.GetConnected then
    raise Exception.Create(SConnectionInTransactionNotActive);

  CommandText := 'BEGIN ';

  case FIsolationLevel of
    ilReadUncommitted, ilReadCommitted, ilRepeatableRead:
      CommandText := CommandText + 'DEFERRED';
    ilIsolated, ilSnapshot:
      CommandText := CommandText + 'IMMEDIATE';
  else
    raise Exception.Create(SUnsupportedIsolationLevel);
  end;

  Connection.ExecCommand(CommandText);

  FActive := True;
  FNativeTransaction := True;
end;

procedure TSQLiteTransaction.Commit;
var
  Connection: TSQLiteConnection;
begin
  CheckActive;

  Connection := TSQLiteConnection(FConnections[0]);
  if FNativeTransaction then
    Connection.ExecCommand('COMMIT');

  FActive := False;
end;

procedure TSQLiteTransaction.Rollback;
var
  Connection: TSQLiteConnection;
begin
  CheckActive;

  Connection := TSQLiteConnection(FConnections[0]);
  if FNativeTransaction then
    Connection.ExecCommand('ROLLBACK');

  FActive := False;
end;

procedure TSQLiteTransaction.Savepoint(const Name: string);
var
  Connection: TSQLiteConnection;
begin
  CheckActive;

  Connection := TSQLiteConnection(FConnections[0]);
  Connection.ExecCommand('SAVEPOINT ' + Name);
end;

procedure TSQLiteTransaction.ReleaseSavepoint(const Name: string);
var
  Connection: TSQLiteConnection;
begin
  CheckActive;

  Connection := TSQLiteConnection(FConnections[0]);
  Connection.ExecCommand('RELEASE SAVEPOINT ' + Name);
end;

procedure TSQLiteTransaction.RollbackToSavepoint(const Name: string);
var
  Connection: TSQLiteConnection;
begin
  CheckActive;

  Connection := TSQLiteConnection(FConnections[0]);
  Connection.ExecCommand('ROLLBACK TO SAVEPOINT ' + Name);
end;

{ TSQLiteTypes }

constructor TSQLiteTypes.Create(Connection: TSQLiteConnection);
begin
  inherited Create;

  FConnection := Connection;
end;

destructor TSQLiteTypes.Destroy;
begin
  inherited;
end;

procedure TSQLiteTypes.GetTypeAttr(DataType: Integer; TypeModifier: Integer;
  var Length, Scale: integer);
begin
  Length := 0;
  Scale := 0;

  case DataType of
    dtString:
      if TypeModifier <> -1 then
        Length := TypeModifier - 4;
  end;
end;

procedure TSQLiteTypes.DetectDataType(TypeOID, TypeModifier: integer;
  var DataType, SubDataType: word; var Length, Scale, Size: integer;
  var Fixed: boolean; var ObjectType: TObjectType;
  LongStrings, FlatBuffers, EnableBCD, EnableFMTBCD,
  CursorAsString, FieldsAsText: boolean);
begin
  DataType := GetInternalType(TypeOID, ObjectType);
  SubDataType := 0;
  Fixed := False;

  GetTypeAttr(DataType, TypeModifier, Length, Scale);

  if FieldsAsText then begin
    SubDataType := DataType;
    DataType := dtString;
  end
  else begin
    case DataType of
      dtUnknown: begin
        SubDataType := DataType;
        DataType := dtString;
      end;
    end;
  end;

  case DataType of
    dtString: begin
      if Length = 0 then begin
        DataType := dtMemo;
      end;
      if DataType <> dtMemo then begin
        if (Length < 255) or (LongStrings and (Length <= High(Word))) then begin
          if (Length >= FlatBufferLimit) and not FlatBuffers then
            DataType := dtExtString;
        end
        else begin
          DataType := dtMemo;
          SubDataType := dtString;
        end;
      end;
      if FConnection.FUseUnicode then
        case DataType of
          dtString:
            DataType := dtWideString;
          dtExtString:
            DataType := dtExtWideString;
          dtMemo: begin
            DataType := dtWideMemo;
            if SubDataType = 0 then
              SubDataType := dtWideString;
          end;
        end;
    end;
    dtMemo:
      if FConnection.FUseUnicode then
        DataType := dtWideMemo;
  {$IFDEF LITE}
    dtLargeint:
      if not (EnableFMTBCD or FConnection.EnableFMTBCD) then begin
        DataType := dtFloat;
        SubDataType := dtLargeint;
      end;
  {$ENDIF}
  end;

  Size := TSQLiteRecordSet.GetBufferSize(DataType, Length);
end;

function TSQLiteTypes.GetInternalType(TypeCode: integer): word;
var
  ObjectType: TObjectType;
begin
  Result := GetInternalType(TypeCode, ObjectType);
end;

function TSQLiteTypes.GetInternalType(TypeCode: integer; var ObjectType: TObjectType): word;
begin
  ObjectType := nil;
  case TypeCode of
    SQLITE_INTEGER:
      Result := dtInteger;
    SQLITE_FLOAT:
      Result := dtFloat;
    SQLITE_TEXT:
      Result := dtString;
    SQLITE_BLOB:
      Result := dtBlob;
  else
    Result := dtUnknown;
  end;
end;

function TSQLiteTypes.GetSQLiteType(DataType: word; var NeedConvertToText: boolean): word;
begin
  NeedConvertToText := False;

  case DataType of
    dtInt8, dtInt16, dtInt32, dtInt64, dtUInt16, dtUInt32, dtBoolean:
      Result := SQLITE_INTEGER;
    dtFloat, dtCurrency, dtBCD{$IFNDEF FPC}, dtFMTBCD{$ENDIF}:
      Result := SQLITE_FLOAT;
    dtDate: begin
      Result := SQLITE_TEXT;
      NeedConvertToText := True;
    end;
    dtTime: begin
      Result := SQLITE_TEXT;
      NeedConvertToText := True;
    end;
    dtDateTime{$IFDEF VER6P}{$IFNDEF FPC}, dtSQLTimeStamp{$ENDIF}{$ENDIF}: begin
      Result := SQLITE_TEXT;
      NeedConvertToText := True;
    end;
    dtBlob, dtBytes, dtVarBytes:
      Result := SQLITE_BLOB;
  else
    // Unknown types save as TEXT
    Result := SQLITE_TEXT;
  end;
end;

function TSQLiteTypes.GetVarType(VarType: TVarType): word;
begin
  case VarType of
    varShortInt, varByte:
      Result := dtInt8;
    varSmallInt:
      Result := dtInt16;
    varWord:
      Result := dtUInt16;
    varInteger:
      Result := dtInt32;
    varLongWord:
      Result := dtUInt32;
    varInt64:
      Result := dtInt64;
    varBoolean:
      Result := dtBoolean;
    varSingle, varDouble:
      Result := dtFloat;
    varCurrency:
      Result := dtCurrency;
    varDate:
      Result := dtDateTime;
    varArray + varByte:
      Result := dtVarBytes;
{$IFNDEF CLR}
    varByRef:
      Result := dtVarBytes;
    varAny:
      Result := dtBytes;
{$ENDIF}
  else
    Result := dtUnknown;
  end;
end;

function TSQLiteTypes.ConvertToText(DataType: word; Value: Variant): AnsiString;
begin
  case DataType of
    dtDate:
      Result := AnsiString(FormatDateTime('yyyy-mm-dd', Value));
    dtTime:
      Result := AnsiString(FormatDateTime('hh'':''nn'':''ss', Value));
    dtDateTime{$IFDEF VER6P}{$IFNDEF FPC}, dtSQLTimeStamp{$ENDIF}{$ENDIF}:
      Result := AnsiString(FormatDateTime('yyyy-mm-dd hh'':''nn'':''ss', Value));
  else
    Result := AnsiString(VarToStr(Value));
  end;
end;

function TSQLiteTypes.ConvertBlob(Value: Variant; var DestrType: IntPtr): TBytes;
var
  Obj: TObject;
  Blob: TBlob;
{$IFNDEF CLR}
//  pBlob: pointer;
//  lb, hb: integer;
{$ENDIF}
begin
  case VarType(Value) of
    {$IFDEF CLR}varObject{$ELSE}varByRef{$IFDEF FPC} or varVariant{$ENDIF}{$ENDIF}: begin
    {$IFDEF CLR}
      Obj := Value;
    {$ELSE}
      Obj := TObject(TVarData(Value).VPointer);
    {$ENDIF}
      if not (Obj is TBlob) then
        raise Exception.Create(SCannotConvertType);
      Blob := TBlob(Obj);
      Blob.Defrag;
      Marshal.Copy(PtrOffset(Blob.FirstPiece, SizeOf(TPieceHeader)), Result, 0, Blob.FirstPiece.Used);
      DestrType := SQLITE_STATIC;
    end;
  {$IFNDEF CLR}
    varArray or varByte: begin
      Result := Value;
      {
      if VarArrayDimCount(Value) <> 1 then
        raise Exception.Create(SCannotConvertType);
      lb := VarArrayLowBound(Value, 1);
      hb := VarArrayHighBound(Value, 1);
      pBlob := VarArrayLock(Value);
      try
        Marshal.Copy(pBlob, Result, 0, hb - lb + 1);
      finally
        VarArrayUnlock(Value);
      end;
      }
      DestrType := SQLITE_TRANSIENT;
    end;
  {$ENDIF}
  else
    Result := Encoding.Default.GetBytes(VarToStr(Value));
    DestrType := SQLITE_TRANSIENT;
  end;
end;

function TSQLiteTypes.ConvertMemo(Value: Variant): AnsiString;
var
  ws: WideString;
  Obj: TObject;
  Blob: TBlob;
begin
  case VarType(Value) of
    {$IFDEF CLR}varObject{$ELSE}varByRef{$IFDEF FPC} or varVariant{$ENDIF}{$ENDIF}: begin
    {$IFDEF CLR}
      Obj := Value;
    {$ELSE}
      Obj := TObject(TVarData(Value).VPointer);
    {$ENDIF}
      if not (Obj is TBlob) then
        raise Exception.Create(SCannotConvertType);
      Blob := TBlob(Obj);
      ws := Blob.AsWideString;
    end;
  else
    ws := VarToWideStr(Value);
  end;

  // encode string: UTF8 or ASCII
  Result := FConnection.EncodeString(ws);
end;

{ TSQLiteCommand }

constructor TSQLiteCommand.Create;
begin
  inherited;

  ResetFormatSettings;
end;

destructor TSQLiteCommand.Destroy;
begin

  inherited;
end;

class function TSQLiteCommand.GetSQLInfoClass: TSQLInfoClass;
begin
  Result := TSQLiteInfo;
end;

function TSQLiteCommand.SetProp(Prop: integer; const Value: variant): boolean;
begin
  {Result := True;
  case Prop of
  else}
    Result := inherited SetProp(Prop, Value);
  //end;
end;

function TSQLiteCommand.GetProp(Prop: integer; out Value: variant): boolean;
begin
  Result := True;
  case Prop of
    prRowsProcessed:
      Value := FRowsAffected;
  else
    Result := inherited GetProp(Prop, Value);
  end;
end;

procedure TSQLiteCommand.SetConnection(Value: TCRConnection);
begin
  if Value <> FConnection then begin
    inherited;

    FConnection := TSQLiteConnection(Value);
  end;
end;

class function TSQLiteCommand.GetTableInfoClass: TTableInfoClass;
begin
  Result := TSQLiteTableInfo;
end;

class function TSQLiteCommand.GetParserClass: TSQLParserClass;
begin
  Result := TLiteParser;
end;

procedure TSQLiteCommand.Prepare;
var
  NeedFreeSQL: boolean;
begin
  if FStmt <> nil then
    Exit;

  if FConnection = nil then
    raise Exception.Create(SConnectionNotDefined);
  if Trim(FSQL) = '' then
    raise Exception.Create(SEmptySQLStatement);

  NeedFreeSQL := False;
  // encode string: UTF8 or ASCII
  if FCommandLength = 0 then begin
    FCommandSQL := FConnection.EncodeString(FSQL);
    FCommandLength := LengthA(FCommandSQL);
    FPCommandSQL := Marshal.StringToHGlobalAnsi(FCommandSQL);
    FCurrentCommand := FPCommandSQL;
  end;
  try
    try
      // FStmt will be NULL on error
      Check(FConnection.FAPI.sqlite3_prepare_v2(FConnection.FAPI.SQLite,
        FCurrentCommand, FCommandLength, FStmt, FCommandTail));

      Dec(FCommandLength, NativeInt(FCommandTail) - NativeInt(FCurrentCommand));
      if FCommandLength < 0 then
        FCommandLength := 0;
      while (FCommandLength > 0) and (Marshal.ReadByte(FCommandTail) <= byte(' ')) do begin
        FCommandTail := PtrOffset(FCommandTail, 1);
        Dec(FCommandLength);
      end;
    except
      NeedFreeSQL := True;
      raise;
    end;
  finally
    if (FCommandLength = 0) or NeedFreeSQL then
      Marshal.FreeCoTaskMem(FPCommandSQL);
  end;

  FCursorState := csPrepared;
end;

procedure TSQLiteCommand.Unprepare;
begin
  if FStmt = nil then
    Exit;

  FConnection.FAPI.sqlite3_finalize(FStmt);
  FStmt := nil;
  FCursorState := csInactive;
end;

function TSQLiteCommand.GetPrepared: boolean;
begin
  Result := FStmt <> nil;
end;

procedure TSQLiteCommand.Execute(Iters: integer = 1);
var
  NeedPrepare: boolean;
  i: integer;
  HasNoSemicolon: boolean;
begin
  if GetCursorState > csPrepared then
    Exit;

  try
    NeedPrepare := not GetPrepared;
    if NeedPrepare then
      Prepare;

    try
      FConnection.FAPI.sqlite3_reset(FStmt);

      BindParams;

      while True do begin
        FExecResult := FConnection.FAPI.sqlite3_step(FStmt);
        if not ( (FExecResult = SQLITE_ROW) or (FExecResult = SQLITE_DONE) ) then
          Check(FExecResult);

        if (FCommandLength > 0) then begin
          HasNoSemicolon := False;
          for i := 0 to FCommandLength - 1 do
            if Marshal.ReadByte(FCommandTail, i) <> byte(';') then begin
              HasNoSemicolon := True;
              break;
            end;
          if not HasNoSemicolon then
            break;

          UnPrepare;
          FCurrentCommand := FCommandTail;
          Prepare;
        end
        else
          Break;
      end;

      FConnection.FLastInsertId := FConnection.FAPI.sqlite3_last_insert_rowid(FConnection.FAPI.SQLite);
      FRowsAffected := FConnection.FAPI.sqlite3_changes(FConnection.FAPI.SQLite);
    finally
      if NeedPrepare then
        UnPrepare;
    end;

  except
    if not FLockAfterExecute and Assigned(FAfterExecute) then
      FAfterExecute(False);
    raise;
  end;
  if not FLockAfterExecute and Assigned(FAfterExecute) then
    FAfterExecute(True);
end;

procedure TSQLiteCommand.SetSQL(const Value: string);
begin
  inherited;

  FCommandTail := nil;
  FCommandLength := 0;
end;

function TSQLiteCommand.ParseSQL(const SQL: string; Params: TParamDescs; ReplaceAll: boolean = True; const RenamePrefix: string = ''): string;
var
  Param: TParamDesc;
  Parser: TLiteParser;
  Code: integer;
  St: string;
  AllParams: TStringList;
begin
  Result := SQL;

  if not FScanParams then
    exit;

  Assert(Params <> nil);
  Params.Clear;
  Parser := TLiteParser.Create(SQL);
  Parser.DecSeparator := '.';
  Parser.OmitBlank := True;
  Parser.OmitComment := True;
  AllParams := TStringList.Create;
  AllParams.Sorted := True;
  try
    Parser.ToBegin;
    repeat
      Code := Parser.GetNext(St);
      while (Code <> lcEnd) and (St <> ':') do begin
        Code := Parser.GetNext(St);
      end;
      if St = ':' then begin
        Code := Parser.GetNext(St);
        if (Code = lcIdent) or (Code = lcNumber) or (Code >= lxSQLFirst) then begin
          // remove quotes
          if St[1] = '"' then begin
            Delete(St, 1, 1);
            if St[Length(St)] = '"' then
              SetLength(St, Length(St) - 1);
          end;

          if AllParams.IndexOf(St) < 0 then begin
            Param := TParamDesc.Create;
            Param.SetName(St);
            Params.Add(Param);
            AllParams.Add(St)
          end;
        end;
      end;
    until Code = lcEnd;
  finally
    AllParams.Free;
    Parser.Free;
  end;
end;

procedure TSQLiteCommand.InitProcParams(const Name: string; Overload: integer);
begin
  raise Exception.Create(SStoredProcNotSupported);
end;

function TSQLiteCommand.CreateProcCall(const Name: string; NeedDescribe: boolean; IsQuery: boolean): string;
begin
  raise Exception.Create(SStoredProcNotSupported);
end;

function TSQLiteCommand.GetCursorState: TCursorState;
begin
  Result := FCursorState;
end;

{$IFNDEF LITE}
class function TSQLiteCommand.GetMapRulesClass: TCRMapRulesClass;
begin
  Result := TLiteMapRules;
end;
{$ENDIF}

procedure TSQLiteCommand.SetCursorState(Value: TCursorState);
begin
  FCursorState := Value;
end;

procedure TSQLiteCommand.Check(ErrorCode: integer);
begin
  FConnection.Check(ErrorCode);
end;

procedure TSQLiteCommand.InitFormatSettings;

  function DetectSeparator(var Format: string; DefaultSeparator: Char): Char;
  var
    i: integer;
    NewSeparator: Char;
  begin
    NewSeparator := #0;
    for i := 1 to Length(Format) do
      if CharInSet(Char(Format[i]), ['-', '.', ':', '/', '\']) then begin
        if NewSeparator = #0 then
          NewSeparator := Format[i];
        if Format[i] = NewSeparator then
          Format[i] := DefaultSeparator;
      end;

    if NewSeparator <> #0 then
      Result := NewSeparator
    else
      Result := DefaultSeparator;
  end;

begin
  ResetFormatSettings;

  if FConnection = nil then
    exit;

  if FConnection.FDateFormat <> '' then begin
    FShortDateFormat := FConnection.FDateFormat;
    FDateSeparator := DetectSeparator(FShortDateFormat, '-');
  end;

  if FConnection.FTimeFormat <> '' then begin
    FShortTimeFormat := FConnection.FTimeFormat;
    FTimeSeparator := DetectSeparator(FShortTimeFormat, ':');
  end;
end;

procedure TSQLiteCommand.ResetFormatSettings;
begin
  FDateSeparator := '-';
  FShortDateFormat := 'yyyy-mm-dd';
  FTimeSeparator := ':';
  FShortTimeFormat := 'hh:nn:ss';
end;

procedure TSQLiteCommand.BindParams;
var
  Count, i: integer;
  Param: TParamDesc;
  str_val: AnsiString;
  int_val: int64;
  float_val: double;
  pstr_val: IntPtr;
  bool_var: boolean;
begin
  Count := FConnection.FAPI.sqlite3_bind_parameter_count(FStmt);
  if Count <> Params.Count then
    raise Exception.CreateFmt(SIncorrectParamCount, [Count, Params.Count]);


  for i := 0 to Count - 1 do begin
    Param := Params[i];

    if Param.GetNull then begin
      Check(FConnection.FAPI.sqlite3_bind_null(FStmt, i + 1));
      continue;
    end;

    case Param.GetDataType of
      dtInt8, dtUInt8, dtInt16, dtInt32, dtInt64, dtUInt16, dtUInt32: begin
        int_val := Param.Value;
        Check(FConnection.FAPI.sqlite3_bind_int64(FStmt, i + 1, int_val));
      end;
      dtBoolean: begin
        bool_var := Param.Value;
        Check(FConnection.FAPI.sqlite3_bind_int64(FStmt, i + 1, Int64(bool_var)));
      end;
      dtFloat, dtCurrency, dtBCD{$IFNDEF FPC}, dtFMTBCD{$ENDIF}: begin
        float_val := Param.Value;
        Check(FConnection.FAPI.sqlite3_bind_double(FStmt, i + 1, float_val));
      end;
      dtDate: begin
        str_val := AnsiString(FormatDateTime(StringReplace(FShortDateFormat, '-', FDateSeparator, [rfReplaceAll]), Param.Value));
        pstr_val := Marshal.StringToHGlobalAnsi(str_val);
        try
        Check(FConnection.FAPI.sqlite3_bind_text(FStmt, i + 1, pstr_val, LengthA(str_val), SQLITE_TRANSIENT));
        finally
          Marshal.FreeCoTaskMem(pstr_val);
        end;
      end;
      dtTime: begin
        str_val := AnsiString(FormatDateTime(StringReplace(FShortTimeFormat, ':', FTimeSeparator, [rfReplaceAll]), Param.Value));
        pstr_val := Marshal.StringToHGlobalAnsi(str_val);
        try
        Check(FConnection.FAPI.sqlite3_bind_text(FStmt, i + 1, pstr_val, LengthA(str_val), SQLITE_TRANSIENT));
        finally
          Marshal.FreeCoTaskMem(pstr_val);
        end;
      end;
      dtDateTime{$IFDEF VER6P}{$IFNDEF FPC}, dtSQLTimeStamp{$ENDIF}{$ENDIF}: begin
        str_val := AnsiString(FormatDateTime(StringReplace(FShortDateFormat, '-', FDateSeparator, [rfReplaceAll]) + ' ' +
                                             StringReplace(FShortTimeFormat, ':', FTimeSeparator, [rfReplaceAll]), Param.Value));
        pstr_val := Marshal.StringToHGlobalAnsi(str_val);
        try
        Check(FConnection.FAPI.sqlite3_bind_text(FStmt, i + 1, pstr_val, LengthA(str_val), SQLITE_TRANSIENT));
        finally
          Marshal.FreeCoTaskMem(pstr_val);
        end;
      end;
      dtBlob, dtBytes, dtVarBytes:
        BindBlob(i, Param.Value);
    else
      BindMemo(i, Param.Value);
    end;
  end;
end;

procedure TSQLiteCommand.BindBlob(Index: integer; const Value: variant);
var
  s: AnsiString;
  Obj: TObject;
  Blob: TBlob;
  BlobData: IntPtr;
{$IFNDEF CLR}
  lb, hb: integer;
{$ENDIF}
begin
  case VarType(Value) of
    {$IFDEF CLR}varObject{$ELSE}varByRef{$IFDEF FPC} or varVariant{$ENDIF}{$ENDIF}: begin
    {$IFDEF CLR}
      Obj := Value;
    {$ELSE}
      Obj := TObject(TVarData(Value).VPointer);
    {$ENDIF}
      if not (Obj is TBlob) then
        raise Exception.Create(SCannotConvertType);
      Blob := TBlob(Obj);
      Blob.Defrag;
      BlobData := PtrOffset(Blob.FirstPiece, SizeOf(TPieceHeader));
      Check(FConnection.FAPI.sqlite3_bind_blob(FStmt, Index + 1, BlobData, Blob.FirstPiece.Used,
        SQLITE_STATIC));
    end;
  {$IFNDEF CLR}
    varArray or varByte: begin
      if VarArrayDimCount(Value) <> 1 then
        raise Exception.Create(SCannotConvertType);
      lb := VarArrayLowBound(Value, 1);
      hb := VarArrayHighBound(Value, 1);
      BlobData := VarArrayLock(Value);
      try
        Check(FConnection.FAPI.sqlite3_bind_blob(FStmt, Index + 1, BlobData, hb - lb + 1,
          SQLITE_TRANSIENT));
      finally
        VarArrayUnlock(Value);
      end;
    end;
  {$ENDIF}
  else
    s := AnsiString(VarToStr(Value));
  {$IFNDEF CLR}
    BlobData := PAnsiChar(s);
  {$ELSE}
    BlobData := Marshal.AllocHGlobal(Length(s));
    Marshal.Copy(TBytes(s), 0, BlobData, LengthA(s));
  {$ENDIF}
    try
      Check(FConnection.FAPI.sqlite3_bind_blob(FStmt, Index + 1, BlobData, LengthA(s),
        SQLITE_TRANSIENT));
    finally
    {$IFDEF CLR}
      Marshal.FreeHGlobal(BlobData);
    {$ENDIF}
    end;
  end;
end;

procedure TSQLiteCommand.BindMemo(Index: integer; const Value: variant);
var
  ws: WideString;
  sa: AnsiString;
  Obj: TObject;
  Blob: TBlob;
  psa: IntPtr;
begin
  case VarType(Value) of
    {$IFDEF CLR}varObject{$ELSE}varByRef{$IFDEF FPC} or varVariant{$ENDIF}{$ENDIF}: begin
    {$IFDEF CLR}
      Obj := Value;
    {$ELSE}
      Obj := TObject(TVarData(Value).VPointer);
    {$ENDIF}
      if not (Obj is TBlob) then
        raise Exception.Create(SCannotConvertType);
      Blob := TBlob(Obj);
      ws := Blob.AsWideString;
    end;
  else
    ws := VarToWideStr(Value);
  end;

  // encode string: UTF8 or ASCII
  sa := FConnection.EncodeString(ws);
  psa := Marshal.StringToHGlobalAnsi(sa);
  try
  Check(FConnection.FAPI.sqlite3_bind_text(FStmt, Index + 1, psa, LengthA(sa),
    SQLITE_TRANSIENT));
  finally
    Marshal.FreeCoTaskMem(psa);
  end;
end;

{ TSQLiteRecordSet }

constructor TSQLiteRecordSet.Create;
begin
  inherited Create;

  FFetchAll := False;
  FFetchRows := 25;
end;

destructor TSQLiteRecordSet.Destroy;
begin
  Close;

  inherited;
end;

function TSQLiteRecordSet.SetProp(Prop: integer; const Value: variant): boolean;
begin
  Result := True;
  case Prop of
    prDumpData:
      FDumpData := Value;
  else
    Result := inherited SetProp(Prop, Value);
  end;
end;

function TSQLiteRecordSet.GetProp(Prop: integer; out Value: variant): boolean;
begin
  Result := True;
  case Prop of
    prRowsFetched:
      Value := FRecordCount;
  else
    Result := inherited GetProp(Prop, Value);
  end;
end;

procedure TSQLiteRecordSet.ExecCommand;
var
  NeedPrepare: boolean;
begin
  FCommand.InitFormatSettings;

  FCommand.FLockAfterExecute := True;
  try
    NeedPrepare := not Prepared;
    if NeedPrepare then
      InternalPrepare;

    try
      inherited;

      if FCommand.CommandType = ctCursor then begin
        if FCommand.FExecResult = SQLITE_DONE then
          FCommand.SetCursorState(csFetched)
        else
          FCommand.SetCursorState(csExecuted)
      end;

    except
      if NeedPrepare then
        InternalUnprepare;
      raise;
    end;

    if FCommand.CommandType <> ctCursor then
      if NeedPrepare then
        InternalUnprepare;

  except
    FCommand.FLockAfterExecute := False;
    if Assigned(FCommand.AfterExecute) then
      FCommand.AfterExecute(False);
    raise;
  end;
  FCommand.FLockAfterExecute := False;
  if Assigned(FCommand.AfterExecute) then
    FCommand.AfterExecute(True);
end;

function TSQLiteRecordSet.IsFullReopen: boolean;
begin
  Result := False;
end;

procedure TSQLiteRecordSet.Reopen;
begin
  if FCommand.GetCursorState > csPrepared then
    FCommand.SetCursorState(csPrepared);

  FreeData;
  InitData;
  if Assigned(FOnDataChanged) then
    // perform dataset resync to prevent AV if grid is repainted in BeforeFetch/AfterFetch events
    FOnDataChanged;

  InternalOpen(True);
end;

procedure TSQLiteRecordSet.SetToEnd;
begin
  FetchAll;

  inherited;
end;

procedure TSQLiteRecordSet.Check(ErrorCode: integer);
begin
  FCommand.FConnection.Check(ErrorCode);
end;

procedure TSQLiteRecordSet.CreateCommand;
begin
  SetCommand(TSQLiteCommand.Create);
end;

procedure TSQLiteRecordSet.SetCommand(Value: TCRCommand);
begin
  inherited;

  FCommand := TSQLiteCommand(Value);
end;

procedure TSQLiteRecordSet.InternalPrepare;
begin
  inherited;

  if FCommand.FConnection.FAPI.sqlite3_column_count(FCommand.FStmt) > 0 then
    FCommand.CommandType := ctCursor
  else
    FCommand.CommandType := ctStatement;
end;

procedure TSQLiteRecordSet.InternalUnPrepare;
begin
  inherited;

  FCommand.CommandType := ctUnknown;
end;

procedure TSQLiteRecordSet.DetectIdentityField;
var
  FieldDesc: TCRFieldDesc;
  i: integer;
begin
  FIdentityField := nil;

  for i := 0 to FFields.Count - 1 do begin
    FieldDesc := TCRFieldDesc(FFields[i]);
    if CompareText(FieldDesc.ActualName, 'rowid') = 0 then
      if FieldDesc.TableInfo = UpdatingTableInfo then begin
        FIdentityField := FieldDesc;
        Exit;
      end;
  end;
end;

procedure TSQLiteRecordSet.DescribeFieldDesc(Field: TCRFieldDesc; Index: integer);
var
  TableName, DatabaseName, DataTypeName: string;
  TableInfo: TCRTableInfo;
  DataType, Len, Scale: integer;
{$IFNDEF LITE}
  pDatabaseName,
  pTableName,
  pFieldName,
  pDataType,
  pCollation: IntPtr;
  NotNull, PrimaryKey, AutoInc: integer;
  FetchConverter: TFetchConverter;
{$ENDIF}

  procedure GetSQLTypeMeasures(var SQLTypeName: string; var SQLTypeLen, SQLTypeScale: integer);
  var
    s: string;
    p1, p2: integer;
  begin
    SQLTypeLen := -1;
    SQLTypeScale := -1;
    p1 := Pos('(', SQLTypeName);
    p2 := Pos(')', SQLTypeName);
    if (p1 > 0) and (p2 > 0) then begin
      s := Trim(Copy(SQLTypeName, p1 + 1, p2 - p1 - 1));
      DataTypeName := Copy(SQLTypeName, 1, p1 - 1);
      p1 := Pos(',', s);
      if p1 > 0 then begin
        SQLTypeLen := StrToIntDef(Trim(Copy(s, 1, p1 - 1)), -1);
        SQLTypeScale := StrToIntDef(Trim(Copy(s, p1 + 1, Length(s) - p1)), -1);
      end
      else
        SQLTypeLen := StrToIntDef(Trim(s), -1);
    end;
  end;

begin
  Len := 0;
  Scale := 0;
  Field.FieldNo := Index + 1;
  Field.ActualFieldNo := Index;
  Field.Name := FCommand.FConnection.DecodeStringW(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_name(FCommand.FStmt, Index))));
  if FCommand.FConnection.FAPI.IsMetaDataAPIAvailable then
    Field.ActualName := FCommand.FConnection.DecodeStringW(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_origin_name(FCommand.FStmt, Index))))
  else
    Field.ActualName := '';

  Field.TableInfo := nil;
  if FCommand.FConnection.FAPI.IsMetaDataAPIAvailable then begin
    TableName := FCommand.FConnection.DecodeStringW(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_table_name(FCommand.FStmt, Index))));
    if TableName <> '' then begin
      // TableName can containt "."
      TableName := SQLiteInfo.QuoteIfNeed(TableName);
      TableInfo := FTablesInfo.FindByName(TableName);
      if TableInfo = nil then begin
        TableInfo := FTablesInfo.Add;
        TableInfo.TableName := TableName;
        TableInfo.TableAlias := '';
        DatabaseName := FCommand.FConnection.DecodeStringW(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_database_name(FCommand.FStmt, Index))));
        if DatabaseName <> '' then
          TableName := DatabaseName + '.' + TableName;
        TableInfo.TableNameFull := TableName;
      end;
      Field.TableInfo := TableInfo;
    end;
  end;

{$IFNDEF LITE}
  DataTypeName := '';
  if (FSmartFetchState <> sfNone) and (Field.TableInfo <> nil) then begin
    pDatabaseName := Marshal.StringToHGlobalAnsi(FCommand.FConnection.EncodeString(DatabaseName));
    pTableName := Marshal.StringToHGlobalAnsi(FCommand.FConnection.EncodeString(Field.TableInfo.TableName));
    pFieldName := Marshal.StringToHGlobalAnsi(FCommand.FConnection.EncodeString(Field.Name));
    try
      if FCommand.FConnection.FAPI.sqlite3_table_column_metadata(FCommand.FConnection.FAPI.SQLite,
        pDatabaseName, pTableName, pFieldName, pDataType, pCollation, NotNull, PrimaryKey, AutoInc) = 0 then
      begin
        DataTypeName := UpperCase(FCommand.FConnection.DecodeStringW(AnsiString(Marshal.PtrToStringAnsi(pDataType))));
        Field.IsKey := boolean(PrimaryKey);
        Field.IsAutoIncrement := boolean(AutoInc);
        Field.Required := boolean(NotNull);
      end;
    finally
      Marshal.FreeCoTaskMem(pDatabaseName);
      Marshal.FreeCoTaskMem(pTableName);
      Marshal.FreeCoTaskMem(pFieldName);
    end;
  end;
{$ENDIF}

  if FDumpData then
    Field.DataType := dtWideMemo
  else begin
  {$IFNDEF LITE}
    if DataTypeName = '' then
  {$ENDIF}
      DataTypeName := UpperCase(FCommand.FConnection.DecodeStringW(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_decltype(FCommand.FStmt, Index)))));

    if DataTypeName <> '' then begin
      GetSQLTypeMeasures(DataTypeName, Len, Scale);
      Field.DBLength := Len;
      Field.DBScale := Scale;
    {$IFNDEF LITE}
      Field.DBType := TLiteConverterManager.GetDBType(DataTypeName);

      FetchConverter := GetMapFetchConverter(Field.Name, Field.DBType, Field.DBLength, Field.DBScale);

      if FetchConverter <> nil then
        Field.DataType := FetchConverter.InternalDataType
      else
    {$ENDIF}
      begin
        if (DataTypeName = 'INT2') or
           (DataTypeName = 'SMALLINT') or
           (DataTypeName = 'SMALL INT') or
           (DataTypeName = 'TINYINT') or
           (DataTypeName = 'TINY INT')
        then
          Field.DataType := dtSmallint // Int16
        else
        if (DataTypeName = 'BIGINT') or
           (DataTypeName = 'BIG INT') or
           (DataTypeName = 'UNSIGNED BIG INT') or
           (DataTypeName = 'UNSIGNED BIGINT') or
           (DataTypeName = 'INT8') or
           (DataTypeName = 'INT64')
        then
          Field.DataType := dtLargeint // Int64
        else
        if (DataTypeName = 'INT') or
           (DataTypeName = 'INTEGER') or
           (DataTypeName = 'MEDIUMINT') or
           (DataTypeName = 'MEDIUM INT')
        then
          Field.DataType := dtInteger // Int32
        else
        // other integers
        if Pos('INT', DataTypeName) > 0 then
          Field.DataType := dtInteger
        else
        if (Pos('CHAR', DataTypeName) > 0) then
          Field.DataType := dtString
        else
        if AnsiStartsStr('TEXT', DataTypeName) or
           AnsiStartsStr('CLOB', DataTypeName)
        then
          Field.DataType := dtMemo
        else
        if AnsiStartsStr('BLOB', DataTypeName) then
          Field.DataType := dtBlob
        else
        if AnsiStartsStr('REAL', DataTypeName) or
           AnsiStartsStr('FLOAT', DataTypeName) or
           AnsiStartsStr('DOUBLE', DataTypeName)
        then
          Field.DataType := dtFloat
        else
        if (DataTypeName = 'NUMERIC') or
           (DataTypeName = 'DECIMAL') or
           (DataTypeName = 'NUMBER')
        then begin
        {$IFNDEF FPC}
          if FCommand.EnableFMTBCD or FCommand.FConnection.EnableFMTBCD then begin
            Field.DataType := dtFMTBCD;
          end
          else
        {$ENDIF}
          if FCommand.EnableBCD or FCommand.FConnection.EnableBCD then begin
            Field.DataType := dtBCD;
          end
          else
            Field.DataType := dtFloat;
        end
        else
        if DataTypeName = 'MONEY' then
          Field.DataType := dtCurrency
        else
        if AnsiStartsStr('BOOL', DataTypeName) then
          Field.DataType := dtBoolean
        else
        if (DataTypeName = 'BINARY') or
           (DataTypeName = 'VARBINARY')
        then
          Field.DataType := dtVarBytes
        else
        if DataTypeName = 'DATE' then
          Field.DataType := dtDate
        else
        if DataTypeName = 'TIME' then
          Field.DataType := dtTime
        else
        if (DataTypeName = 'DATETIME') or
           (DataTypeName = 'TIMESTAMP')
        then
          Field.DataType := dtDateTime
        else
          Field.DataType := dtString;
      end;
    end
    else if Field.ActualName = '' then begin
      // set DataType basing on type of values from first record
      DataType := FCommand.FConnection.FAPI.sqlite3_column_type(FCommand.FStmt, Index);

      Field.DBLength := -1;
      Field.DBScale := -1;
    {$IFNDEF LITE}
      Field.DBType := TLiteConverterManager.GetDBType(DataType);

      FetchConverter := GetMapFetchConverter(Field.Name, Field.DBType, Field.DBLength, Field.DBScale);

      if FetchConverter <> nil then
        Field.DataType := FetchConverter.InternalDataType
      else
    {$ENDIF}
      begin
        case DataType of
          SQLITE_INTEGER:
            Field.DataType := dtLargeint;
          SQLITE_FLOAT:
            Field.DataType := dtFloat;
          SQLITE_TEXT, SQLITE_NULL:
            Field.DataType := dtString;
          SQLITE_BLOB:
            Field.DataType := dtBlob;
        else
          Assert(False);
        end;
      end;
    end
    else begin
      Field.DataType := dtString;
    {$IFNDEF LITE}
      Field.DBType := liteNull;
    {$ENDIF}
      Field.DBLength := -1;
      Field.DBScale := -1;
    end;

    if Field.ActualName = '' then
      Field.ActualName := Field.Name;

    if Field.DataType in [dtString, dtWideString] then begin
      if ((Field.DBLength >= 255) and (not FLongStrings)) or (Field.DBLength < 0) then
        if Field.DataType = dtString then
          Field.DataType := dtMemo
        else
          Field.DataType := dtWideMemo;
    end
    else if Field.DataType = dtVarBytes then
      if Field.DBLength <= 0 then
        Field.DataType := dtBlob;

    if FCommand.FConnection.FUseUnicode then
      case Field.DataType of
        dtString:
          Field.DataType := dtWideString;
        dtMemo:
          Field.DataType := dtWideMemo;
      end;
  end;

  if Len < 0 then
    Len := 0;
  if Scale < 0 then
    Scale := 0;

  Field.Length := Len;
  Field.Scale := Scale;

  if Field.DataType = dtVarBytes then begin
    if Field.Length > 0 then
      if (Field.Length >= FlatBufferLimit) and not FFlatBuffers then
        Field.DataType := dtExtVarBytes;
  end
{$IFNDEF FPC}
  else if Field.DataType = dtFMTBCD then begin
    if (Len > MaxFMTBcdDigits) or (Len <= 0) then
      Field.Length := MaxFMTBcdDigits
    else
      Field.Length := Len;
    if Scale > Field.Length then // if length was reduced
      Field.Scale := Field.Length
    else
      Field.Scale := Scale;
  end
{$ENDIF}
  else if Field.DataType = dtBCD then begin
    if (Len > MaxBcdPrecision) or (Len <= 0) then
      Field.Length := MaxBcdPrecision
    else
      Field.Length := Len;
    if Scale > MaxBcdScale then
      Field.Scale := MaxBcdScale
    else
      Field.Scale := Scale;
  end
  else if Field.DataType in [dtString, dtWideString] then begin
    if not FFlatBuffers and (Field.Length >= FlatBufferLimit) then
      if Field.DataType = dtString then
        Field.DataType := dtExtString
      else if Field.DataType = dtWideString then
        Field.DataType := dtExtWideString;
  end;

  Field.Size := GetBufferSize(Field.DataType, Field.Length);
end;

procedure TSQLiteRecordSet.CreateFieldDescs;
var
  OldCursorState: TCursorState;
  Field: TCRFieldDesc;
  FieldsCount: Integer;
  i: integer;
begin
  OldCursorState := FCommand.GetCursorState;
  if OldCursorState = csInactive then
    InternalPrepare;

  try
    if FCommand.CommandType <> ctCursor then
      raise Exception.Create(SNotRows);

    FTablesInfo.BeginUpdate;
    try
      if not FCommand.FConnection.FAPI.IsMetaDataAPIAvailable then begin
        FTablesInfo.CaseSensitive := False;
        FCommand.SQLInfo.ParseTablesInfo(FCommand.SQL, FTablesInfo);
      end;

      FieldsCount := FCommand.FConnection.FAPI.sqlite3_column_count(FCommand.FStmt);
      for i := 0 to FieldsCount - 1 do begin
        Field := TCRFieldDesc(CreateFieldDesc);
        DescribeFieldDesc(Field, i);

        FFields.Add(Field);
      end;
    finally
      FTablesInfo.EndUpdate;
    end;
  finally
    if OldCursorState = csInactive then
      InternalUnPrepare;
  end;
end;

function TSQLiteRecordSet.IdentityFieldIsData: boolean;
begin
  Result := True;
end;

procedure TSQLiteRecordSet.InternalClose;
begin
  if FCommand.GetCursorState > csPrepared then
    FCommand.SetCursorState(csPrepared);

  inherited;

  if not Prepared then
    InternalUnprepare;
end;

procedure TSQLiteRecordSet.FetchBlock(Block: PBlockHeader; FetchBack: boolean; out RowsObtained: Integer);
var
  Item: IntPtr;
  Res: integer;
begin
  Item := PtrOffset(Block, SizeOf(TBlockHeader));
  RowsObtained := 1;
  while True do begin
    if FDumpData then
      ReadFieldValuesForDump(PtrOffset(Item, SizeOf(TItemHeader)))
    else
      ReadFieldValues(PtrOffset(Item, SizeOf(TItemHeader)));

    Res := FCommand.FConnection.FAPI.sqlite3_step(FCommand.FStmt);
    if Res = SQLITE_DONE then begin
      FCommand.SetCursorState(csFetched);
      break;
    end
    else
      if Res <> SQLITE_ROW then
        Check(Res);

    if RowsObtained = FFetchRows then
      break;
    Item := PtrOffset(Item, RecordSize + SizeOf(TItemHeader));
    Inc(RowsObtained);
  end;
end;

function TSQLiteRecordSet.NeedUnPrepareAfterFetch: boolean;
begin
  Result := not Prepared; {AutoPrepare}
end;

procedure TSQLiteRecordSet.ReadFieldValues(RecBuf: IntPtr);
var
{$IFDEF VER7P}
  FmtSet: TFormatSettings;
{$ELSE}
  OldDateSeparator, OldTimeSeparator, OldDecimalSeparator: char;
  OldDateFormat, OldTimeFormat: string;
{$ENDIF}

  procedure MoveToBufferAnsi(const Str: AnsiString; Buf: IntPtr; Len: integer);
  begin
  {$IFNDEF CLR}
    Move(Pointer(Str)^, Buf^, Len);
  {$ELSE}
    Marshal.Copy(TBytes(Str), 0, Buf, Len);
  {$ENDIF}
  end;

  procedure MoveToBufferUni(const Str: WideString; Buf: IntPtr; Len: integer);
  {$IFDEF CLR}
  var
    Bytes: TBytes;
  {$ENDIF}
  begin
  {$IFNDEF CLR}
    Move(Pointer(Str)^, Buf^, Len * 2);
  {$ELSE}
    SetLength(Bytes, Len * 2);
    Encoding.Unicode.GetBytes(Str, 0, Len, Bytes, 0);
    Marshal.Copy(Bytes, 0, Buf, Len * 2);
  {$ENDIF}
  end;

  procedure SetSeparators;
  begin
  {$IFDEF VER7P}
    FmtSet.DateSeparator := FCommand.FDateSeparator;
    FmtSet.ShortDateFormat := FCommand.FShortDateFormat;
    FmtSet.TimeSeparator :=FCommand.FTimeSeparator;
    FmtSet.ShortTimeFormat := FCommand.FShortTimeFormat;
    FmtSet.DecimalSeparator := '.';
  {$ELSE}
    OldDateSeparator := DateSeparator;
    OldDateFormat := ShortDateFormat;
    OldTimeSeparator := TimeSeparator;
    OldTimeFormat := LongTimeFormat;
    OldDecimalSeparator := DecimalSeparator;
    DateSeparator := FCommand.FDateSeparator;
    ShortDateFormat := FCommand.FShortDateFormat;
    TimeSeparator := FCommand.FTimeSeparator;
    ShortTimeFormat := FCommand.FShortTimeFormat;
    DecimalSeparator := '.';
  {$ENDIF}
  end;

{$IFNDEF VER7P}
  procedure RestoreSeparators;
  begin
    DateSeparator := OldDateSeparator;
    ShortDateFormat := OldDateFormat;
    LongTimeFormat := OldTimeFormat;
    TimeSeparator := OldTimeSeparator;
    DecimalSeparator := OldDecimalSeparator;
  end;
{$ENDIF}

var
  i, j, ValueType, Len,
  Separators, FieldIndex: integer;
  Field: TFieldDesc;
  FieldBuf, HeapBuf, BlobData: IntPtr;
  Blob: TBlob;
  Piece: PPieceHeader;
  val_int: integer;
  val_int64: int64;
  val_float: double;
  val_cur: currency;
  val_bcd: TBcd;
  val_str: string;
  val_ws: WideString;
  val_as: AnsiString;
  val_blob: IntPtr;
  val_date: TDateTime;
{$IFDEF CLR}
  Bytes: TBytes;
{$ENDIF}
begin
  CreateComplexFields(RecBuf, True);

  for i := 0 to FFields.Count - 1 do begin
    Field := FFields[i];
    FieldBuf := PtrOffset(RecBuf, Field.Offset);
    FieldIndex := Field.ActualFieldNo;
    if (Field.ActualFieldNo > -1) {KeyOnly SmartFetchState} then
      ValueType := FCommand.FConnection.FAPI.sqlite3_column_type(FCommand.FStmt, FieldIndex)
    else
      ValueType := SQLITE_NULL;
    if ValueType = SQLITE_NULL then
      Marshal.WriteByte(RecBuf, DataSize + i, 1)
    else begin
      Marshal.WriteByte(RecBuf, DataSize + i, 0);
      case Field.DataType of
        dtSmallint: begin
          val_int := FCommand.FConnection.FAPI.sqlite3_column_int(FCommand.FStmt, FieldIndex);
          Marshal.WriteInt16(FieldBuf, val_int);
        end;
        dtInteger: begin
          val_int := FCommand.FConnection.FAPI.sqlite3_column_int(FCommand.FStmt, FieldIndex);
          Marshal.WriteInt32(FieldBuf, val_int);
        end;
        dtWord: begin
          val_int := FCommand.FConnection.FAPI.sqlite3_column_int(FCommand.FStmt, FieldIndex);
          Marshal.WriteInt16(FieldBuf, Word(val_int));
        end;
        dtLongWord: begin
          val_int := FCommand.FConnection.FAPI.sqlite3_column_int(FCommand.FStmt, FieldIndex);
          Marshal.WriteInt32(FieldBuf, LongWord(val_int));
        end;
        dtLargeint: begin
          val_int64 := FCommand.FConnection.FAPI.sqlite3_column_int64(FCommand.FStmt, FieldIndex);
          Marshal.WriteInt64(FieldBuf, val_int64);
        end;
        dtFloat, dtCurrency: begin
          val_float := FCommand.FConnection.FAPI.sqlite3_column_double(FCommand.FStmt, FieldIndex);
          Marshal.WriteInt64(FieldBuf,
            BitConverter.DoubleToInt64Bits(val_float));
        end;
        dtBCD: begin
          if ValueType = SQLITE_INTEGER then
            val_cur := FCommand.FConnection.FAPI.sqlite3_column_int64(FCommand.FStmt, FieldIndex)
          else
            val_cur := FCommand.FConnection.FAPI.sqlite3_column_double(FCommand.FStmt, FieldIndex);
        {$IFDEF CLR}
          val_int64 := Decimal.ToOACurrency(val_cur);
        {$ELSE}
          val_int64 := PInt64(@val_cur)^;
        {$ENDIF}
          Marshal.WriteInt64(FieldBuf, val_int64);
        end;
      {$IFNDEF FPC}
        dtFMTBCD: begin
          if ValueType = SQLITE_INTEGER then
            val_str := IntToStr(FCommand.FConnection.FAPI.sqlite3_column_int64(FCommand.FStmt, FieldIndex))
          else
            val_str := FloatToStr(FCommand.FConnection.FAPI.sqlite3_column_double(FCommand.FStmt, FieldIndex));
          val_bcd := StrToBcd(val_str);
        {$IFDEF CLR}
          Bytes := TBcd.ToBytes(val_bcd);
          Marshal.Copy(Bytes, 0, FieldBuf, SizeOfTBcd);
        {$ELSE}
          PBcd(FieldBuf)^ := val_bcd;
        {$ENDIF}
        end;
      {$ENDIF}
        dtBoolean: begin
          val_int64 := FCommand.FConnection.FAPI.sqlite3_column_int64(FCommand.FStmt, FieldIndex);
          if val_int64 <> 0 then
            val_int64 := 1;
          Marshal.WriteInt16(FieldBuf, val_int64);
        end;
        dtString: begin
          val_as := FCommand.FConnection.DecodeStringA(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_text(FCommand.FStmt, FieldIndex))));
          Len := Min(LengthA(val_as), Field.Length);
          MoveToBufferAnsi(val_as, FieldBuf, Len);
          Marshal.WriteByte(FieldBuf, Len, 0);
        end;
        dtWideString: begin
          val_ws := FCommand.FConnection.DecodeStringW(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_text(FCommand.FStmt, FieldIndex))), True);
          Len := Min(Length(val_ws), Field.Length);
          MoveToBufferUni(val_ws, FieldBuf, Len);
          Marshal.WriteInt16(FieldBuf, Len * 2, 0);
        end;
        dtExtString: begin
          val_as := FCommand.FConnection.DecodeStringA(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_text(FCommand.FStmt, FieldIndex))));
          Len := Min(LengthA(val_as), Field.Length);
          HeapBuf := StringHeap.NewBuf(Len + 1);
          MoveToBufferAnsi(val_as, HeapBuf, Len);
          Marshal.WriteByte(HeapBuf, Len, 0);
          Marshal.WriteIntPtr(FieldBuf, HeapBuf);
        end;
        dtExtWideString: begin
          val_ws := FCommand.FConnection.DecodeStringW(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_text(FCommand.FStmt, FieldIndex))), True);
          Len := Min(Length(val_ws), Field.Length);
          HeapBuf := StringHeap.NewBuf((Len + 1) * 2);
          MoveToBufferUni(val_ws, HeapBuf, Len);
          Marshal.WriteInt16(HeapBuf, Len * 2, 0);
          Marshal.WriteIntPtr(FieldBuf, HeapBuf);
        end;
        dtMemo: begin
          val_as := FCommand.FConnection.DecodeStringA(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_text(FCommand.FStmt, FieldIndex))));
          Len := LengthA(val_as);
          if Len > 0 then begin
            Blob := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(FieldBuf)));
            Blob.AllocPiece(Piece, Len);
            BlobData := PtrOffset(Piece, SizeOf(TPieceHeader));
            MoveToBufferAnsi(val_as, BlobData, Len);
            Piece.Used := Len;
            Blob.AppendPiece(Piece);
          end;
        end;
        dtWideMemo: begin
          val_ws := FCommand.FConnection.DecodeStringW(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_text(FCommand.FStmt, FieldIndex))), True);
          Len := Length(val_ws);
          if Len > 0 then begin
            Blob := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(FieldBuf)));
            Blob.AllocPiece(Piece, Len * 2);
            BlobData := PtrOffset(Piece, SizeOf(TPieceHeader));
            MoveToBufferUni(val_ws, BlobData, Len);
            Piece.Used := Len * 2;
            Blob.AppendPiece(Piece);
          end;
        end;
        dtBlob: begin
          Len := FCommand.FConnection.FAPI.sqlite3_column_bytes(FCommand.FStmt, FieldIndex);
          if Len > 0 then begin
            val_blob := FCommand.FConnection.FAPI.sqlite3_column_blob(FCommand.FStmt, FieldIndex);
            Blob := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(FieldBuf)));
            Blob.AllocPiece(Piece, Len);
            BlobData := PtrOffset(Piece, SizeOf(TPieceHeader));
            CopyBuffer(val_blob, BlobData, Len);
            Piece.Used := Len;
            Blob.AppendPiece(Piece);
          end;
        end;
        dtVarBytes: begin
          Len := FCommand.FConnection.FAPI.sqlite3_column_bytes(FCommand.FStmt, FieldIndex);
          Marshal.WriteInt16(FieldBuf, Len);
          if Len > 0 then begin
            val_blob := FCommand.FConnection.FAPI.sqlite3_column_blob(FCommand.FStmt, FieldIndex);
            CopyBuffer(val_blob, PtrOffset(FieldBuf, 2), Len);
          end;
        end;
        dtExtVarBytes: begin
          Len := FCommand.FConnection.FAPI.sqlite3_column_bytes(FCommand.FStmt, FieldIndex);
          HeapBuf := StringHeap.NewBuf(Len + 2);
          Marshal.WriteInt16(HeapBuf, Len);
          if Len > 0 then begin
            val_blob := FCommand.FConnection.FAPI.sqlite3_column_blob(FCommand.FStmt, FieldIndex);
            CopyBuffer(val_blob, PtrOffset(HeapBuf, 2), Len);
          end;
          Marshal.WriteIntPtr(FieldBuf, HeapBuf);
        end;
        dtDate, dtTime, dtDateTime{$IFDEF VER6P}{$IFNDEF FPC}, dtSQLTimeStamp{$ENDIF}{$ENDIF}: begin
          val_str := FCommand.FConnection.DecodeStringW(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_text(FCommand.FStmt, FieldIndex))));
          val_date := 0;
          Separators := 0;
          Len := Length(val_str);
          for j := Len - 1 downto 2 do begin
            if CharInSet(val_str[j], [':', '-', '.', ',', '/']) then
              Inc(Separators);
            if Separators = 2 then begin
              SetSeparators;
            {$IFDEF VER7P}
              val_date := StrToDateTimeDef(val_str, 0, FmtSet);
            {$ELSE}
              try
                val_date := StrToDateTimeDef(val_str, 0);
              finally
                RestoreSeparators;
              end;
            {$ENDIF}
              break;
            end
            else if j = 2 then
              if Separators > 0 then begin
                SetSeparators;
              {$IFDEF VER7P}
                val_float := StrToFloat(val_str, FmtSet);
              {$ELSE}
                try
                  val_float := StrToFloat(val_str);
                finally
                  RestoreSeparators;
                end;
              {$ENDIF}
                if FCommand.FConnection.FNativeDate then
                  val_date := JulianDateToDateTime(val_float)
                else
                  val_date := val_float;
              end
              else
                if FCommand.FConnection.FNativeDate then
                  val_date := UnixToDateTime(StrToInt(val_str))
                else
                  val_date := StrToInt(val_str);
          end;
          if Field.DataType = dtDate then
            val_date := Trunc(val_date)
          else
          if Field.DataType = dtTime then
            val_date := Abs(Frac(val_date));
          Marshal.WriteInt64(FieldBuf, BitConverter.DoubleToInt64Bits(val_date));
        end;
      else
        Assert(False);
      end;
    end;
  end;
end;

procedure TSQLiteRecordSet.ReadFieldValuesForDump(RecBuf: IntPtr);

  procedure MoveToBuffer(const Str: WideString; Buf: IntPtr; Len: integer);
  {$IFDEF CLR}
  var
    Bytes: TBytes;
  {$ENDIF}
  begin
  {$IFNDEF CLR}
    Move(Pointer(Str)^, Buf^, Len * 2);
  {$ELSE}
    SetLength(Bytes, Len * 2);
    Encoding.Unicode.GetBytes(Str, 0, Len, Bytes, 0);
    Marshal.Copy(Bytes, 0, Buf, Len * 2);
  {$ENDIF}
  end;

var
  i, ValueType, Len: integer;
  Field: TFieldDesc;
  FieldBuf, BlobData: IntPtr;
  Blob: TBlob;
  Piece: PPieceHeader;
  val_str: WideString;
  val_blob: IntPtr;
  s: string;
{$IFDEF CLR}
  Bytes, Hex: TBytes;
{$ENDIF}
begin
  CreateComplexFields(RecBuf, True);

  for i := 0 to FFields.Count - 1 do begin
    Field := FFields[i];
    Marshal.WriteByte(RecBuf, DataSize + i, 0);
    FieldBuf := PtrOffset(RecBuf, Field.Offset);
    ValueType := FCommand.FConnection.FAPI.sqlite3_column_type(FCommand.FStmt, i);
    case ValueType of
      SQLITE_INTEGER:
        val_str := IntToStr(FCommand.FConnection.FAPI.sqlite3_column_int64(FCommand.FStmt, i));
      SQLITE_FLOAT:
        val_str := StringReplace(FloatToStr(FCommand.FConnection.FAPI.sqlite3_column_double(FCommand.FStmt, i)),
          {$IFDEF VER16P}FormatSettings.{$ENDIF}DecimalSeparator, '.', []);
      SQLITE_TEXT:
        val_str := FCommand.FConnection.DecodeStringW(QuotedStr(AnsiString({$IFDEF CLR}Marshal.PtrToStringAnsi{$ENDIF}(FCommand.FConnection.FAPI.sqlite3_column_text(FCommand.FStmt, i)))));
      SQLITE_BLOB: begin
        Len := FCommand.FConnection.FAPI.sqlite3_column_bytes(FCommand.FStmt, i);
        val_blob := FCommand.FConnection.FAPI.sqlite3_column_blob(FCommand.FStmt, i);
      {$IFDEF CLR}
        SetLength(Bytes, Len);
        Marshal.Copy(val_blob, Bytes, 0, Len);
        SetLength(Hex, Len * 2);
        BinToHex(Bytes, 0, Hex, 0, Len);
        s := AnsiString(Hex);
      {$ELSE}
        SetLength(s, Len * 2);
        BinToHex(val_blob, PChar(s), Len);
      {$ENDIF}
        val_str := WideString('X''' + s + '''');
      end;
      SQLITE_NULL:
        val_str := 'NULL';
    end;

    Len := Length(val_str);
    if Len > 0 then begin
      Blob := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(FieldBuf)));
      Blob.AllocPiece(Piece, Len * 2);
      BlobData := PtrOffset(Piece, SizeOf(TPieceHeader));
      MoveToBuffer(val_str, BlobData, Len);
      Piece.Used := Len * 2;
      Blob.AppendPiece(Piece);
    end;
  end;
end;

{ TSQLiteTableInfo }

function TSQLiteTableInfo.GetTableNameFull: string;
begin
  Result := FTableNameFull;
end;

procedure TSQLiteTableInfo.SetTableNameFull(const Value: string);
begin
  FTableNameFull := Value;
end;

{ TSQLiteMetaData }

constructor TSQLiteMetaData.Create;
begin
  inherited;
end;

destructor TSQLiteMetaData.Destroy;
begin
  inherited;
end;

function TSQLiteMetaData.GetConnection: TSQLiteConnection;
begin
  Result := TSQLiteRecordSet(FRecordSet).FCommand.FConnection;
end;

function TSQLiteMetaData.GetTypesForSQL(const ObjectTypes: string; AllTypes: array of string): string;
var
  i: integer;
  Res: TBooleanArray;
begin
  Res := ParseTypes(ObjectTypes, AllTypes);
  Result := '';
  for i := 0 to High(AllTypes) do
    if Res[i] then begin
      if Result <> '' then
        Result := Result + ', ';
      Result := Result + '''' + AllTypes[i] + '''';
    end;
  if Result = '' then
    Result := '''''';
end;

function TSQLiteMetaData.CreateRecordSet: TCRRecordSet;
begin
  Result := TSQLiteRecordSet.Create;
end;

procedure TSQLiteMetaData.ParseTableSQL(const SQL: string; var Members: TTableMembers);
var
  Lexem, s: string;
  Parser: TLiteParser;
  BracketCount, Code, i, c, j: integer;
  IsLast, InDataType, InDefault, InConstraints: boolean;
begin
  Members.Columns := nil;
  Members.Indexes := nil;

  Parser := TLiteParser.Create(SQL);
  try
    Parser.OmitBlank := True;
    Parser.OmitComment := True;
    Parser.Uppered := False;
    Parser.QuotedString := True;
    if Parser.ToLexem('(') then begin
      BracketCount := 0;
      IsLast := False;
      InConstraints := False;
      c := -1;
      repeat
        Code := Parser.GetNext(Lexem);
        if (Code = lcIdent) or
           (Code > Parser.SymbolLexems.Count) and (Code <= Parser.SymbolLexems.Count + Parser.KeywordLexems.Count)
        then begin
          s := LowerCase(Lexem);
          if (s = 'constraint') or (s = 'primary') or (s = 'foreign')
            or (s = 'check') or (s = 'unique')
          then begin
            InConstraints := True;
            break;
          end;
          c := Length(Members.Columns);
          SetLength(Members.Columns, c + 1);
          Members.Columns[c].Name := SQLiteInfo.UnQuote(Lexem);
        end
        else
          break;

        InDataType := True;
        InDefault := False;
        while True do begin
          Code := Parser.GetNext(Lexem);

          if (c >= 0) and
            ( (Code = lcIdent) or (Code > Parser.SymbolLexems.Count) and
            (Code <= Parser.SymbolLexems.Count + Parser.KeywordLexems.Count) )
          then begin
            s := LowerCase(Lexem);
            if (s = 'primary') or (s = 'unique') then begin
              i := Length(Members.Indexes);
              SetLength(Members.Indexes, i + 1);
              if s = 'primary' then
                Members.Indexes[i].IndexType := itPrimaryKey
              else if s = 'unique' then
                Members.Indexes[i].IndexType := itUnique
              else
                Members.Indexes[i].IndexType := itNonUnique;
              SetLength(Members.Indexes[i].ColumnInfo, 1);
              Members.Indexes[i].ColumnInfo[0].ColumnIndex := c;
              InDataType := False;
              if s = 'primary' then begin
                if LowerCase(Members.Columns[c].DataType) = 'integer' then
                  Members.Columns[c].IsAutoincrement := True;
                Parser.GetNextToken; // key
                Code := Parser.GetNext(Lexem);
                if Code <> lcEnd then
                  Members.Indexes[i].ColumnInfo[0].IsDesc := LowerCase(Lexem) = 'desc';
              end;
            end
            else
            if (s = 'foreign') or (s = 'check') or (s = 'autoincrement')
              or (s = 'not') or (s = 'null')
            then
              InDataType := False
            else
            if s = 'default' then begin
              InDefault := True;
              InDataType := False;
              continue;
            end
            else
            if InDataType then begin
              if Members.Columns[c].DataType <> '' then
                Members.Columns[c].DataType := Members.Columns[c].DataType + ' ';
              Members.Columns[c].DataType := Members.Columns[c].DataType + Lexem;
            end;
          end;

          if (c >= 0) then
            if InDefault then begin
              Members.Columns[c].Default := Lexem;
              InDefault := False;
            end;

          if Code = lcEnd then begin
            IsLast := True;
            break;
          end;

          if (Lexem = ',') and (BracketCount = 0) then
            break
          else
          if Lexem = '(' then
            Inc(BracketCount)
          else
          if Lexem = ')' then
            if BracketCount = 0 then begin
              IsLast := True;
              break
            end
            else
              Dec(BracketCount);
        end;
      until IsLast;

      if not InConstraints then
        exit;

      IsLast := False;
      repeat
        s := LowerCase(Lexem);
        if s = 'constraint' then
          repeat
            Code := Parser.GetNext(Lexem);
            s := LowerCase(Lexem);
          until (Code = lcEnd) or (s = 'primary') or (s = 'foreign')
            or (s = 'check') or (s = 'unique');

        if (s = 'primary') or (s = 'unique') then begin
          if not Parser.ToLexem('(') then
            break;
          i := Length(Members.Indexes);
          SetLength(Members.Indexes, i + 1);
          if s = 'primary' then
            Members.Indexes[i].IndexType := itPrimaryKey
          else if s = 'unique' then
            Members.Indexes[i].IndexType := itUnique
          else
            Members.Indexes[i].IndexType := itNonUnique;
          Members.Indexes[i].ColumnInfo := nil;
          repeat
            Code := Parser.GetNext(Lexem);
            if (Code = lcEnd) or (Lexem = ')') then
              break;
            if (Lexem <> ',') then begin
              for c := 0 to Length(Members.Columns) - 1 do
                if LowerCase(SQLiteInfo.UnQuote(Lexem)) = LowerCase(Members.Columns[c].Name) then begin
                  j := Length(Members.Indexes[i].ColumnInfo);
                  SetLength(Members.Indexes[i].ColumnInfo, j + 1);
                  Members.Indexes[i].ColumnInfo[j].ColumnIndex := c;

                  Code := Parser.GetNext(Lexem); // asc, desc
                  if Code <> lcEnd then begin
                    Members.Indexes[i].ColumnInfo[j].IsDesc := LowerCase(Lexem) = 'desc';
                    if Lexem = ')' then
                      Parser.Back;
                  end;
                  break;
                end;
            end;
          until False;
        end;

        while True do begin
          Code := Parser.GetNext(Lexem);

          if Code = lcEnd then begin
            IsLast := True;
            break;
          end;

          if (Lexem = ',') and (BracketCount = 0) then
            break
          else
          if Lexem = '(' then
            Inc(BracketCount)
          else
          if Lexem = ')' then
            if BracketCount = 0 then begin
              IsLast := True;
              break
            end
            else
              Dec(BracketCount);
        end;

        if IsLast then
          break;

        Code := Parser.GetNext(Lexem);
      until Code = lcEnd;
    end;

    for i := Low(Members.Indexes) to High(Members.Indexes) do
      if Members.Indexes[i].IndexType = itPrimaryKey then
        if length(Members.Indexes[i].ColumnInfo) = 1 then
          if Members.Columns[Members.Indexes[i].ColumnInfo[0].ColumnIndex].DataType = 'integer' then
            Members.Columns[Members.Indexes[i].ColumnInfo[0].ColumnIndex].IsAutoincrement := True;
  finally
    Parser.Free;
  end;
end;

procedure TSQLiteMetaData.ParseIndexSQL(const SQL: string; var Members: TTableMembers);
var
  Lexem: string;
  Parser: TLiteParser;
  Code, c, j: integer;
  s: string;
begin
  Members.Indexes := nil;

  Parser := TLiteParser.Create(SQL);
  try
    Parser.OmitBlank := True;
    Parser.OmitComment := True;
    Parser.Uppered := False;
    Parser.QuotedString := True;
    repeat
      Code := Parser.GetNext(Lexem);
      s := LowerCase(Lexem);
      if s = 'create' then
        break;
    until Code = lcEnd;

    Parser.GetNext(Lexem);
    s := LowerCase(Lexem);

    if Parser.ToLexem('(') then begin
      SetLength(Members.Indexes, 1);
      if s = 'unique' then
        Members.Indexes[0].IndexType := itUnique
      else
        Members.Indexes[0].IndexType := itNonUnique;

      repeat
        Code := Parser.GetNext(Lexem);
        if (Code = lcEnd) or (Lexem = ')') then
          break;
        if (Lexem <> ',') then begin
          Lexem := LowerCase(SQLiteInfo.UnQuote(Lexem));
          for c := 0 to Length(Members.Columns) - 1 do
            if Lexem = LowerCase(SQLiteInfo.UnQuote(Members.Columns[c].Name)) then begin
              j := Length(Members.Indexes[0].ColumnInfo);
              SetLength(Members.Indexes[0].ColumnInfo, j + 1);
              Members.Indexes[0].ColumnInfo[j].ColumnIndex := c;

              Code := Parser.GetNext(Lexem); // asc, desc
              if Code <> lcEnd then
                Members.Indexes[0].ColumnInfo[j].IsDesc := LowerCase(Lexem) = 'desc';
              break;
            end;
        end;
      until False;
    end;
  finally
    Parser.Free;
  end;
end;

function TSQLiteMetaData.GetTables(Restrictions: TStrings): TData;
const
  SQL = 'SELECT name, type ' +
    'FROM sqlite_master ' +
    'WHERE %s lower(type) IN (%s) ORDER BY name';

{$IFDEF DBX_METADATA}
  dnRECNO         = 1;
  dnCATALOG_NAME  = 2;
  dnSCHEMA_NAME   = 3;
  dnTABLE_NAME    = 4;
  dnTABLE_TYPE    = 5;
{$ELSE}
  dnCATALOG_NAME  = 1;
  dnSCHEMA_NAME   = 2;
  dnTABLE_NAME    = 3;
  dnTABLE_TYPE    = 4;
{$ENDIF}
var
  WhereClause, TableName, TableTypes, QuotedTypes: string;
begin
  TableName := Trim(Restrictions.Values['TABLE_NAME']);
  TableTypes := Trim(Restrictions.Values['TABLE_TYPE']);

  WhereClause := '';
  AddWhere(WhereClause, 'lower(name)', LowerCase(TableName));
  if WhereClause <> '' then
    WhereClause := WhereClause + ' AND ';

  QuotedTypes := GetTypesForSQL(LowerCase(TableTypes), ['table', 'view']);

  FRecordSet.SetSQL(Format(SQL, [WhereClause, QuotedTypes]));
  FRecordSet.Open;

  CreateTablesFields;
  FMemData.Open;
  FMemDataHelper.AllocBuffer;
  FRecordSetHelper.AllocBuffer;
  while FRecordSetHelper.NextRecord do begin
    FMemDataHelper.InitRecord;
  {$IFDEF DBX_METADATA}
    CopyRecord([1], [dnTABLE_NAME]);
    if LowerCase(FRecordSetHelper.FieldValues[2]) = 'table' then
      FMemDataHelper.FieldValues[dnTABLE_TYPE] := 1
    else if LowerCase(FRecordSetHelper.FieldValues[2]) = 'view' then
      FMemDataHelper.FieldValues[dnTABLE_TYPE] := 2
    else
      FMemDataHelper.FieldValues[dnTABLE_TYPE] := 0; // unknown
  {$ELSE}
    CopyRecord([1, 2], [dnTABLE_NAME, dnTABLE_TYPE]);
  {$ENDIF}
    FMemDataHelper.AppendRecord;
  end;

  FRecordSet.Close;
  FMemData.SetToBegin;
  Result := FMemData;
end;

function TSQLiteMetaData.GetColumns(Restrictions: TStrings): TData;
const
  SQL = 'SELECT sql FROM sqlite_master ' +
    'WHERE Lower(name) = ''%s'' AND type = ''table''';

  dnCATALOG       = 1;
  dnSCHEMA        = 2;
  dnTABLE_NAME    = 3;
  dnCOLUMN_NAME   = 4;
  dnPOSITION      = 5;
  dnDATA_TYPE     = 6;
  dnLENGTH        = 7;
  dnPRECISION     = 8;
  dnSCALE         = 9;
  dnNULLABLE      = 10;
  dnDEFAULT       = 11;
var
  TableName, ColumnName, TableSQL: string;
  Members: TTableMembers;
  i: integer;
begin
  TableName := Trim(Restrictions.Values['TABLE_NAME']);
  ColumnName := Trim(Restrictions.Values['COLUMN_NAME']);

  if TableName = '' then
    raise Exception.CreateFmt(SRestrictionMustBeSet, ['TABLE_NAME']);

  FRecordSet.SetSQL(Format(SQL, [LowerCase(SQLiteInfo.UnQuote(TableName))]));
  FRecordSet.Open;

  CreateColumnsFields;
  FMemData.Open;

  FRecordSetHelper.AllocBuffer;
  if FRecordSetHelper.NextRecord then begin
    TableSQL := VarToStr(FRecordSetHelper.FieldValues[1]);
    if TableSQL <> '' then begin
      ParseTableSQL(TableSQL, Members);
      FMemDataHelper.AllocBuffer;
      for i := 0 to Length(Members.Columns) - 1 do begin
        if (ColumnName = '') or
          (LowerCase(Members.Columns[i].Name) = LowerCase(ColumnName))
        then begin
          FMemDataHelper.InitRecord;
          FMemDataHelper.FieldValues[dnTABLE_NAME] := TableName;
          FMemDataHelper.FieldValues[dnCOLUMN_NAME] := Members.Columns[i].Name;
          FMemDataHelper.FieldValues[dnPOSITION] := i + 1;
          FMemDataHelper.FieldValues[dnDATA_TYPE] := Members.Columns[i].DataType;
          if Members.Columns[i].IsAutoincrement then
            FMemDataHelper.FieldValues[dnNULLABLE] := 0
          else
            FMemDataHelper.FieldValues[dnNULLABLE] := 1;
          FMemDataHelper.FieldValues[dnDEFAULT] := Members.Columns[i].Default;
          FMemDataHelper.AppendRecord;
        end;
      end;
    end;
  end;

  FRecordSet.Close;
  FMemData.SetToBegin;
  Result := FMemData;
end;

function TSQLiteMetaData.GetProcedures(Restrictions: TStrings): TData;
begin
  CreateProceduresFields;
  FMemData.Open;
  Result := FMemData;
end;

function TSQLiteMetaData.GetProcedureParameters(Restrictions: TStrings): TData;
begin
  CreateProcedureParametersFields;
  FMemData.Open;
  Result := FMemData;
end;

function TSQLiteMetaData.GetIndexes(Restrictions: TStrings): TData;
const
  SQL1 = 'SELECT sql FROM sqlite_master ' +
    'WHERE Lower(name) = ''%s'' AND type = ''table''';

  SQL2 = 'SELECT name, sql FROM sqlite_master ' +
    'WHERE Lower(tbl_name) = ''%s'' AND type = ''index'' AND sql is not null';

  dnTABLE_CATALOG = 1;
  dnTABLE_SCHEMA  = 2;
  dnTABLE_NAME    = 3;
  dnINDEX_CATALOG = 4;
  dnINDEX_SCHEMA  = 5;
  dnINDEX_NAME    = 6;
  dnUNIQUE        = 7;

var
  TableName, DDL, IndName: string;
  Members: TTableMembers;
  i: integer;
begin
  TableName := Trim(Restrictions.Values['TABLE_NAME']);
  //IndexName := Trim(Restrictions.Values['INDEX_NAME']);

  if TableName = '' then
    raise Exception.CreateFmt(SRestrictionMustBeSet, ['TABLE_NAME']);

  FRecordSet.SetSQL(Format(SQL1, [LowerCase(TableName)]));
  FRecordSet.Open;

  CreateIndexesFields;
  FMemData.Open;
  FMemDataHelper.AllocBuffer;

  FRecordSetHelper.AllocBuffer;
  if FRecordSetHelper.NextRecord then begin
    DDL := VarToStr(FRecordSetHelper.FieldValues[1]);
    if DDL <> '' then begin
      ParseTableSQL(DDL, Members);
      for i := 0 to Length(Members.Indexes) - 1 do begin
        FMemDataHelper.InitRecord;
        FMemDataHelper.FieldValues[dnTABLE_NAME] := TableName;
        FMemDataHelper.FieldValues[dnINDEX_NAME] := 'autoindex' + IntToStr(i);
        FMemDataHelper.FieldValues[dnUNIQUE] := 1;
        FMemDataHelper.AppendRecord;
      end;
    end;
  end;
  FRecordSet.Close;

  FRecordSet.SetSQL(Format(SQL2, [LowerCase(TableName)]));
  FRecordSet.Open;
  FRecordSetHelper.AllocBuffer;
  while FRecordSetHelper.NextRecord do begin
    IndName := VarToStr(FRecordSetHelper.FieldValues[1]);
    DDL := VarToStr(FRecordSetHelper.FieldValues[2]);
    if DDL <> '' then begin
      ParseIndexSQL(DDL, Members);
      for i := 0 to Length(Members.Indexes) - 1 do begin
        FMemDataHelper.InitRecord;
        FMemDataHelper.FieldValues[dnTABLE_NAME] := TableName;
        FMemDataHelper.FieldValues[dnINDEX_NAME] := IndName;
        if Pos('unique', LowerCase(DDL)) > 0 then
          FMemDataHelper.FieldValues[dnUNIQUE] := 1
        else
          FMemDataHelper.FieldValues[dnUNIQUE] := 0;
        FMemDataHelper.AppendRecord;
      end;
    end;
  end;
  FRecordSet.Close;

  FMemData.SetToBegin;
  Result := FMemData;
end;

function TSQLiteMetaData.GetIndexColumns(Restrictions: TStrings): TData;
const
  SQL1 = 'SELECT sql FROM sqlite_master ' +
    'WHERE Lower(name) = ''%s'' AND type = ''table''';

  SQL2 = 'SELECT name, sql FROM sqlite_master ' +
    'WHERE Lower(tbl_name) = ''%s'' AND type = ''index'' AND sql is not null';

{$IFDEF DBX_METADATA}
  dnRECNO = 1;
  dnCATALOG_NAME = 2;
  dnSCHEMA_NAME = 3;
  dnTABLE_NAME = 4;
  dnINDEX_NAME = 5;
  dnCOLUMN_NAME = 6;
  dnPOSITION = 7;
  dnPKEY_NAME = 8;
  dnINDEX_TYPE = 9;
  dnSORT_ORDER = 10;
  dnFILTER = 11;
{$ELSE}
  dnTABLE_CATALOG   = 1;
  dnTABLE_SCHEMA    = 2;
  dnTABLE_NAME      = 3;
  dnINDEX_CATALOG   = 4;
  dnINDEX_SCHEMA    = 5;
  dnINDEX_NAME      = 6;
  dnCOLUMN_NAME     = 7;
  dnPOSITION        = 8;
  dnSORT_ORDER      = 9;
{$ENDIF}

var
  TableName, IndexName, Uniqueness: string;
  DDL, IndName: string;
{$IFDEF DBX_METADATA}
  IndType: Integer;
{$ENDIF}
  Members: TTableMembers;
  i, j: integer;
begin
  TableName := Trim(Restrictions.Values['TABLE_NAME']);
  IndexName := Trim(Restrictions.Values['INDEX_NAME']);
  Uniqueness := Trim(Restrictions.Values['UNIQUE']);

  if TableName = '' then
    raise Exception.CreateFmt(SRestrictionMustBeSet, ['TABLE_NAME']);

  CreateIndexColumnsFields;
  FMemData.Open;
  FMemDataHelper.AllocBuffer;

  FRecordSet.SetSQL(Format(SQL1, [LowerCase(TableName)]));
  FRecordSet.Open;

  FRecordSetHelper.AllocBuffer;
  if FRecordSetHelper.NextRecord then begin
    DDL := VarToStr(FRecordSetHelper.FieldValues[1]);
    if DDL <> '' then begin
      ParseTableSQL(DDL, Members);
      if Uniqueness <> '0' then begin
        for i := 0 to Length(Members.Indexes) - 1 do begin
          IndName := 'autoindex' + IntToStr(i);
          if (IndexName <> '') and (IndexName <> IndName) then
            continue;
        {$IFDEF DBX_METADATA}
          case Members.Indexes[i].IndexType of
            itPrimaryKey:
              IndType := 6;
            itUnique:
              IndType := 2
          else
            IndType := 1;
          end;
        {$ENDIF}

          for j := 0 to Length(Members.Indexes[i].ColumnInfo) - 1 do begin
            FMemDataHelper.InitRecord;
            FMemDataHelper.FieldValues[dnTABLE_NAME] := TableName;
            FMemDataHelper.FieldValues[dnINDEX_NAME] := IndName;
            FMemDataHelper.FieldValues[dnCOLUMN_NAME] :=
              Members.Columns[Members.Indexes[i].ColumnInfo[j].ColumnIndex].Name;
            FMemDataHelper.FieldValues[dnPOSITION] := j + 1;
            if Members.Indexes[i].ColumnInfo[j].IsDesc then
              FMemDataHelper.FieldValues[dnSORT_ORDER] := 'DESC'
            else
              FMemDataHelper.FieldValues[dnSORT_ORDER] := 'ASC';
          {$IFDEF DBX_METADATA}
            FMemDataHelper.FieldValues[dnINDEX_TYPE] := IndType;
            if IndType = 6 then
              FMemDataHelper.FieldValues[dnPKEY_NAME] := IndName;
          {$ENDIF}
            FMemDataHelper.AppendRecord;
          end;
        end;
      end;
    end;
  end;
  FRecordSet.Close;

  FRecordSet.SetSQL(Format(SQL2, [LowerCase(TableName)]));
  FRecordSet.Open;
  FRecordSetHelper.AllocBuffer;
  while FRecordSetHelper.NextRecord do begin
    IndName := VarToStr(FRecordSetHelper.FieldValues[1]);
    if (IndexName <> '') and (IndexName <> IndName) then
      continue;
    DDL := VarToStr(FRecordSetHelper.FieldValues[2]);
    if DDL <> '' then begin
      if ((Uniqueness <> '0') or (Pos('unique', LowerCase(DDL)) = 0)) and
        ((Uniqueness <> '1') or (Pos('unique', LowerCase(DDL)) > 0))
      then begin
        ParseIndexSQL(DDL, Members);
        for i := 0 to Length(Members.Indexes) - 1 do begin
        {$IFDEF DBX_METADATA}
          case Members.Indexes[i].IndexType of
            itPrimaryKey:
              IndType := 6;
            itUnique:
              IndType := 2
          else
            IndType := 1;
          end;
        {$ENDIF}
          for j := 0 to Length(Members.Indexes[i].ColumnInfo) - 1 do begin
            FMemDataHelper.InitRecord;
            FMemDataHelper.FieldValues[dnTABLE_NAME] := TableName;
            FMemDataHelper.FieldValues[dnINDEX_NAME] := IndName;
            FMemDataHelper.FieldValues[dnCOLUMN_NAME] :=
              Members.Columns[Members.Indexes[i].ColumnInfo[j].ColumnIndex].Name;
            FMemDataHelper.FieldValues[dnPOSITION] := j + 1;
            if Members.Indexes[i].ColumnInfo[j].IsDesc then
              FMemDataHelper.FieldValues[dnSORT_ORDER] := 'DESC'
            else
              FMemDataHelper.FieldValues[dnSORT_ORDER] := 'ASC';
          {$IFDEF DBX_METADATA}
            FMemDataHelper.FieldValues[dnINDEX_TYPE] := IndType;
            if IndType = 6 then
              FMemDataHelper.FieldValues[dnPKEY_NAME] := IndName;
          {$ENDIF}
            FMemDataHelper.AppendRecord;
          end;
        end;
      end;
    end;
  end;
  FRecordSet.Close;

  FMemData.SetToBegin;
  Result := FMemData;
end;

function TSQLiteMetaData.GetConstraints(Restrictions: TStrings): TData;
begin
  CreateConstraintsFields;
  FMemData.Open;
  Result := FMemData;
end;

{ TLiteSQLInfo }

function TSQLiteInfo.LeftQuote: Char;
begin
  Result := '[';
end;

function TSQLiteInfo.RightQuote: Char;
begin
  Result := ']';
end;

function TSQLiteInfo.IdentCase: TIdentCase;
begin
  Result := icMixed;
end;

function TSQLiteInfo.ParamQuoteAllowed: boolean;
begin
  Result := false;
end;

function TSQLiteInfo.IsQuoted(const Value: string): boolean;
var
  l: integer;
begin
  l := Length(Value);
  if (l <= 1) then
    Result := False
  else
    Result :=  (CharInSet(Value[1], ['`', '"', '['])) and (CharInSet(Value[l], ['`', '"', ']']));
end;

procedure TSQLiteInfo.ParseTablesInfo(const SQL: string; TablesInfo: TCRTablesInfo);
var
  i: integer;
begin
  inherited ParseTablesInfo(SQL, TablesInfo);
  for i := 0 to TablesInfo.Count - 1 do
    TSQLiteTableInfo(TablesInfo[i]).TableNameFull := TablesInfo[i].TableName;
end;

{$IFNDEF LITE}

{ TSQLiteLoader }

constructor TSQLiteLoader.Create;
begin
  inherited;
end;

destructor TSQLiteLoader.Destroy;
begin
  inherited;
end;

procedure TSQLiteLoader.CreateCommand;
begin
  FCommand := TSQLiteCommand.Create;
end;

class function TSQLiteLoader.GetRecordSetClass: TCRRecordSetClass;
begin
  Result := TSQLiteRecordSet;
end;

procedure TSQLiteLoader.DoPrepare;
{$IFDEF UNIDACPRO}
var
  Value: variant;
{$ENDIF}
begin
  inherited;

  if UsedConnection <> nil then begin
  {$IFDEF UNIDACPRO}
    UsedConnection.GetProp(prAutoCommit, Value);
    FAutoCommitIsUsed := Value and FAutoCommit;
  {$ELSE}
    FAutoCommitIsUsed := FAutoCommit;
  {$ENDIF}
  end;

  if FAutoCommitIsUsed then begin
    if UsedTransaction.GetInTransaction then
      UsedTransaction.Commit;
    UsedTransaction.StartTransaction;
  end;
end;

procedure TSQLiteLoader.DoLoadRow;
begin
  inherited;

  if FAutoCommitIsUsed then
    if (FLoadedRows mod FAutoCommitRowCount) = 0 then begin
      if UsedTransaction.GetInTransaction then
        UsedTransaction.Commit;
      UsedTransaction.StartTransaction;
    end;
end;

function TSQLiteLoader.UsedConnection: TSQLiteConnection;
begin
  Result := nil;
  if FConnection <> nil then
    Result := TSQLiteConnection(FConnection);
end;

function TSQLiteLoader.UsedTransaction: TSQLiteTransaction;
begin
  if FTransaction <> nil then
    Result := TSQLiteTransaction(FTransaction)
  else
    Result := TSQLiteTransaction(UsedConnection.GetInternalTransaction);
end;

function TSQLiteLoader.SetProp(Prop: integer; const Value: variant): boolean;
begin
  Result := True;
  case Prop of
    prAutoCommit:
      FAutoCommit := Value;
    prAutoCommitRowCount:
      FAutoCommitRowCount := Value;
  else
    Result := inherited SetProp(Prop, Value);
  end;
end;

function TSQLiteLoader.GetProp(Prop: integer; var Value: variant): boolean;
begin
  Result := True;
  case Prop of
    prAutoCommit:
      Value := FAutoCommit;
    prAutoCommitRowCount:
      Value := FAutoCommitRowCount;
  else
    Result := inherited GetProp(Prop, Value);
  end;
end;
{$ENDIF}

initialization
  //ActiveConnectionsCount := 0;
  hLockConnectCount := TCriticalSection.Create;
  SQLiteInfo := TSQLiteInfo.Create(nil);


finalization

  SQLiteInfo.Free;
  hLockConnectCount.Free;

end.
