{ $DEFINE DEBUG_ENABLED } // Enables Debug information
 { $DEFINE DEBUG_VERY_LOUD}
 { $DEFINE DEBUG_LOUD}
{$I asqlite_def.inc}

unit ASGSQLite;
{*_* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Author:       Albert Drent
Description:  SQLite DataSet class (encapsulates the Delphi DataSet Class)
Target:       Delphi 4, 5, 6 and 7, Borland C++ 5 and 6
Creation:     November 2003
Version:      2.0.C beta
EMail:        a.drent@aducom.com (www.aducom.com/sqlite, sqlite.aducom.com)
Support:      support@aducom.com (www.aducom.com/sqlite, sqlite.aducom.com)
Legal issues: Copyright (C) 2003, 2004 by Aducom Software

              Aducom Software
              Eckhartstr 61
              9746 BN  Groningen
              Netherlands

              This software is provided 'as-is', without any express or
              implied warranty.  In no event will the author be held liable
              for any damages arising from the use of this software.

              Permission is granted to anyone to use this software for any
              purpose, including commercial applications, and to alter it
              and redistribute it freely, subject to the following
              restrictions:

              1. The origin of this software must not be misrepresented,
                 you must not claim that you wrote the original software.
                 If you use this software in a product, an acknowledgment
                 in the product documentation would be appreciated but is
                 not required.

              2. Altered source versions must be plainly marked as such, and
                 must not be misrepresented as being the original software.

              3. If you make changes which improves the component you must
                 mail these to aducom as the moderator of the components
                 complete with documentation for the benefits of the community.

              4. You are not allowed to create commercial available components
                 using this software. If you use this source in any way to create
                 your own components, your source should be free of charge,
                 available to anyone. It's a far better idea to distribute your
                 changes through Aducom Software.

              5. This notice may not be removed or altered from any source
                 distribution.

              6. You must register this software by entering the support forum.
                 I like to keep track about where the components are used, so
                 sending a picture postcard to the author would be appreciated.
                 Use a nice stamp and mention your name, street
                 address, EMail address and any comment you like to say.

Acknowledgement
              These components were written for our own needs. Since SQLite is
              a freeware component we like to donate this one to the community
              too. Parts of the code is adapted from several sources, but mainly
              from a sample and the vcl sources of Borland itself . And, of
              course, we did a lot and still are...
To Do
              A lot...
              We are very busy, but will develop on our needs. If anyone can
              contribute, please feel welcome. Alter the source with lots of comment
              and mail it to me. If it works right I will add it to the official
              source and add your credit here below. Before you start, please
              put a request on the forum. It would be a shame and a waste of your
              time if you develop something which already is...
History:
              Nov 8, 2003 First alpha release 1.0.A Albert Drent (c) 2003 Aducom Software
              Nov 11, Release alpha 1.0.B Albert Drent (c) 2003 Aducom Software
                        - added 'param' support
                        - fixed null pointer assignment
                        - added support for partial select (limit / offset)
              Nov 12, Release alpha 1.0.C Albert Drent (c) 2003 Aducom Software
                        - fixed bug in update
                        - support for events
                        - added 'RowsAffected'
              Nov 16, Release beta 1.0.D Albert Drent (c) 2003 Aducom Software
                        - fixed 0 resultlist after any ExecSQL usage
                        - added Transaction support
              Nov 24, Release beta 1.0.E Albert Drent (c) 2003 Aducom Software
                        - StartTransaction will open database if it is'nt already open
                        - Changed resultset method
                        - Added property editor for table names
                        - Added GetTableNames function to TASQLiteDatabase
                        - Added GetIndexNames function to TASQLiteDatabase
                        - Added Open and Close function to TASQLiteDatabase
                        - Added AutoCommit property to TDataSet descendants
                        - Split of source in designtime and runtime package
              Dec 15, Release beta 1.0.F Albert Drent (c) 2003 Aducom Software
                        - Optimized code for speed
                        - Added support for quering databases
                        - Added property for base directory (default dir) TDatabase
                        - Preparations for mastersets and TUpdateSQL (not functional yet)
                        - Solved GPF on stringfields
                        - Solved hangup of Delphi when developing
                        - Solved invalid pointer operation bug while developing
              Jan 11 2004, Release beta 1.0.G Albert Drent (c) 2003, 2004 Aducom Software
                        - Fixed GetTableNames Bug as reported on forum
                        - Solved some minor bugs, several code optimizations
                        - Added 'getfieldnames' procedure to asqlitedb
                        - Added component asqlitepragma for adjustments to sqlite behaviour
                        - Added several property editors to smooth things up
                        - Added component asqliteupdatesql
                        - Added master-detail support for tasqlitetable (not fully tested yet)
                        - Added master-detail support for tasqlitequery (not fully tested yet)
                        - Added filter property to TASQLiteQuery
              Jan 18, 2004, Release beta 1.0.H Albert Drent (c) 2003, 2004 Aducom Software
                        - Added TASQLiteLog component
                        - Support for autoincrement (index primary key)
              Jan 22, 2004, Release beta 1.0.I Albert Drent (c) 2003, 2004 Aducom Software
                        - Solved bug, causing the user to open database first (where
                          it should be opened automatically after open query or table.
                        - datatype text is now treated as a string of max 255 chars.
                        - added samples
                        - added preparations for import and export component
              Jan 26, 2004, Release beta 1.1.A Albert Drent (c) 2003, 2004 Aducom Software
                        - Support for master-detail
                        - Support for TUpdateSQL
              Feb 05, 2004, Release beta 1.1.B Albert Drent (c) 2003, 2004 Aducom Software
                        - Solved small bug: basequery is closed on querychanged event
                        - Solved small bug: basequery is closed on filter change event
                        - Solved bug in design package, by Marc Wetzel(forum)
                        - Notification of BaseQuery removed and added to SQLiteTable
              Feb 24, 2004 Release alpha 1.2.A Albert Drent (c) 2003, 2004 Aducom Software
                        - Locate implemented, working on resultset!
                        - Some small bugfixes
              Feb 25, 2004 Release alpha 1.2.B Albert Drent (c) 2003, 2004 Aducom Software
                        - Reformat of source (Marc Wetzel)
                        - All the debug directives (Marc Wetzel)
                          The debug stuff is added to be able to do more debugging on the
                          components. At this stage there are still some isues which are
                          hard to be find.
                        - Some small bugfixes (Marc Wetzel)
                        - Solved bug with dblookupcombobox, lookup is now shown (variant error)
                        - Derived more classes from TDataset to solve compatibility isues with
                          3rd party software (DevExpress)
                        - Start of port to lower Delphi versions (designintf vs dsgnintf)
              March 25, 2004 Release alpha 1.2.C Albert Drent (c) 2003, 2004 Aducom Software
                        - Added property editor for database directory
                        - Added property editor for sqlitedll directory
                        - Bugix needed for release 13 of SQLite.dll (is compatible to lower
                          dll versions)
                        - More changes to solve compatibility problem with DevExpress (Plato of DevExpress)
                        - Solved some compatibility isues with TDataSet
                        - Solved some compatibility isues with the newest SQLite version (2.8.12)
                        - Added/modified, GetFieldNames, GetPrimaryKeys
                        - Added GetTableInfo
                        - Added StartTransaction and Commit and RollBack to TTable and TQuery
                          This will create a more readable source:
                             db.starttranaction;
                             q.somesql
                             db.commit
                          becomes now
                             with q do begin
                               starttransaction;
                               somesql;
                               try
                                 commit;
                               except
                                 rollback;
                               end;
                             end;
                        - Changed cleanup of components in notification (TheSneak)
                        - Fixed potential AV (so far not reported)
                        - Changed escape of string characters (TheSneak)
                          (might cause incompatibility of older components if you use single
                          quote in data)
                        - Fixed question mark problem in data (TheSneak)
              April 7, 2004
                        - Fixed bug GetxxxxNames, moved pragma (reported by Martini)
                        - Improved some performance isues (TheSneak)
                        - Added GetTableIndexNames(by Martini)
                        - Added support for 'small text blobs', it isn't the real stuff but
                          limited to 20000 characters.
                        - Fixed another compatibility isue with DevExpress
              April 8, 2004
                        - Fixed EnableControls (TheSneak)
                        - Fixed memoryleak ASQLiteQuery.InternalPost (TheSneak)
                        - Downgraded some stuff for support Delphi 4/5
                        - Updated Locate function (by Joel  hottcha@juno.com)
              April 14, 2004 Release beta 1.2.C Albert Drent (c) 2003, 2004 Aducom Software
                        - RawSQL property implemented (supresses parsing of sql data)
                        - published csv release as 1.2.C beta
              April 15, 2004 Release alpha 2.0.A Albert Drent (c) 2003, 2004 Aducom Software
                        - support for real clobs
                        - added fieldtype numeric(x.y)
                        - Implement RawSQL property in TASQLiteQuery
              May 26, 2004 Release beta 2.0.B Albert Drent (c) 2003, 2004 Aducom Software
                        - new procedure: GetGetLastInsertRow
                        - new component: TASQLiteInlineSQL, to be used to contain all
                          kinds of pre-stored sql statements. I.e. for creation of tables in case
                          of an in-memory database, or a local storage for sql statements to
                          simplify sourcecode. In this release it is bound to the ASQLiteDB component.
                        - new component TASQLiteOutput, to be used to generate csv files,
                          xml and html documents. It is NOT bound to ASQLite components but
                          to a datasource (containing any database connection)
                        - implemented bound as described by minhl on the forum
                        - implemented IsNull as described by Kazooie64 on the forum
                        - implemented a uniform datetime implementation by jpierce
              May, 26, 2004 Release beta 2.0.B Albert Drent (c) 2003, 2004 Aducom Software
                        - A few bugfixes
              June, 15, 2004 Release beta 2.0.C Albert Drent (c) 2003, 2004 Aducom Software
                        - A few bugfixes, thanks to Tzvetan

              todo
                        - LocateNearest implemented, working on resultset!
                        - Order resultset on primary key implemented

*_* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * }


interface

uses
  DB,
  Classes,
  Windows,
  SysUtils,
  ASGRout;
const
  SQLiteVersion = 'ASGSQLite V2.0.C beta';

  MaxBuf = 30000;          // max stringbuffer for record (length) (excluding blob's)
  SQLite_OK = 0;           // Successful result
  SQLite_ERROR = 1;        // SQL error or missing database
  SQLite_INTERNAL = 2;     // An internal logic error in SQLite
  SQLite_PERM = 3;         // Access permission denied
  SQLite_ABORT = 4;        // Callback routine requested an abort
  SQLite_BUSY = 5;         // The database file is locked
  SQLite_LOCKED = 6;       // A table in the database is locked
  SQLite_NOMEM = 7;        // A malloc() failed
  SQLite_READONLY = 8;     // Attempt to write a readonly database
  SQLite_INTERRUPT = 9;    // Operation terminated by SQLite_interrupt()
  SQLite_IOERR = 10;       // Some kind of disk I/O error occurred
  SQLite_CORRUPT = 11;     // The database disk image is malformed
  SQLite_NOTFOUND = 12;    // (Internal Only) Table or record not found
  SQLite_FULL = 13;        // Insertion failed because database is full
  SQLite_CANTOPEN = 14;    // Unable to open the database file
  SQLite_PROTOCOL = 15;    // Database lock protocol error
  SQLite_EMPTY = 16;       // (Internal Only) Database table is empty
  SQLite_SCHEMA = 17;      // The database schema changed
  SQLite_TOOBIG = 18;      // Too much data for one row of a table
  SQLite_CONSTRAINT = 19;  // Abort due to contraint violation
  SQLite_MISMATCH = 20;    // Data type mismatch
  Crlf: string = #13#10;
  Q = '''';

type
  pInteger  = ^integer;

  pSmallInt = ^smallint;
  pFloat    = ^extended;
  pBoolean  = ^boolean;

  TConvertBuffer = array[1..255] of char;

  TSQLiteExecCallback = function(Sender: TObject; Columns: integer; ColumnValues: Pointer; ColumnNames: Pointer): integer of object; cdecl;
  TSQLiteBusyCallback = function(Sender: TObject; ObjectName: pchar; BusyCount: integer): integer of object; cdecl;
  TOnData = procedure(Sender: TObject; Columns: integer; ColumnNames, ColumnValues: string) of object;
  TOnBusy = procedure(Sender: TObject; ObjectName: string; BusyCount: integer; var Cancel: boolean) of object;
  TOnQueryComplete = procedure(Sender: TObject) of object;
  TASQLiteNotifyEvent = procedure(Sender: TObject) of object;

  // structure for holding field information. It is used by GetTableInfo

  TASQLiteField = class
  public
    FieldNumber: integer;
    FieldName: string;
    FieldType: string;
    FieldNN: integer; // 1 if notnull
    FieldDefault: string;
    FieldPK: integer; // 1 if primary key
  end;

  // object to 'play' with SQLite's default settings

  TASQLitePragma = class(TComponent)
  Private
    FTempCacheSize: integer;
    FDefaultCacheSize: integer;
    FDefaultSynchronous: string;
    FDefaultTempStore: string;
    FTempStore: string;
    FSynchronous: string;
  Protected
    function GetTempCacheSize: string;
    function GetDefaultCacheSize: string;
    function GetDefaultSynchronous: string;
    function GetDefaultTempStore: string;
    function GetTempStore: string;
    function GetSynchronous: string;
  Published
    { Published declarations }
    property TempCacheSize: integer Read FTempCacheSize Write FTempCacheSize;
    property DefaultCacheSize: integer Read FDefaultCacheSize Write FDefaultCacheSize;
    property DefaultSynchronous: string Read FDefaultSynchronous
      Write FDefaultSynchronous;
    property DefaultTempStore: string Read FDefaultTempStore Write FDefaultTempStore;
    property TempStore: string Read FTempStore Write FTempStore;
    property Synchronous: string Read FSynchronous Write FSynchronous;
  end;

  // component to log messages
  // it's for debugging purpose and may be obsolete due
  // to the event implementation. not sure yet...

  TASQLiteLog = class(TComponent)
  Private
    FLogFile: string;
    FLogDebugOut: boolean;
    FAppend: boolean;
    FLogSQL: boolean;
    FLogInt: boolean;
  Protected
  Public
    procedure Display(Msg: string);
  Published
    { Published declarations }
    property LogFile: string Read FLogFile Write FLogFile;
    property LogDebugOut: boolean Read FLogDebugOut Write FLogDebugOut;
        // 20040225
    property Append: boolean Read FAppend Write FAppend;
    property LogSQL: boolean Read FLogSQL Write FLogSQL;
    property LogInternals: boolean Read FLogInt Write FLogInt;
  end;

// This component can be used to store sql outside the pascal source.
// It is useful for automatically creating tables on open of a temporary database
// (i.e. in-memory database)

  TASQLiteInlineSQL = class(TComponent)
  private
    FSQL: TStrings;
    procedure SetSQL(const Value: TStrings);
    function GetSQL: TStrings;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  Published
    property SQL: TStrings Read GetSQL Write SetSQL;
  end;

  { Basic Database component }

  TASQLiteDB = class(TComponent)
  Private
    { Private declarations }
    FAfterConnect: TASQLiteNotifyEvent;
    FBeforeConnect: TASQLiteNotifyEvent;
    FAfterDisconnect: TASQLiteNotifyEvent;
    FBeforeDisconnect: TASQLiteNotifyEvent;
  Protected
    { Protected declarations }
    FInlineSQL : TASQLiteInlineSQL;
    FExecuteInlineSQL : boolean;
    FDatabase: string;
    FDefaultExt: string;
    FDefaultDir: string;
    FDriverDll: string;
    FConnected: boolean;
    FMustExist: boolean;
    FVersion: string;
    FCharEncoding: string;
    DBHandle: Pointer;
    FASQLitePragma: TASQLitePragma;
    FASQLiteLog: TASQLiteLog;
    FLastError: string;
    SQLite_Open: function(dbname: pchar; mode: integer; var ErrMsg: pchar): Pointer; cdecl;
    SQLite_Close: procedure(db: Pointer); cdecl;
    SQLite_Exec: function(db: Pointer; SQLStatement: pchar; CallbackPtr: Pointer;
      Sender: TObject; var ErrMsg: pchar): integer; cdecl;
    SQLite_Version: function(): pchar; cdecl;
    SQLite_Encoding: function(): pchar; cdecl;
    SQLite_ErrorString: function(ErrNo: integer): pchar; cdecl;
    SQLite_GetTable: function(db: Pointer; SQLStatement: pchar; var ResultPtr: Pointer;
      var RowCount: cardinal; var ColCount: cardinal; var ErrMsg: pchar): integer; cdecl;
    SQLite_FreeTable: procedure(Table: pchar); cdecl;
    SQLite_FreeMem: procedure(P: pchar); cdecl;
    SQLite_Complete: function(P: pchar): boolean; cdecl;
    SQLite_LastInsertRow: function(db: Pointer): integer; cdecl;
    SQLite_Cancel: procedure(db: Pointer); cdecl;
    SQLite_BusyHandler: procedure(db: Pointer; CallbackPtr: Pointer; Sender: TObject); cdecl;
    SQLite_BusyTimeout: procedure(db: Pointer; TimeOut: integer); cdecl;
    SQLite_Changes: function(db: Pointer): integer; cdecl;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure DBConnect(Connected: boolean);
    function FGetDefaultExt: string;
  Public
    DLLHandle: THandle;
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function LoadLibs: boolean;
    procedure FSetDatabase(Database: string);
    function FGetDriverDLL: string;
    function RowsAffected: integer;
    procedure StartTransaction;
    procedure Open;
    procedure Close;
    procedure Commit;
    procedure RollBack;
    procedure ShowDatabases(List: TStrings);
    procedure GetTableNames(List: TStrings; SystemTables: boolean = false);
    procedure GetTableInfo(TableName: string; List: TList);
    procedure GetIndexNames(List: TStrings; SystemTables: boolean = false);
    procedure GetFieldNames(TableName: string; List: TStrings);
    procedure GetPrimaryKeys(TableName: string; List: TStrings);
    procedure GetTableIndexNames(TableName: String; List: TStrings);
    procedure ExecPragma;
    function SQLite_XExec(db: Pointer; SQLStatement: pchar;
      CallbackPtr: Pointer; Sender: TObject; var ErrMsg: pchar): integer; cdecl;
  Published
    { Published declarations }
    property Database: string Read FDatabase Write FSetDatabase;
    property ASQLitePragma: TASQLitePragma Read FASQLitePragma Write FASQLitePragma;
    property ASQLiteLog: TASQLiteLog Read FASQLiteLog Write FASQLiteLog;
    property DefaultExt: string Read FGetDefaultExt Write FDefaultExt;
    property DefaultDir: string Read FDefaultDir Write FDefaultDir;
    property Version: string Read FVersion write FVersion;
    property CharacterEncoding: string Read FCharEncoding Write FCharEncoding;
    property DriverDLL: string Read FGetDriverDLL Write FDriverDLL;
    property Connected: boolean Read FConnected Write DBConnect;
    property MustExist: boolean Read FMustExist Write FMustExist;
    property ASQLiteInlineSQL: TASQLiteInlineSQL read FInlineSQL write FInlineSQL;
    property ExecuteInlineSQL : boolean Read FExecuteInlineSQL write FExecuteInlineSQL;
    property AfterConnect: TASQLiteNotifyEvent Read FAfterConnect Write FAfterConnect;
    property BeforeConnect: TASQLiteNotifyEvent Read FBeforeConnect Write FBeforeConnect;
    property AfterDisconnect: TASQLiteNotifyEvent
      Read FAfterDisconnect Write FAfterDisconnect;
    property BeforeDisconnect: TASQLiteNotifyEvent
      Read FBeforeDisconnect Write FBeforeDisconnect;
  end;

  AsgError = class(Exception);

{ TRecInfo }

{   This structure is used to access additional information stored in
  each record buffer which follows the actual record data.

    Buffer: PChar;
   ||
   \/
    --------------------------------------------
    |  Record Data  | Bookmark | Bookmark Flag |
    --------------------------------------------
                    ^-- PRecInfo = Buffer + FRecInfoOfs

  Keep in mind that this is just an example of how the record buffer
  can be used to store additional information besides the actual record
  data.  There is no requirement that TDataSet implementations do it this
  way.

  For the purposes of this demo, the bookmark format used is just an integer
  value.  For an actual implementation the bookmark would most likely be
  a native bookmark type (as with BDE), or a fabricated bookmark for
  data providers which do not natively support bookmarks (this might be
  a variant array of key values for instance).

  The BookmarkFlag is used to determine if the record buffer contains a
  valid bookmark and has special values for when the dataset is positioned
  on the "cracks" at BOF and EOF. }

  PRecInfo = ^TRecInfo;

  TRecInfo = packed record
    Bookmark: integer;
    BookmarkFlag: TBookmarkFlag;
  end;

 //============================================================================== TFResult
 // The TFResult class is used to maintain the resultlist in memory. This
 // will only be the case for 'normal' data. Blobs and Clobs will be treated
 // differently, but they are not supported yet.
 //==============================================================================
  TASQLiteBaseQuery = class;

  TFResult = class
  Protected
    Data: TList;
    BookMark: TList;
    RowId: TList;
    FLastBookmark: integer;
    FBufSize: integer;
    FDataSet : TASQLiteBaseQuery;
  Public
    constructor Create(TheDataSet : TASQLiteBaseQuery);
    destructor Destroy; override;
    procedure FreeBlobs;
    procedure SetBufSize(TheSize: integer);
    procedure Add(TheBuffer: pchar; TheRowId: integer);
    procedure Insert(Index: integer; TheBuffer: Pointer; TheRowId: integer);
    procedure Delete(Index: integer);
    function GetData(Index: integer): Pointer;
    function Count: integer;
    function IndexOf(TheBookMark: pointer): integer;
    function GetBookmark(Index: integer): integer;
    function GetRowId(Index: integer): integer;
  end;

//============================================================================== TASQLiteUpdateSQL
  TASQLiteUpdateSQL = class(TComponent)
  Private
    FInsertSQL: TStrings;
    FUpdateSQL: TStrings;
    FDeleteSQL: TStrings;
    procedure SetInsertSQL(const Value: TStrings);
    procedure SetUpdateSQL(const Value: TStrings);
    procedure SetDeleteSQL(const Value: TStrings);
  Public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  Published
    property InsertSQL: TStrings Read FInsertSQL Write SetInsertSQL;
    property UpdateSQL: TStrings Read FUpdateSQL Write SetUpdateSQL;
    property DeleteSQL: TStrings Read FDeleteSQL Write SetDeleteSQL;
  end;

//============================================================================== TASQLiteOutput

  TASQLiteOutput = class(TComponent)
  private
    FActive : boolean;
    FOutputType : string;
    FTableClass : string;
    FHeaderClass : string;
    FCellClass : string;
    FOutput : TStrings;
    FSeparator : string;
    FDataSource : TDataSource;
    procedure SetOutput(const Value: TStrings);
    procedure SetFActive(Active : boolean);
    function GetOutput : TStrings;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Execute(MyDataSet: TDataSet);
  Protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  Published
    property Active : boolean read FActive write SetFActive;
    property DataSource : TDataSource read FDataSource write FDataSource;
    property OutputType : string read FOutputType write FOutputType;
    property TableClass : string read FTableClass write FTableClass;
    property HeaderClass : string read FHeaderClass write FHeaderClass;
    property CellClass : string read FCellClass write FCellClass;
    property Output: TStrings Read GetOutput Write SetOutput;
    property FieldSeparator : string  read FSeparator write FSeparator;
  end;

//============================================================================== TASQLiteBaseQuery
  TASQLiteBaseQuery = class(TDataSet)
  Private
    FTypeLess: boolean;
    FNoResults: boolean; // suppresses the creation of a result list
    FAutoCommit: boolean;
    FTableDateFormat : string;
    FSQLiteDateFormat : boolean;
    FResult: TFResult;
    FSQL: TStrings;
    FPrepared: string;
    FRecBufSize: integer;
    FRecInfoOfs: integer;
    FCurRec: integer;
    FMasterFields: string;
    FMasterSource: TDataSource;
    FLastError: string;
    FSaveChanges: boolean;
    FMetaData: boolean;
    MaxStrLen: integer;
    FConnection: TASQLiteDB;
    FMaxResults: integer;
    FStartResult: integer;
    CurrentRowId: integer;
    SQLStr: string;
    ResultStr: pchar;
    DetailList: TList;
    procedure SetSQL(const Value: TStrings);
    function UnpackBuffer(Buffer: pchar; FieldType: TFieldType) : TConvertBuffer;
    procedure SetDataSource(Value: TDataSource);
  Protected
//    procedure Notification(AComponent: TComponent; Operation: TOperation); override;

    procedure RegisterDetailDataset(DetailDataSet: TASQLiteBaseQuery);
    procedure LoadQueryData;
    function GetActiveBuffer(var Buffer: pchar): boolean;
    function GetDataSource: TDataSource; override;
    procedure NotifySQLiteMasterChanged;

    { Overriden abstract methods (required) }
    function AllocRecordBuffer: pchar; override;
    procedure FreeRecordBuffer(var Buffer: pchar); override;
    procedure GetBookmarkData(Buffer: pchar; Data: Pointer); override;
    function GetBookmarkFlag(Buffer: pchar): TBookmarkFlag; override;
    function GetRecord(Buffer: pchar; GetMode: TGetMode;
      DoCheck: boolean): TGetResult; override;
    function GetRecordSize: word; override;
    procedure InternalAddRecord(Buffer: Pointer; Append: boolean); override;
    procedure InternalClose; override;
    procedure InternalDelete; override;
    procedure InternalFirst; override;
    procedure InternalGotoBookmark(Bookmark: Pointer); override;
    procedure InternalHandleException; override;
    procedure InternalInitFieldDefs; override;
    procedure InternalInitRecord(Buffer: pchar); override;
    procedure InternalLast; override;
    procedure InternalOpen; override;
    procedure InternalPost; override;
    procedure InternalSetToRecord(Buffer: pchar); override;
    function IsCursorOpen: boolean; override;
    procedure SetBookmarkFlag(Buffer: pchar; Value: TBookmarkFlag); override;
    procedure SetBookmarkData(Buffer: pchar; Data: Pointer); override;
    procedure SetFieldData(Field: TField; Buffer: Pointer); override;
  Protected
    function GetFieldSize(FieldNo: integer): integer;
    function GetNativeFieldSize(FieldNo: integer): integer;
    function GetFieldOffset(FieldNo: integer): integer;
    function GetMasterFields: string;
    procedure SetMasterFields(const Value: string);
    { Additional overrides (optional) }
    function GetRecordCount: integer; override;
    function GetRecNo: integer; override;
    procedure SetRecNo(Value: integer); override;
    property BaseSQL: TStrings Read FSQL Write SetSQL;
    procedure SetFilterText(const Value: string); override;
  Public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure StartTransaction;
    procedure Commit;
    procedure RollBack;
    procedure SetFiltered(Value: Boolean); override;
    procedure SQLiteMasterChanged; virtual;
    function GetFieldData(Field: TField; Buffer: Pointer): boolean; override;
    function GetFieldData(FieldNo: integer; Buffer: Pointer): boolean; override;                     // 20040225
    function GetLastInsertRow : integer;
{$IFDEF ASQLITE_D6PLUS}
//    function GetFieldData(Field: TField; Buffer: Pointer; NativeFormat: boolean): boolean; override;
{$ENDIF}
    function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
    function Locate(const KeyFields: string; const KeyValues: variant;
      Options: TLocateOptions): boolean; override;
    function  BookmarkValid(Bookmark: Pointer) : boolean; override;
//    function    LocateNearest(const KeyFields: String; const KeyValues: Variant; Options: TLocateOptions): Boolean;
  Published
    property AutoCommit: boolean Read FAutoCommit Write FAutoCommit Default true;
    property SQLiteDateFormat: boolean Read FSQLiteDateFormat Write FSQLiteDateFormat Default true;
    property TableDateFormat: string Read FTableDateFormat Write FTableDateFormat;
    property Connection: TASQLiteDB Read FConnection Write FConnection;
    property MaxResults: integer Read FMaxResults Write FMaxResults;
    property StartResult: integer Read FStartResult Write FStartResult;
    property TypeLess: boolean Read FTypeLess Write FTypeLess Default false;
    property MasterFields: string Read GetMasterFields Write SetMasterFields;
    property MasterSource: TDataSource Read GetDataSource Write SetDataSource;
    property AutoCalcFields;
    property Filter;
    property Filtered;
    property Active;
    property BeforeOpen;
    property AfterOpen;
    property BeforeClose;
    property AfterClose;
    property BeforeInsert;
    property AfterInsert;
    property BeforeEdit;
    property AfterEdit;
    property BeforePost;
    property AfterPost;
    property BeforeCancel;
    property AfterCancel;
    property BeforeDelete;
    property AfterDelete;
    property BeforeScroll;
    property AfterScroll;
{$IFDEF ASQLITE_D6PLUS}
    property BeforeRefresh;
    property AfterRefresh;
{$ENDIF}
    property OnCalcFields;
    property OnDeleteError;
    property OnEditError;
    property OnNewRecord;
    property OnPostError;
  end;
//============================================================================== TASQLiteQuery

  TASQLiteQuery = class(TASQLiteBaseQuery)
  Private
    FParams: TParams;
    FUpdateSQL: TASQLiteUpdateSQL;
    FRawSQL : boolean;
    procedure SetSQL(const Value: TStrings);
    function GetSQL: TStrings;
    procedure SetParamsList(Value: TParams);
    procedure QueryChanged(Sender: TObject);
  Protected
    procedure InternalOpen; override;
    procedure InternalPost; override;
    procedure InternalDelete; override;
    function GetParamsCount: word;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure InternalClose; override;
  Public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure ExecSQL;
    property Params: TParams Read FParams Write SetParamsList Stored false;
    procedure SQLiteMasterChanged; override;
  Published
    property RawSQL: boolean Read FRawSQL Write FRawSQL Default false;
    property SQL: TStrings Read GetSQL Write SetSQL;
    property UpdateSQL: TASQLiteUpdateSQL Read FUpdateSQL Write FUpdateSQL;
  end;

//============================================================================== TASQLiteTable

  TASQLiteTable = class(TASQLiteBaseQuery)
  Private
    FTableName: string;
    FPrimaryAutoInc: boolean;
  Protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure InternalOpen; override;
    procedure InternalPost; override;
    procedure InternalDelete; override;
  Public
    procedure SQLiteMasterChanged; override;
  Published
    property TableName: string Read FTableName Write FTableName;
    property PrimaryAutoInc: boolean Read FPrimaryAutoInc Write FPrimaryAutoInc;
  end;

 //============================================================================== TASQLiteBlobStream

  TASQLiteBlobStream = class(TMemoryStream)
  private
    FField: TBlobField;
    FDataSet: TASQLiteBaseQuery;
    FMode: TBlobStreamMode;
    FModified: Boolean;
    FOpened: Boolean;
    procedure LoadBlobData;
    procedure SaveBlobData;
  public
    constructor Create(Field: TBlobField; Mode: TBlobStreamMode);
    destructor Destroy; override;
    function Read(var Buffer; Count: Longint): Longint; override;
    function Write(const Buffer; Count: Longint): Longint; override;
  end;


implementation

uses Forms,
     Dialogs;

{$IFDEF DEBUG_ENABLED}
Var
  DebugSpaces: Integer = 0;
{$ENDIF}

procedure Debug(const S: string);
begin
{$IFDEF DEBUG_ENABLED}
  OutputDebugString(PChar(StringOfChar(' ', DebugSpaces) + S));
{$ENDIF}
end;

procedure DebugEnter(const S: string);
begin
{$IFDEF DEBUG_ENABLED}
  OutputDebugString(PChar(StringOfChar(' ', DebugSpaces) + 'Enter ' + S));
  inc(DebugSpaces);
{$ENDIF}
end;

procedure DebugLeave(const S: string);
begin
{$IFDEF DEBUG_ENABLED}
  dec(DebugSpaces);
  OutputDebugString(PChar(StringOfChar(' ', DebugSpaces) + 'Leave ' + S));
{$ENDIF}
end;

//==============================================================================
// SyntaxCheck. This routine is used to check if words match the sql syntax
//              It is called where sql statements are parsed and generated
//==============================================================================
function SyntaxCheck(LWord, RWord: string): boolean;
begin
  DebugEnter('SyntaxCheck');
  try
   if CompareText(LWord, RWord) <> 0 then begin
     SyntaxCheck := false;
     raise AsgError.Create('SQL macro syntax error on sql, expected ' + RWord)
   end else
    SyntaxCheck := true;
  finally
   DebugLeave('SyntaxCheck');
  end;
end;

//==============================================================================
// Parse the SQL fielddescription and return the Delphi Field types, length etc.
//==============================================================================
procedure GetFieldInfo(FieldInfo: string; var FieldType: TFieldType;
  var FieldLen, FieldDec: integer);
var
  p1, p2, pn : integer;
  vt:     string;
begin
  DebugEnter('GetFieldInfo');
  FieldType := ftString; // just a default;
  FieldLen  := 255;
  FieldDec  := 0;

  p1 := pos('(', FieldInfo);
  if p1 <> 0 then
  begin
    p2 := pos(')', FieldInfo);
    if p2 <> 0 then
    begin
      vt := LowerCase(Copy(FieldInfo, 1, p1 - 1));
      if (vt = 'varchar') or (vt = 'char') or (vt = 'varchar2') then begin
         FieldType := ftString;
         FieldLen := StrToInt(Copy(FieldInfo, p1 + 1, p2 - p1 - 1));
      end else if (vt='numeric') then begin
         vt := Copy(FieldInfo, p1 + 1, p2 - p1 - 1);
         pn := pos('.',vt); if pn=0 then pn := pos(',',vt);
         FieldType := ftFloat;
         if pn = 0 then begin
            FieldLen := StrToInt(vt);
            FieldDec := 0;
         end else begin
            FieldLen := StrToInt(Copy(vt, 1, pn - 1));
            FieldDec := StrToInt(Copy(vt, pn+1, 2));
         end;
      end;
    end
    else
      FieldLen := 256;
  end
  else
  begin
    vt := LowerCase(FieldInfo);
    if vt = 'date' then
    begin
      FieldType := ftDate;
      FieldLen  := 10;
    end
    else if vt = 'datetime' then
    begin
      FieldType := ftDateTime;                  // fpierce original ftDate
      FieldLen  := 24;                          // aducom
    end
    else if vt = 'time' then
    begin
      FieldType := ftTime;
      FieldLen  := 12;
    end
{$IFDEF ASQLITE_D6PLUS}
    else if vt = 'timestamp' then
    begin
      FieldType := ftTimeStamp;
      FieldLen  := 12;
    end
{$ENDIF}
    else if vt = 'integer' then
    begin
      FieldType := ftInteger;
      FieldLen  := 12;
    end
    else if (vt = 'float') or (vt = 'real') then
    begin
      FieldType := ftFloat;
      FieldLen  := 12;
    end
    else if (vt = 'boolean') or (vt = 'logical') then
    begin
      FieldType := ftBoolean;
      FieldLen  := 2;
    end
    else if (vt = 'text') or (vt='string') then
    begin
      FieldType := ftString;
      FieldLen  := 255;
    end
    else if (vt = 'currency') or (vt = 'financial') or (vt = 'money') then      // devstate
    begin                                                                       // devstate
      FieldType := ftCurrency;                                                  // devstate
      FieldLen := 10;                                                           // devstate
    end                                                                         // devstate
    else if (vt = 'blob') then
    begin
      FieldType := ftBlob;
      FieldLen  := SizeOf(Pointer);
    end
    else if (vt = 'graphic') then
    begin
      FieldType := ftGraphic;
      FieldLen  := SizeOf(Pointer);
    end
    else if (vt = 'clob') or (vt = 'memo') or (vt = 'longtext') then
    begin
      FieldType := ftMemo;
      FieldLen  := SizeOf(Pointer);
    end;
  end;
  DebugLeave('GetFieldInfo: ' + vt);
end;

 //==============================================================================
 // This callback routine is used to retrieve each selected record from the
 // database and put it into an internal structure. It is also responsible to
 // set FieldDef and bookmark info.
 //==============================================================================

function ExecCallback(Sender: TObject; Columns: integer; ColumnValues: Pointer;
  ColumnNames: Pointer): integer; cdecl;
var
  PVal, PType, PName: ^pchar;
  bValidRecord: boolean;
  RowId: integer;
  FieldType: TFieldType;
  FieldLen: integer;
  FieldDec: integer;
  bIgnore: boolean;
  bHasRowId: boolean;
  i:  integer;
  mv: integer;
  convertbuf : TConvertBuffer;
  BlobStream : TMemoryStream;
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('ExecCallback');
{$ENDIF}
  ExecCallBack := 0;
  if not (Sender is TASQLiteBaseQuery) then exit;

  RowId := -1;

  if (Sender as TASQLiteBaseQuery).FNoResults = false then
  begin
    bValidRecord := false;
    bHasRowId    := false;

    with Sender as TASQLiteBaseQuery do
    begin
      FillChar(ResultStr^, MaxBuf, 0);
      if ( not FMetaData) then
      begin
        FieldDefs.Clear;
        MaxStrLen := 0;
      end;

      PName := ColumnNames; // setup fielddefs
      PVal  := ColumnValues;
      PType := ColumnNames;

      Inc(PType, Columns);
      for i := 0 to Columns - 1 do
      begin
        bIgnore := CompareText('RowId', PName^) = 0;
        if bIgnore then
          bHasRowId := true;
      // obtain metadata on the first row
        if ( not FMetaData) and ( not bIgnore) and (i < Columns) then
        begin
          GetFieldInfo(PType^, FieldType, FieldLen, FieldDec);
          if FTypeLess then
          begin // typeless means 'all strings'
            FieldType := ftString;
            with FieldDefs.AddFieldDef do
            begin
              Name     := PName^;
              DataType := FieldType;
              Size     := FieldLen;
            end;
          end else begin
            with FieldDefs.AddFieldDef do
            begin
              if FieldType <> ftString then
              begin
                Name      := PName^;
                DataType  := FieldType;
                if FieldType = ftFloat then begin
                  Precision := FieldDec;
                end
              end
              else
              begin
                Name     := PName^;
                DataType := FieldType;
                Size     := FieldLen;
              end;
            end;
          end;
          MaxStrLen := MaxStrLen + GetNativeFieldSize(i + 1);
          FResult.SetBufSize(MaxStrLen + 1 + Sizeof(TBookmark));
        end;

        if PVal = nil then
        begin
          Inc(PName);
          Inc(PType);
          continue;
        end;

        if ( not bIgnore) then
        begin
          bValidRecord := true;
          if PVal^ = nil then
          begin
          end
          else
          begin
            if not FTypeLess then
            begin
              if FieldDefs[i].DataType = ftString then begin
                mv := GetNativeFieldSize(i + 1);
                if StrLen(PVal^) < mv then
                   mv := StrLen(PVal^);
                move(PVal^^, (ResultStr + GetFieldOffset(i + 1))^, mv);
              end else begin
                if FieldDefs[i].DataType in [ftMemo, ftGraphic, ftFMTMemo, ftBlob] then begin
                   // create memory stream to save blob;
                   BlobStream := TMemoryStream.Create;
                   BlobStream.Write(PVal^^, lstrlen(PVal^));
                   move(BlobStream, (ResultStr + GetFieldOffset(i + 1))^, SizeOf(BlobStream));
                end else begin
                  ConvertBuf := UnpackBuffer(PVal^, FieldDefs[i].DataType);
                  move(ConvertBuf, (ResultStr + GetFieldOffset(i + 1))^, GetFieldSize(i + 1));
                end;
              end;
            end
            else
            begin
              mv := GetNativeFieldSize(i + 1);
              if StrLen(PVal^) < mv then
                mv := StrLen(PVal^);
              move(PVal^^, (ResultStr + GetFieldOffset(i + 1))^, mv);
            end;
          end;
        end
        else
          RowId := StrToInt(PVal^);

        Inc(PName);
        Inc(PVal);
        Inc(PType);
      end;
      FMetaData := true;
      if not bHasRowId then
        RowId := -1;
      if bValidRecord then
        FResult.Add(ResultStr, RowId);
    end;
  end;
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('ExecCallback');
{$ENDIF}

end;

 //==============================================================================
 // Convert TDateTime to TDateTimeRec
 //==============================================================================

function DateTimeToNative(DataType: TFieldType; Data: TDateTime): TDateTimeRec;
var
  TimeStamp: TTimeStamp;
begin
  DebugEnter('DateTimeToNative');
  TimeStamp := DateTimeToTimeStamp(Data);
  case DataType of
    ftDate: Result.Date := TimeStamp.Date;
    ftTime: Result.Time := TimeStamp.Time;
    else
      Result.DateTime := TimeStampToMSecs(TimeStamp);
  end;
  DebugLeave('DateTimeToNative');
end;

//============================================================================== TASQLITELOG

procedure TASQLiteLog.Display(Msg: string);
var
  fn: Textfile;
begin
  DebugEnter('TASQLiteLog.Display');
  if FileExists(FLogFile) then
  begin
    if FAppend then
    begin
      AssignFile(fn, FLogFile);
      System.Append(fn);
    end
    else
    begin
      SysUtils.DeleteFile(FLogFile);
      AssignFile(fn, FLogFile);
      Rewrite(fn);
    end;
  end
  else
  begin
    AssignFile(fn, FLogFile);
    Rewrite(fn);
  end;
  Writeln(fn, FormatDateTime('yyyy mmm dd (hh:nn:ss) ', now) + Msg);
  CloseFile(fn);
  DebugLeave('TASQLiteLog.Display');
end;

//============================================================================== TASQLITEPRAGMA

function TASQLitePragma.GetTempCacheSize: string;
begin
  DebugEnter('TASQLitePragma.GetTempCacheSize');
  GetTempCacheSize := 'pragma cache_size=' + IntToStr(FTempCacheSize);
  DebugLeave('TASQLitePragma.GetTempCacheSize');
end;

function TASQLitePragma.GetDefaultCacheSize: string;
begin
  DebugEnter('TASQLitePragma.GetDefaultCacheSize');
  GetDefaultCacheSize := 'pragma default_cache_size=' + IntToStr(FDefaultCacheSize);
  DebugLeave('TASQLitePragma.GetDefaultCacheSize');
end;

function TASQLitePragma.GetDefaultSynchronous: string;
begin
  DebugEnter('TASQLitePragma.GetDefaultSynchronous');
  GetDefaultSynchronous := 'pragma default_synchronous=' + FDefaultSynchronous;
  DebugLeave('TASQLitePragma.GetDefaultSynchronous');
end;

function TASQLitePragma.GetDefaultTempStore: string;
begin
  DebugEnter('TASQLitePragma.GetDefaultTempStore');
  GetDefaultTempStore := 'pragma default_temp_store=' + FDefaultTempStore;
  DebugLeave('TASQLitePragma.GetDefaultTempStore');
end;

function TASQLitePragma.GetTempStore: string;
begin
  DebugEnter('TASQLitePragma.GetTempStore');
  GetTempStore := 'pragma temp_store=' + FTempStore;
  DebugLeave('TASQLitePragma.GetTempStore');
end;

function TASQLitePragma.GetSynchronous: string;
begin
  DebugEnter('TASQLitePragma.GetSynchronous');
  GetSynchronous := 'pragma synchronous=' + FSynchronous;
  DebugLeave('TASQLitePragma.GetSynchronous');
end;

 //============================================================================== TFRESULT
 // TResult is a representation of an internal pointerlist of results.
 // Only 'normal' results will be stored internally within a fixed memory block
 // depending on calculated length internally. This is not the case
 // for blobs and clobs. In this case only the handle is stored in the fixed
 // structure and a separate memory handle is retrieved to store the blob and
 // clob data. This is because the blobs are stored as null terminated 'strings'
 // and thus have different lengths. No more memory is allocated this way than
 // strictly necessary.
 //==============================================================================

constructor TFResult.Create(TheDataSet : TASQLiteBaseQuery);
begin
  DebugEnter('TFResult.Create');
  Data          := TList.Create;
  Bookmark      := TList.Create;
  RowId         := TList.Create;
  FDataSet      := TheDataset;
  FLastBookmark := 0;
  DebugLeave('TFResult.Create');
end;

destructor TFResult.Destroy;
var
  ptr: Pointer;
begin
  DebugEnter('TFResult.Destroy');
  FreeBlobs;
  if Assigned(Data) then begin
    while Data.Count > 0 do begin
      ptr := Data.Items[0];
      if Assigned(ptr) then FreeMem(ptr, FBufSize);
      Data.Delete(0);
    end;
    Data.Free;                  // D4 compatibility, otherwise FreeAndNil could be used
    Data := nil;
  end;

  if Assigned(Bookmark) then begin
     Bookmark.Free;
     Bookmark := nil;
  end;

  if Assigned(RowId) then begin
     RowId.Free;
     RowId := nil;
  end;

  DebugLeave('TFResult.Destroy');
end;

procedure TFResult.FreeBlobs;
var i,j : integer;
    offset : integer;
    ptr : pchar;
    stream : TMemoryStream;
begin
 for j := 0 to Data.Count - 1 do begin
   ptr := GetData(j);
   for i := 0 to FDataSet.FieldList.Count - 1 do begin
     if FDataSet.FieldList[i].DataType in [ftMemo, ftFmtMemo, ftGraphic, ftBlob] then begin
       Offset := FDataset.GetFieldOffset(FDataSet.FieldList[i].FieldNo);
       Move((ptr + Offset)^, Pointer(Stream), sizeof(Pointer));
       Stream.Free; 
     end;
   end;
 end;
end;

procedure TFResult.SetBufSize(TheSize: integer);
begin
  DebugEnter('TFResult.SetBufSize');
  FBufSize := TheSize;
  DebugLeave('TFResult.SetBufSize');
end;

//==============================================================================
// Adds a row of data to the resultset.
//==============================================================================
procedure TFResult.Add(TheBuffer: pchar; TheRowId: integer);
var
  ptr: pchar;
//  i:   integer;
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('TFResult.Add');
{$ENDIF}
  Inc(FLastBookmark);
  GetMem(Ptr, FBufSize);
  move(TheBuffer^, ptr^, FBufSize);
  Data.Add(Ptr);
//  for i := 0 to FBufSize - 1 do   // implement this if you have trouble
//  begin                           // retrieving data stored with older versions
//    if ptr^ = '`' then            // of these components
//      ptr^ := '''';
//    Inc(ptr);
//  end;
  Bookmark.Add(Pointer(FLastBookMark));
  if TheRowId >= 0 then
    RowId.Add(Pointer(TheRowId))
  else
    RowId.Add(Pointer(RowId.Count));
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TFResult.Add');
{$ENDIF}
end;

//==============================================================================
// Inserts a row of date into the resultset
//==============================================================================
procedure TFResult.Insert(Index: integer; TheBuffer: pointer; TheRowId: integer);
var
  ptr: Pointer;
begin
  DebugEnter('TFResult.Insert');
  Inc(FLastBookmark);
  GetMem(Ptr, FBufSize);
  move(TheBuffer^, ptr^, FBufSize);
  Data.Insert(Index, Ptr);
  Bookmark.Insert(Index, Pointer(FLastBookMark));
  RowId.Insert(Index, Pointer(TheRowId));
  DebugLeave('TFResult.Insert');
end;

//==============================================================================
// Deletes a row of data from the resultset
//==============================================================================
procedure TFResult.Delete(Index: integer);
var
  ptr: pointer;
begin
  DebugEnter('TFResult.Delete');
  if not ((Index < 0) or (Index >= Data.Count)) then
  begin
    ptr := Data.Items[Index];
    if ptr <> nil then
      FreeMem(ptr, FBufSize);
    Data.Delete(Index);
    Bookmark.Delete(Index);
    Rowid.Delete(Index);
  end;
  DebugLeave('TFResult.Delete');
end;

//==============================================================================
// Returns a row from the resultset
//==============================================================================
function TFResult.GetData(Index: integer): Pointer;
begin
  DebugEnter('TFResult.GetData');
  if (Index < 0) or (Index >= Data.Count) then
    GetData := nil
  else
    GetData := Data.Items[Index];
  DebugLeave('TFResult.GetData');
end;

function TFResult.GetBookmark(Index: integer): integer;
begin
  DebugEnter('TFResult.GetBookmark');
  if (Index < 0) or (Index >= Data.Count) then
    GetBookmark := -1
  else
    GetBookmark := integer(Bookmark.Items[Index]);
  DebugLeave('TFResult.GetBookmark');
end;

function TFResult.GetRowId(Index: integer): integer;
begin
  DebugEnter('TFResult.GetRowId');
  if (Index < 0) or (Index >= RowId.Count) then
    GetRowId := -1
  else
    GetRowId := integer(RowId.Items[Index]);
  DebugLeave('TFResult.GetRowId');
end;

function TFResult.Count: integer;
begin
  Count := Data.Count;
end;

function TFResult.IndexOf(TheBookMark: pointer): integer;
begin
  Result := BookMark.IndexOf(TheBookmark);
end;

//============================================================================== ASQLITEDB

procedure TASQLiteDB.Notification(AComponent: TComponent; Operation: TOperation);
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('TASQLiteDB.Notification');
{$ENDIF}
//  Application.ProcessMessages;
  if Assigned(AComponent) then
  begin
    if (Operation = opRemove) then
    begin
      if (AComponent is TASQLitePragma) then begin
        if Assigned(FASQLitePragma) then begin
          if TASQLitePragma(AComponent) = FASQLitePragma  then
             FASQLitePragma := nil;
        end;
      end
      else if (AComponent is TASQLiteLog) then
      begin
        if Assigned(FASQLiteLog) then begin
          if TASQLiteLog(AComponent) = FASQLiteLog then
            FASQLiteLog := nil;
        end;
      end
      else if (AComponent is TASQLiteInlineSQL) then
      begin
        if Assigned(FInlineSQL) then begin
          if TASQLiteInlineSQL(AComponent) = FInlineSQL then
            FInlineSQL := nil;
        end;
      end;
    end;
  end;
  Inherited;
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TASQLiteDB.Notification');
{$ENDIF}
end;

function TASQLiteDB.LoadLibs: boolean;
begin
 try
  DebugEnter('TASQLiteDB.LoadLibs');
  OutputDebugString('loading sqlite lib');
  OutputDebugString(pchar(FDriverDLL));
  Result    := false;
  DLLHandle := LoadLibrary(pchar(FDriverDLL));
  if DLLHandle <> 0 then
  begin
    @SQLite_Open := GetProcAddress(DLLHandle, 'sqlite_open');
    if not Assigned( @SQLite_Open) then exit;
    @SQLite_Close := GetProcAddress(DLLHandle, 'sqlite_close');
    if not Assigned( @SQLite_Close) then exit;
    @SQLite_Exec := GetProcAddress(DLLHandle, 'sqlite_exec');
    if not Assigned( @SQLite_Exec) then exit;
    @SQLite_Version := GetProcAddress(DLLHandle, 'sqlite_libversion');
    if not Assigned( @SQLite_Version) then exit;
    @SQLite_Encoding := GetProcAddress(DLLHandle, 'sqlite_libencoding');
    if not Assigned( @SQLite_Encoding) then exit;
    @SQLite_ErrorString := GetProcAddress(DLLHandle, 'sqlite_error_string');
    if not Assigned( @SQLite_ErrorString) then exit;
    @SQLite_GetTable := GetProcAddress(DLLHandle, 'sqlite_get_table');
    if not Assigned( @SQLite_GetTable) then exit;
    @SQLite_FreeTable := GetProcAddress(DLLHandle, 'sqlite_free_table');
    if not Assigned( @SQLite_FreeTable) then exit;
    @SQLite_FreeMem := GetProcAddress(DLLHandle, 'sqlite_freemem');
    if not Assigned( @SQLite_FreeMem) then exit;
    @SQLite_Complete := GetProcAddress(DLLHandle, 'sqlite_complete');
    if not Assigned( @SQLite_Complete) then exit;
    @SQLite_LastInsertRow := GetProcAddress(DLLHandle, 'sqlite_last_insert_rowid');
    if not Assigned( @SQLite_LastInsertRow) then exit;
    @SQLite_Cancel := GetProcAddress(DLLHandle, 'sqlite_interrupt');
    if not Assigned( @SQLite_Cancel) then exit;
    @SQLite_BusyTimeout := GetProcAddress(DLLHandle, 'sqlite_busy_timeout');
    if not Assigned( @SQLite_BusyTimeout) then exit;
    @SQLite_BusyHandler := GetProcAddress(DLLHandle, 'sqlite_busy_handler');
    if not Assigned( @SQLite_BusyHandler) then exit;
    @SQLite_Changes := GetProcAddress(DLLHandle, 'sqlite_changes');
    if not Assigned( @SQLite_Changes) then exit;
    Result := true;
  end;
 finally
  DebugLeave('TASQLiteDB.LoadLibs');
 end;
end;

function TASQLiteDB.SQLite_XExec(db: Pointer; SQLStatement: pchar;
  CallbackPtr: Pointer; Sender: TObject; var ErrMsg: pchar): integer;
begin
  DebugEnter('TASQLiteDB.SQLite_XExec ' + SQLStatement);
  if Assigned(FASQLiteLog) then
    FASQLiteLog.Display(SQLStatement);
  OutputDebugString(SQLStatement);
  SQLite_XExec := SQLite_Exec(db, SQLStatement, CallbackPtr, Sender, ErrMsg);
  DebugLeave('TASQLiteDB.SQLite_XExec');
end;

function TASQLiteDB.FGetDriverDLL: string;
begin
  DebugEnter('TASQLiteDB.FGetDriverDLL');
  if FDriverDLL = '' then
    FDriverDLL := 'SQLite.dll';
  FGetDriverDLL := FDriverDLL;
  DebugLeave('TASQLiteDB.FGetDriverDLL');
end;

function TASQLiteDB.FGetDefaultExt: string;
begin
  DebugEnter('TASQLiteDB.FGetDefaultExt');
  if FDefaultExt = '' then
    FDefaultExt := '.sdb';
  FGetDefaultExt := FDefaultExt;
  DebugLeave('TASQLiteDB.FGetDefaultExt');
end;

procedure TASQLiteDB.FSetDatabase(Database: string);
begin
  DebugEnter('TASQLiteDB.FSetDatabase ' + Database);
  FDatabase := Trim(Database);
  DebugLeave('TASQLiteDB.FSetDatabase');
end;

procedure TASQLiteDB.ShowDatabases(List: TStrings);
var
  sr: TSearchRec;
begin
  DebugEnter('TASQLiteDB.ShowDatabases');
  if DefaultExt = '' then
    DefaultExt := '.sdb';
  if DefaultExt[1] <> '.' then
    DefaultExt := '.' + DefaultExt;
  if DefaultDir <> '' then
    if DefaultDir[Length(DefaultDir)] <> '\' then
      DefaultDir := DefaultDir + '\';
  if FindFirst(FDefaultDir + '*' + DefaultExt, faAnyFile, sr) = 0 then
  begin
    repeat
      List.Add(sr.Name);
    until FindNext(sr) <> 0;
    SysUtils.FindClose(sr);
  end;
  DebugLeave('TASQLiteDB.ShowDatabases');
end;

procedure TASQLiteDB.GetTableNames(List: TStrings; SystemTables: boolean = false);
var
  ResultPtr: Pointer;
  ResultStr: ^Pointer;
  RowCount: cardinal;
  ColCount: cardinal;
  ErrMsg: pchar;
  i: integer;
begin
  DebugEnter('TASQLiteDB.GetTableNames');
  if not FConnected then
    Connected := true;
  if FConnected then
  begin
    SQLite_GetTable(DBHandle, pchar(
      'SELECT name FROM sqlite_master WHERE type="table" ORDER BY name'),
      ResultPtr, RowCount, ColCount, ErrMsg);

    ResultStr := ResultPtr;
    List.Clear;
    Inc(ResultStr); // ignore header                  
    for i := 1 to RowCount do
    begin
      if (CompareText('name', pchar(ResultStr^)) <> 0) then
        List.Add(pchar(ResultStr^));
      Inc(ResultStr);
    end;
    if Assigned(ResultPtr) then SQLite_FreeTable(ResultPtr);
  end;
  DebugLeave('TASQLiteDB.GetTableNames');
end;

procedure TASQLiteDB.GetIndexNames(List: TStrings; SystemTables: boolean = false);
var
  ResultPtr: Pointer;
  ResultStr: ^Pointer;
  RowCount: cardinal;
  ColCount: cardinal;
  ErrMsg: pchar;
  i: integer;
begin
  DebugEnter('TASQLiteDB.GetIndexNames');
  if not FConnected then
    Connected := true;
  if FConnected then
  begin
    SQLite_GetTable(DBHandle, pchar(
      'SELECT name FROM sqlite_master WHERE type="index" ORDER BY name'),
      ResultPtr, RowCount, ColCount, ErrMsg);

    ResultStr := ResultPtr;
    List.Clear;
    Inc(ResultStr); // ignore header
    for i := 1 to RowCount do
    begin
      List.Add(pchar(ResultStr^));
      Inc(ResultStr);
    end;
    if Assigned(ResultPtr) then SQLite_FreeTable(ResultPtr);
  end;
  DebugLeave('TASQLiteDB.GetIndexNames');
end;

procedure TASQLiteDB.GetFieldNames(TableName: string; List: TStrings);
var
  ResultPtr: Pointer;
  ResultStr: ^Pointer;
  RowCount: cardinal;
  ColCount: cardinal;
  ErrMsg: pchar;
  i: integer;
begin
  DebugEnter('TASQLiteDB.GetFieldNames ' + Tablename);
  if not FConnected then
    Connected := true;
  if FConnected then
  begin
    SQLite_GetTable(DBHandle, pchar('PRAGMA table_info("' + TableName + '");'),
      ResultPtr, RowCount, ColCount, ErrMsg);
    ResultStr := ResultPtr;
    List.Clear;
    Inc(ResultStr, 6); // headers can be ignored
    for i := 1 to RowCount do
    begin
      Inc(ResultStr);
      List.Add(pchar(ResultStr^));    // the second field contains the fieldname
      Inc(ResultStr, 5);
    end;
    if Assigned(ResultPtr) then SQLite_FreeTable(ResultPtr);
  end;
  DebugLeave('TASQLiteDB.GetFieldNames');
end;

procedure TASQLiteDB.GetPrimaryKeys(TableName: string; List: TStrings);
var
  ResultPtr: Pointer;
  ResultStr: ^Pointer;
//  PK:     ^Pointer;
  RowCount: cardinal;
  ColCount: cardinal;
  ErrMsg: pchar;
  Temp:   string;
  i:      integer;
begin
  DebugEnter('TASQLiteDB.GetPrimaryKeys ' + Tablename);
  if not FConnected then
    Connected := true;
  if FConnected then
  begin
    SQLite_GetTable(DBHandle, pchar('PRAGMA table_info("' + TableName + '");'),
      ResultPtr, RowCount, ColCount, ErrMsg);
    ResultStr := ResultPtr;
    List.Clear;
    Inc(ResultStr, 6); // headers can be ignored
    for i := 1 to RowCount do
    begin
      Inc(ResultStr);
      Temp := pchar(ResultStr^);    // the second field contains the fieldname
      Inc(ResultStr, 4);
            // the last field reveils a indicator for primary key
      if pchar(ResultStr^) = '1' then
        List.Add(Temp);
      Inc(ResultStr);
    end;
    if Assigned(ResultPtr) then SQLite_FreeTable(ResultPtr);
  end;
  DebugLeave('TASQLiteDB.GetPrimaryKeys');
end;

procedure TASQLiteDB.GetTableInfo(TableName: string; List: TList);
var
  ResultPtr: Pointer;
  ResultStr: ^Pointer;
  RowCount: cardinal;
  ColCount: cardinal;
  ErrMsg: pchar;
  Field: TASQLiteField;
  i: integer;
begin
  DebugEnter('TASQLiteDB.GetTableInfo ' + Tablename);
  if not FConnected then
    Connected := true;
  if FConnected then
  begin
    SQLite_GetTable(DBHandle, pchar('PRAGMA table_info("' + TableName + '");'),
      ResultPtr, RowCount, ColCount, ErrMsg);
    ResultStr := ResultPtr;
    while List.Count > 0 do
    begin
      TASQLiteField(List[0]).Free;
      List.Delete(0);
    end;
    List.Clear;

    for i := 1 to RowCount do
    begin
      Field := TASQLiteField.Create;
      with Field do
      begin
        FieldNumber := StrToIntX(pchar(ResultStr^));
        Inc(ResultStr);
        FieldName := pchar(ResultStr^);
        Inc(ResultStr);
        FieldType := pchar(ResultStr^);
        Inc(ResultStr);
        FieldNN := StrToIntX(pchar(ResultStr^));
        Inc(ResultStr);
        FieldDefault := pchar(ResultStr^);
        Inc(ResultStr);
        FieldPK := StrToIntX(pchar(ResultStr^));
        Inc(ResultStr);
      end;
      List.Add(Field);
    end;
    if Assigned(ResultPtr) then SQLite_FreeTable(ResultPtr);
  end;
  DebugLeave('TASQLiteDB.GetTableInfo');
end;

procedure TASQLiteDB.GetTableIndexNames(TableName: String; List: TStrings);
var
  ResultPtr: Pointer;
  ResultStr: ^Pointer;
  RowCount: cardinal;
  ColCount: cardinal;
  ErrMsg: pchar;
  i: integer;
begin
  DebugEnter('TASQLiteDB.GetTableIndexNames');
  if not FConnected then
    Connected := true;
  if FConnected then
  begin
    SQLite_GetTable(DBHandle, pchar(
      'PRAGMA  index_list("' + TableName + '");'),
      ResultPtr, RowCount, ColCount, ErrMsg);

    ResultStr := ResultPtr;
    List.Clear;
    Inc(ResultStr, 4);     // Skip header + 1st col.
    for i := 1 to RowCount do
    begin
      List.Insert(0, pchar(ResultStr^));
      Inc(ResultStr, 3);
    end;
    if Assigned(ResultPtr) then SQLite_FreeTable(ResultPtr);
  end;
  DebugLeave('TASQLiteDB.GetTableIndexNames');
end;

procedure TASQLiteDB.DBConnect(Connected: boolean);
var
  ErrMsg: pchar;
  DBMS:   string;
begin
  DebugEnter('TASQLiteDB.DBConnect');
  if (Connected) and (FDatabase = '') then
  begin
    DebugLeave('TASQLiteDB.DBConnect Exit');
    raise AsgError.Create('Missing database property');
    SQLite_FreeMem(ErrMsg);
    FConnected := false;
    exit;
  end;

  if not Connected then
  begin
    if FConnected then
    begin
      if DLLHandle <> 0 then
      begin
        OutputDebugString('freeing sqlite dll');
        if Assigned(FBeforeDisconnect) then
          FBeforeDisconnect(self);
        if Assigned( @SQLite_Close) then
          SQLite_Close(DBHandle);
        FreeLibrary(DLLHandle);
        DLLHandle := 0;
        if Assigned(FAfterDisconnect) then
          FAfterDisconnect(self);
      end;
      FConnected := false;
      DebugLeave('TASQLiteDB.DBConnect');
      exit;
    end
  end
  else
  begin
    if DefaultDir <> '' then
    begin
      if DefaultDir[Length(DefaultDir)] <> '\' then     //20042302
        DefaultDir := DefaultDir + '\';                 //20042302
      DBMS := DefaultDir + Database;                    //20042302
    end
    else
    begin                                               //20042302
      if Pos('\', Database) = 0 then                    //20042302
        DBMS := GetCurrentDir + '\' + DataBase          //20042302
      else                                              //20042302
        DBMS := Database;                               //20042302
    end;                                                //20042302

    if FMustExist then
    begin
      if CompareText(':memory:', Database) <> 0 then
      begin
        if not FileExists(DBMS) then
        begin
          raise EDatabaseError.Create('Database ' + DBMS + ' does not exist');
          DebugLeave('TASQLiteDB.DBConnect ' + 'Database ' + DBMS + ' does not exist');
          exit;
        end;
      end;
    end;

    if DLLHandle = 0 then
    begin
      if not LoadLibs then
      begin
        FConnected := false;
        raise AsgError.Create('Could not load SQLite library');
        DebugLeave('TASQLiteDB.DBConnect ' + 'Could Not load SQLite Library');
        exit;
      end;
    end;

    FConnected := true;
    FVersion   := SQLite_Version;
    FCharEncoding := SQLite_Encoding;
    DBHandle   := nil;
    ErrMsg     := nil;
    if Assigned(FBeforeConnect) then
      FBeforeConnect(self);
    if Assigned( @SQLite_Open) then
      DBHandle := SQLite_Open(pchar(DBMS), 0, ErrMsg);
    if Assigned(FAfterConnect) then
      FAfterConnect(self);
    if DBHandle = nil then
      FConnected := false;

    // datatypes and length in resultset
    SQLite_XExec(DBHandle, pchar('PRAGMA show_datatypes = ON;'),
        @ExecCallback, self, ErrMsg);
    FLastError := ErrMsg;
    if ErrMsg <> nil then SQLite_FreeMem(ErrMsg);

    // always a need for the fieldnames and types
    SQLite_XExec(DBHandle, pchar('PRAGMA empty_result_callbacks = ON;'),
         @ExecCallback, self, ErrMsg);
    FLastError := ErrMsg;
    if ErrMsg <> nil then SQLite_FreeMem(ErrMsg);

    if Assigned(FASQLitePragma) then
      ExecPragma;

    FLastError := ErrMsg;
    if ErrMsg <> nil then
      SQLite_FreeMem(ErrMsg);
  end;
  DebugLeave('TASQLiteDB.DBConnect');
end;

function TASQLiteDB.RowsAffected: integer;
begin
  DebugEnter('TASQLiteDB.RowsAffected');
  if not FConnected then
    Result := -1
  else
    Result := SQLite_Changes(DBHandle);
  DebugLeave('TASQLiteDB.RowsAffected');
end;

procedure TASQLiteDB.StartTransaction;
var
  ErrMsg: pchar;
begin
  DebugEnter('TASQLiteDB.StartTransaction');
  if not FConnected then // open database if necessary
    Connected := true; // trigger the 'dbconnect' event

  if FConnected then
  begin
    SQLite_XExec(DBHandle, pchar('begin transaction'), @ExecCallback, self, ErrMsg);
    FLastError := ErrMsg;
    if ErrMsg <> nil then
    begin
      raise EDatabaseError.Create(ErrMsg);
      SQLite_FreeMem(ErrMsg);
    end;
  end;
  DebugLeave('TASQLiteDB.StartTransaction');
end;

procedure TASQLiteDB.Open;
var
  ErrMsg: pchar;
begin
  DebugEnter('TASQLiteDB.Open');
  Connected := true;
  if DLLHandle = 0 then
     Connected := false
  else begin
     if ExecuteInlineSQL and Assigned(FInlineSQL) then begin
        StartTransaction;
        SQLite_XExec(DBHandle, pchar(FInlineSQL.FSQL), @ExecCallback, self, ErrMsg);
        if ErrMsg <> '' then begin
           raise EDatabaseError.Create(ErrMsg);
           SQLite_FreeMem(ErrMsg);
        end;
        Commit;
     end;
  end;
  DebugLeave('TASQLiteDB.Open');
end;

procedure TASQLiteDB.Close;
begin
  DebugEnter('TASQLiteDB.Close');
  Connected := false;
  DebugLeave('TASQLiteDB.Close');
end;

procedure TASQLiteDB.ExecPragma;
var
  ErrMsg: pchar;
  Cmd:    string;
begin
  DebugEnter('TASQLiteDB.ExecPragma');
  if not FConnected then
    Connected := true;
  if FConnected then
  begin
    if FASQLitePragma.FTempCacheSize <> 0 then
    begin
      cmd := FASQLitePragma.GetTempCacheSize;
      SQLite_XExec(DBHandle, pchar(cmd), @ExecCallback, self, ErrMsg);
    end;
    if FASQLitePragma.FDefaultCacheSize <> 0 then
    begin
      cmd := FASQLitePragma.GetDefaultCacheSize;
      SQLite_XExec(DBHandle, pchar(cmd), @ExecCallback, self, ErrMsg);
    end;

    if FASQLitePragma.FDefaultSynchronous <> '' then
    begin
      cmd := FASQLitePragma.GetDefaultSynchronous;
      SQLite_XExec(DBHandle, pchar(cmd), @ExecCallback, self, ErrMsg);
    end;

    if FASQLitePragma.FDefaultTempStore <> '' then
    begin
      cmd := FASQLitePragma.GetDefaultTempStore;
      SQLite_XExec(DBHandle, pchar(cmd), @ExecCallback, self, ErrMsg);
    end;

    if FASQLitePragma.FTempStore <> '' then
    begin
      cmd := FASQLitePragma.GetTempStore;
      SQLite_XExec(DBHandle, pchar(cmd), @ExecCallback, self, ErrMsg);
    end;

    if FASQLitePragma.FSynchronous <> '' then
    begin
      cmd := FASQLitePragma.GetSynchronous;
      SQLite_XExec(DBHandle, pchar(cmd), @ExecCallback, self, ErrMsg);
    end;

    FLastError := ErrMsg;
    if ErrMsg <> nil then
    begin
      raise EDatabaseError.Create(ErrMsg);
      SQLite_FreeMem(ErrMsg);
    end;
  end;
  DebugLeave('TASQLiteDB.ExecPragma');
end;

procedure TASQLiteDB.Commit;
var
  ErrMsg: pchar;
begin
  DebugEnter('TASQLiteDB.Commit');
  if not FConnected then
    Connected := true;
  if FConnected then
  begin
    SQLite_XExec(DBHandle, pchar('commit transaction'), @ExecCallback, self, ErrMsg);
    FLastError := ErrMsg;
    if ErrMsg <> nil then
    begin
      raise EDatabaseError.Create(ErrMsg);
      SQLite_FreeMem(ErrMsg);
    end;
  end;
  DebugLeave('TASQLiteDB.Commit');
end;

procedure TASQLiteDB.RollBack;
var
  ErrMsg: pchar;
begin
  DebugEnter('TASQLiteDB.RollBack');
  if not FConnected then
    Connected := true;
  if FConnected then
  begin
    SQLite_XExec(DBHandle, pchar('rollback transaction'), @ExecCallback, self, ErrMsg);
    FLastError := ErrMsg;
    if ErrMsg <> nil then
    begin
      raise EDatabaseError.Create(ErrMsg);
      SQLite_FreeMem(ErrMsg);
    end;
  end;
  DebugLeave('TASQLiteDB.RollBack');
end;

constructor TASQLiteDB.Create(AOwner: TComponent);
//var fn : TextFile;
begin
  DebugEnter('TASQLiteDB.Create');
  OutputDebugString('TASQLiteDB ' + SQLiteVersion);
  Connected     := false;
  ASQLiteLog    := nil;
  ASQLitePragma := nil;
  inherited Create(AOwner);
  DebugLeave('TASQLiteDB.Create');
end;

destructor TASQLiteDB.Destroy;
//var fn : TextFile;
begin
  DebugEnter('TASQLiteDB.Destroy');
  FConnected    := false;
  ASQLiteLog    := nil;
  ASQLitePragma := nil;
  inherited Destroy;
  DebugLeave('TASQLiteDB.Destroy');
end;

//============================================================================== TASQLiteBaseQuery
{
 Register detail dataset for a master-detail relationship
}
procedure TASQLiteBaseQuery.RegisterDetailDataset(DetailDataSet: TASQLiteBaseQuery);
var
  i: integer;
begin
 DebugEnter('TASQLiteBaseQuery.RegisterDetailDataset');
 try
  for i := 0 to DetailList.Count - 1 do
    if DetailList[i] = DetailDataset then exit;
  DetailList.Add(DetailDataSet);
 finally
  DebugLeave('TASQLiteBaseQuery.RegisterDetailDataset');
 end;
end;

{ compatibility isue }
procedure TASQLiteBaseQuery.SQLiteMasterChanged;
begin
  DebugEnter('TASQLiteBaseQuery.SQLiteMasterChanged');
  DebugLeave('TASQLiteBaseQuery.SQLiteMasterChanged');
end;

{
 notify that the master has changed and a requery on the detail has
 to be done
}
procedure TASQLiteBaseQuery.NotifySQLiteMasterChanged;
var
  i: integer;
begin
  DebugEnter('TASQLiteBaseQuery.NotifySQLiteMasterChanged');
  for i := 0 to DetailList.Count - 1 do
  begin
    TASQliteBaseQuery(DetailList[i]).SQLiteMasterChanged;
  end;
  DebugLeave('TASQLiteBaseQuery.NotifySQLiteMasterChanged');
end;

{
   Unpack the buffer (if necessary) and convert it to a valid representation
   this is necessary for sqlite since it it typeless. If typed has been
   defined then the fields have to be converted to the appropiate datatype
}
function TASQLiteBaseQuery.UnpackBuffer(Buffer: pchar; FieldType: TFieldType) : TConvertBuffer;
var
  TempInt:    integer;
  TempDouble: double;
  TempBool:   wordbool;
  TempT:      TDateTimeRec;
begin
 {$IFDEF DEBUG_VERY_LOUD}
   DebugEnter('TASQLiteBaseQuery.UnpackBuffer: ' + Buffer);
 {$ENDIF}
  case FieldType of
    ftString:
    begin
      {$IFDEF DEBUG_VERY_LOUD}
       DebugLeave('TASQLiteBaseQuery.UnpackBuffer');
      {$ENDIF}
      exit;
    end;
    ftInteger, ftSmallInt:
    begin
      TempInt := StrToInt(Buffer);
      Move(TempInt, result, sizeof(TempInt));
    end;
    ftTime:
    begin
      TempT := DateTimeToNative(FieldType, StrToDateTimeX(Buffer));
      Move(TempT, result, sizeof(TDateTime));
    end;
    ftDate:
    begin
      TempT := DateTimeToNative(FieldType, StrToDateTimeX(Buffer));
      Move(TempT, result, sizeof(TDateTime));
    end;
    ftDateTime:
    begin
      if FSQLiteDateFormat then                                              // aducom
         TempT := DateTimeToNative(FieldType, YYYYMMDDParser(Buffer))        // jpierce
      else
         TempT := DateTimeToNative(FieldType, StrToDateTimeX(Buffer));
      Move(TempT, result, sizeof(TDateTime));
    end;
    ftFloat, ftBCD, ftCurrency:
    begin
      TempDouble := StrToFloat(Buffer);
      Move(TempDouble, result, sizeof(TempDouble));
    end;
{$IFDEF ASQLITE_D6PLUS}
    ftBoolean:
    begin
      TempBool := StrToBool(Buffer);
      Move(TempBool, result, sizeof(TempBool));
    end;
{$ENDIF}
    ftMemo, ftGraphic, ftBlob, ftFMTMemo: // pointer to stream
    begin
      TempInt := StrToInt(Buffer);
      Move(TempInt, result, sizeof(TempInt));
    end;
  end;
  {$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TASQLiteBaseQuery.UnpackBuffer');
  {$ENDIF}
end;

{ This method is called by TDataSet.Open and also when FieldDefs need to
  be updated (usually by the DataSet designer).  Everything which is
  allocated or initialized in this method should also be freed or
  uninitialized in the InternalClose method. }

constructor TASQLiteBaseQuery.Create;
begin
  DebugEnter('TASQLiteBaseQuery.Create');
  OutputDebugString('TASQLiteBaseQuery ' + SQLiteVersion);
  MaxStrLen := 0;
  FSQL      := TStringList.Create;
  DetailList := TList.Create;
  GetMem(ResultStr, MaxBuf);
  inherited;
  DebugLeave('TASQLiteBaseQuery.Create');
end;

function TASQLiteBaseQuery.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
begin
  Result := TASQLiteBlobStream.Create(Field as TBlobField, Mode);
end;

destructor TASQLiteBaseQuery.Destroy;
begin
  DebugEnter('TASQLiteBaseQuery.Destroy');
//  Close;
  inherited Destroy;
  if Assigned(FSQL) then
    FSQL.Free;
  FSQL := nil;
  if Assigned(DetailList) then
    DetailList.Free;
  DetailList := nil;
  if Assigned(FConnection) then
    FConnection := nil;
  if Assigned(ResultStr) then
    FreeMem(ResultStr, MaxBuf);
  if Assigned(FResult) then
  begin
    FResult.Free;
    FResult := nil;
  end;

//  if Assigned(BLOB) then begin
//    while BLOB.Count > 0 do begin
//       FreeMem(BLOB[0]);
//       BLOB.Delete(0);
//     end;
//     BLOB.Free;
//     BLOB := nil;
//  end;

  ResultStr := nil;
  DebugLeave('TASQLiteBaseQuery.Destroy');
end;

procedure TASQLiteBaseQuery.StartTransaction;
begin
  if Assigned(FConnection) then
    FConnection.StartTransaction;
end;

procedure TASQLiteBaseQuery.Commit;
begin
  if Assigned(FConnection) then
    FConnection.Commit;
end;

procedure TASQLiteBaseQuery.RollBack;
begin
  if Assigned(FConnection) then
    FConnection.RollBack;
end;

//function TASQLiteBaseQuery.LocateNearest(const KeyFields: String; const KeyValues: Variant; Options: TLocateOptions): Boolean;
 //begin
 //end;

// TODO: Partial key... (not very difficult but not a priority for now...)

function TASQLiteBaseQuery.Locate(const KeyFields: string;
 const KeyValues: variant; Options: TLocateOptions): boolean;
//loCaseInsensitive, loPartialKey
var
 bOk:     boolean;
 i, j, p: integer;
 Fields:  string;
 FieldList: TStringList;
 DebugStr: string;
 DoEnableControls: boolean;
begin
 DebugEnter('TASQLiteBaseQuery.Locate ' + Keyfields);
 DoEnableControls := not ControlsDisabled; {used to determine whether to EnableControls at end of function}
 DisableControls;
 FieldList := TStringList.Create;
 bOk := true;
 try
   Fields := KeyFields;
   p := pos(';', Fields);
   while p > 0 do
   begin
     FieldList.Add(Copy(Fields, 1, p - 1));
     System.Delete(Fields, 1, p);
     p := pos(';', Fields);
   end;
   if Fields <> '' then
     FieldList.Add(Fields);

   First;
   for i := 1 to FResult.Data.Count do
   begin
     SetRecNo(i);
     bOk := true;
     for j := 0 to FieldList.Count - 1 do
     begin
       if loCaseInsensitive in Options then
       begin
         if FieldList.Count = 1 then
         begin
           if CompareText(KeyValues, FieldByName(FieldList[j]).AsString) <> 0 then
           begin
             bOk := false;
             break;
           end;
         end
         else
         begin
           if CompareText(KeyValues[j], FieldByName(FieldList[j]).AsString) <> 0 then
           begin
             bOk := false;
             break;
           end;
         end
       end
       else
       begin
         if FieldList.Count = 1 then
         begin
           if CompareStr(KeyValues, FieldByName(FieldList[j]).AsString) <> 0 then
           begin
             bOk := false;
             break;
           end;
         end
         else
         begin
           if CompareStr(KeyValues[j], FieldByName(FieldList[j]).AsString) <> 0 then
           begin
             bOk := false;
             break;
           end;
         end;
       end;
     end;
     if bOk then
     begin
       break;
     end;
   end;
   if bOk then
   begin
     Locate := true;
     DebugStr := 'TASQLiteBaseQuery.Locate true';
   end
   else
   begin
     Locate := false;
     DebugStr := 'TASQLiteBaseQuery.Locate false';
   end;
 finally
   FieldList.Free;
   if DoEnableControls then {restore original state of the controls}
     EnableControls;
   DebugLeave(DebugStr);
 end;
end;

function TASQLiteBaseQuery.GetDataSource: TDataSource;
begin
  DebugEnter('TASQLiteBaseQuery.GetDataSource');
  Result := FMasterSource;
  DebugLeave('TASQLiteBaseQuery.GetDataSource');
end;

procedure TASQLiteBaseQuery.SetDataSource(Value: TDataSource);
begin
  DebugEnter('TASQLiteBaseQuery.SetDataSource');
  if IsLinkedTo(Value) then
    DatabaseError('circular references are not allowed', Self);
  FMasterSource := Value;
  DebugLeave('TASQLiteBaseQuery.SetDataSource');
end;

function TASQLiteBaseQuery.GetMasterFields: string;
begin
  DebugEnter('TASQLiteBaseQuery.GetMasterFields');
  Result := FMasterFields; //FMasterLink.FieldNames;
  DebugLeave('TASQLiteBaseQuery.GetMasterFields');
end;

procedure TASQLiteBaseQuery.SetMasterFields(const Value: string);
begin
  DebugEnter('TASQLiteBaseQuery.SetMasterFields ' + Value);
  FMasterFields := Value; //  FMasterLink.FieldNames := Value;
  DebugLeave('TASQLiteBaseQuery.SetMasterFields');
end;
 //Checks the State and Results a defined Buffer;

function TASQLiteBaseQuery.GetActiveBuffer(var Buffer: pchar): boolean;
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('TASQLiteBaseQuery.GetActiveBuffer');
{$ENDIF}
  case State of
    dsBrowse: if IsEmpty then
        Buffer := nil
      else
        Buffer := ActiveBuffer;

    dsEdit:   Buffer := ActiveBuffer;
    dsInsert: Buffer := ActiveBuffer;
    dsFilter: Buffer := ActiveBuffer; //FFilterBuffer;

    else
      Buffer := nil;
  end;
  Result := Buffer <> nil;
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TASQLiteBaseQuery.GetActiveBuffer ' + pchar(Buffer));
{$ENDIF}
end;

function TASQLiteBaseQuery.GetNativeFieldSize(FieldNo: integer): integer;
begin
  DebugEnter('TASQLiteBaseQuery.GetNativeFieldSize');
  Result := 0;
  case FieldDefs.Items[FieldNo - 1].Datatype of
    ftString: Inc(Result, FieldDefs.Items[FieldNo - 1].Size);
    ftInteger, ftSmallInt, ftDate, ftTime: Result := 12;
    ftDateTime: Result := 20;
    ftFloat, ftBCD, ftCurrency: Result := 12;
    ftBoolean: Result  := 12;
    ftGraphic, ftMemo, ftBlob, ftFmtMemo: Result := 12; // space for memory handles
    else
      raise AsgError.Create('Fieldtype of Field "' + FieldDefs.Items[FieldNo - 1].Name +
        '" not supported!');
  end;
  DebugLeave('TASQLiteBaseQuery.GetNativeFieldSize');
end;

function TASQLiteBaseQuery.GetFieldSize(FieldNo: integer): integer;
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('TASQLiteBaseQuery.GetFieldSize');
{$ENDIF}
 // try
 // ShowMessage('FieldNo in getfieldsize '+IntToStr(FildNo));
  Result := 0;
  case FieldDefs.Items[FieldNo - 1].Datatype of
    ftString: Inc(Result, FieldDefs.Items[FieldNo - 1].Size);
    ftInteger, ftSmallInt, ftDate, ftTime: Inc(Result, sizeof(integer));
    ftDateTime: Inc(Result, sizeof(TDateTime));
    ftFloat, ftBCD, ftCurrency: Inc(Result, sizeof(double));
    ftBoolean: Inc(Result, sizeof(wordbool));
    ftGraphic, ftMemo, ftBlob, ftFmtMemo : Inc(Result, sizeof(pointer));
    else
      raise AsgError.Create('Fieldtype of Field "' + FieldDefs.Items[FieldNo - 1].Name +
        '" not supported!');
  end;
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TASQLiteBaseQuery.GetFieldSize');
{$ENDIF}
end;

function TASQLiteBaseQuery.GetFieldOffset(FieldNo: integer): integer;
var
  i:      integer;
  Offset: integer;
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('TASQLiteBaseQuery.GetFieldOffset');
{$ENDIF}
  Offset := 0;
  if FieldNo > 1 then
  begin
    for i := 1 to FieldNo - 1 do
      OffSet := OffSet + GetFieldSize(i);
  end;
  GetFieldOffset := Offset;
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TASQLiteBaseQuery.GetFieldOffset');
{$ENDIF}
end;

procedure TASQLiteBaseQuery.SetSQL(const Value: TStrings);
begin
  DebugEnter('TASQLiteBaseQuery.SetSQL');
  Close;
  if Assigned(FSQL) then
    FSQL.Assign(Value)
  else
    FSQL := Value;
  DebugLeave('TASQLiteBaseQuery.SetSQL');
end;

procedure TASQLiteBaseQuery.LoadQueryData;
var
  ErrMsg: pchar;
begin
  DebugEnter('TASQLiteBaseQuery.LoadQueryData');
  if Connection.FConnected then
  begin
    FMetaData := false; // retrieve metadata on the first
    Connection.SQLite_XExec(Connection.DBHandle, pchar(FPrepared),
      @ExecCallback, self, ErrMsg);
// except end;
    FLastError := ErrMsg;
    if ErrMsg <> nil then
    begin
      raise EDatabaseError.Create(ErrMsg + #10 + SQLStr);
      Connection.SQLite_FreeMem(ErrMsg);
    end;
  end;

  DebugLeave('TASQLiteBaseQuery.LoadQueryData');
end;

procedure TASQLiteBaseQuery.InternalOpen;
begin
  DebugEnter('TASQLiteBaseQuery.InternalOpen');
//  inherited;

  if (Connection = nil) then
  begin // check to see if a valid database
    raise AsgError.Create('no database connection');
  end
  else
  begin

    if Connection.Connected = false then // open database if necessary
      Connection.Connected := true; // trigger the 'dbconnect' event

    if (Connection.Connected) and (Connection.DLLHandle <> 0) then
      if Assigned(MasterSource) then
      begin // notify master about existance!
        if (MasterSource.DataSet <> nil) then
        begin
          if CompareText(Copy(MasterSource.DataSet.ClassName, 1, 8), 'TASQLite') = 0 then
          begin
            TASQLiteBaseQuery(MasterSource.DataSet).RegisterDetailDataset(
              TASQLiteBaseQuery(Self));
          end
          else
          begin
            ShowMessage('master dataset ' + MasterSource.DataSet.ClassName +
              ' is not of TSQLiteBaseQuery type');
            DebugLeave('TASQLiteBaseQuery.InternalOpen');
            exit;
          end;
        end
        else
        begin
          ShowMessage('master dataset undefined');
          DebugLeave('TASQLiteBaseQuery.InternalOpen');
          exit;
        end;
      end;

  { Load the result into a resultlist }
    FResult := TFResult.Create(Self);
//    Blob := TList.Create;
    LoadQueryData;

  { Initialize our internal position.
    We use -1 to indicate the "crack" before the first record. }
    FCurRec := -1;

  { Initialize an offset value to find the TRecInfo in each buffer }
    FRecInfoOfs := MaxStrLen;

  { Calculate the size of the record buffers.
    Note: This is NOT the same as the RecordSize property which
    only gets the size of the data in the record buffer }
    FRecBufSize := FRecInfoOfs + SizeOf(TRecInfo);

  { Tell TDataSet how big our Bookmarks are (REQUIRED) }
    BookmarkSize := SizeOf(integer);

  { Initialize the FieldDefs }
    InternalInitFieldDefs;

  { Create TField components when no persistent fields have been created }
    if DefaultFields then
      CreateFields;

  { Bind the TField components to the physical fields }
    BindFields(true);

  end;

//  if CalcFieldsSize > 0 then
//         GetCalcFields(Buffer);
  DebugLeave('TASQLiteBaseQuery.InternalOpen');
end;

procedure TASQLiteBaseQuery.InternalClose;
begin
  DebugEnter('TASQLiteBaseQuery.InternalClose');
//  inherited; // InternalClose;

  if Assigned(FResult) then
  begin
    FResult.Free;
    FResult := nil;
  end;

  { Destroy the TField components if no persistent fields }
  if DefaultFields then
    DestroyFields;

  { Reset these internal flags }
  //  FLastBookmark := 0;
  FCurRec := -1;
  DebugLeave('TASQLiteBaseQuery.InternalClose');
end;

{ This property is used while opening the dataset.
  It indicates if data is available even though the
  current state is still dsInActive. }

function TASQLiteBaseQuery.IsCursorOpen: boolean;
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('TASQLiteBaseQuery.IsCursorOpen');
{$ENDIF}
  Result := Assigned(FResult);
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TASQLiteBaseQuery.IsCursorOpen => '+BoolToStr(Result,True));

{$ENDIF}
end;

procedure TASQLiteBaseQuery.InternalInitFieldDefs;
begin
  DebugEnter('TASQLiteBaseQuery.InternalInitFieldDefs');
//  Just here for compatibility
  DebugLeave('TASQLiteBaseQuery.InternalInitFieldDefs');
end;

{ This is the exception handler which is called if an exception is raised
  while the component is being stream in or streamed out.  In most cases this
  should be implemented useing the application exception handler as follows. }

procedure TASQLiteBaseQuery.InternalHandleException;
begin
  DebugEnter('TASQLiteBaseQuery.InternalHandleException');
  Application.HandleException(Self);
  DebugLeave('TASQLiteBaseQuery.InternalHandleException');
end;

 { Bookmarks }
 { ========= }

{ In this sample the bookmarks are stored in the Object property of the
  TStringList holding the data.  Positioning to a bookmark just requires
  finding the offset of the bookmark in the TStrings.Objects and using that
  value as the new current record pointer. }

procedure TASQLiteBaseQuery.InternalGotoBookmark(Bookmark: Pointer);
var
  Index: integer;
begin
  DebugEnter('TASQLiteBaseQuery.InternalGotoBookmark');
//  inherited;
  Index := FResult.IndexOf(TObject(PInteger(Bookmark)^));
  if Index <> -1 then
    FCurRec := Index
  else
    DatabaseError('Bookmark not found');
  DebugLeave('TASQLiteBaseQuery.InternalGotoBookmark');
end;

function TASQLiteBaseQuery.BookmarkValid(Bookmark: Pointer) : boolean;
var
  Index: integer;
begin
  DebugEnter('TASQLiteBaseQuery.BookmarkValid');
  Index := FResult.IndexOf(TObject(PInteger(Bookmark)^));
  if Index <> -1 then
    BookmarkValid := true
  else
    BookmarkValid := false;
  DebugLeave('TASQLiteBaseQuery.BookmarkValid');
end;

{ This function does the same thing as InternalGotoBookmark, but it takes
  a record buffer as a parameter instead }

procedure TASQLiteBaseQuery.InternalSetToRecord(Buffer: pchar);
begin
  DebugEnter('TASQLiteBaseQuery.InternalSetToRecord');
  InternalGotoBookmark( @PRecInfo(Buffer + FRecInfoOfs).Bookmark);
  NotifySQLiteMasterChanged;
  DebugLeave('TASQLiteBaseQuery.InternalSetToRecord');
end;

{ Bookmark flags are used to indicate if a particular record is the first
  or last record in the dataset.  This is necessary for "crack" handling.
  If the bookmark flag is bfBOF or bfEOF then the bookmark is not actually
  used; InternalFirst, or InternalLast are called instead by TDataSet. }

function TASQLiteBaseQuery.GetBookmarkFlag(Buffer: pchar): TBookmarkFlag;
begin
  DebugEnter('TASQLiteBaseQuery.GetBookmarkFlag');
  Result := PRecInfo(Buffer + FRecInfoOfs).BookmarkFlag;
  DebugLeave('TASQLiteBaseQuery.GetBookmarkFlag');
end;

procedure TASQLiteBaseQuery.SetBookmarkFlag(Buffer: pchar; Value: TBookmarkFlag);
begin
  DebugEnter('TASQLiteBaseQuery.SetBookmarkFlag');
  PRecInfo(Buffer + FRecInfoOfs).BookmarkFlag := Value;
  DebugLeave('TASQLiteBaseQuery.SetBookmarkFlag');
end;

{ These methods provide a way to read and write bookmark data into the
  record buffer without actually repositioning the current record }

procedure TASQLiteBaseQuery.GetBookmarkData(Buffer: pchar; Data: Pointer);
begin
  DebugEnter('TASQLiteBaseQuery.GetBookmarkData');
  PInteger(Data)^ := PRecInfo(Buffer + FRecInfoOfs).Bookmark;
  DebugLeave('TASQLiteBaseQuery.GetBookmarkData');
end;

procedure TASQLiteBaseQuery.SetBookmarkData(Buffer: pchar; Data: Pointer);
begin
  DebugEnter('TASQLiteBaseQuery.SetBookmarkData');
  PRecInfo(Buffer + FRecInfoOfs).Bookmark := PInteger(Data)^;
  DebugLeave('TASQLiteBaseQuery.SetBookmarkData');
end;

 { Record / Field Access }
 { ===================== }

{ This method returns the size of just the data in the record buffer.
  Do not confuse this with RecBufSize which also includes any additonal
  structures stored in the record buffer (such as TRecInfo). }

function TASQLiteBaseQuery.GetRecordSize: word;
begin
  DebugEnter('TASQLiteBaseQuery.GetRecordSize');
  Result := MaxStrLen;
  DebugLeave('TASQLiteBaseQuery.GetRecordSize');
end;

{ TDataSet calls this method to allocate the record buffer.  Here we use
  FRecBufSize which is equal to the size of the data plus the size of the
  TRecInfo structure. }

function TASQLiteBaseQuery.AllocRecordBuffer: pchar;
begin
  DebugEnter('TASQLiteBaseQuery.AllocRecordBuffer');
  GetMem(Result, FRecBufSize);
  FillChar(Result^, GetRecordSize, 0);
  DebugLeave('TASQLiteBaseQuery.AllocRecordBuffer');
end;

{ Again, TDataSet calls this method to free the record buffer.
  Note: Make sure the value of FRecBufSize does not change before all
  allocated buffers are freed. }

procedure TASQLiteBaseQuery.FreeRecordBuffer(var Buffer: pchar);
begin
  DebugEnter('TASQLiteBaseQuery.FreeRecordBuffer');
  if Buffer <> nil then
    FreeMem(Buffer, FRecBufSize);
  DebugLeave('TASQLiteBaseQuery.FreeRecordBuffer');
end;

{ This multi-purpose function does 3 jobs.  It retrieves data for either
  the current, the prior, or the next record.  It must return the status
  (TGetResult), and raise an exception if DoCheck is True. }

function TASQLiteBaseQuery.GetRecord(Buffer: pchar; GetMode: TGetMode;
  DoCheck: boolean): TGetResult;
var
  ptr: pointer;
begin
  DebugEnter('TASQLiteBaseQuery.GetRecord');
//  if Active then CheckBrowseMode;
  if FResult.Count < 1 then
    Result := grEOF
  else
  begin
    Result := grOK;
    case GetMode of
      gmNext:
        if FCurRec >= RecordCount - 1 then
          Result := grEOF
        else
          Inc(FCurRec);
      gmPrior:
        if FCurRec <= 0 then
          Result := grBOF
        else
          Dec(FCurRec);
      gmCurrent:
      begin
        if (FCurRec < 0) or (FCurRec >= RecordCount) then
          Result := grError;
      end;
    end;
    if Result = grOK then
    begin
      ptr := FResult.GetData(FCurRec);
      Move(ptr^, Buffer^, MaxStrLen);

      if FResult.Count = 0 then
      begin
        InternalInitRecord(Buffer);
      end;

      with PRecInfo(Buffer + FRecInfoOfs)^ do
      begin
        BookmarkFlag := bfCurrent;
        Bookmark     := FResult.GetBookMark(FCurRec);
      end;

      if CalcFieldsSize > 0 then
         GetCalcFields(Buffer)
    end
    else if (Result = grError) and DoCheck then
      DatabaseError('No Records');
  end;
  DebugLeave('TASQLiteBaseQuery.GetRecord: ' + Buffer);
end;

{ This routine is called to initialize a record buffer. }

procedure TASQLiteBaseQuery.InternalInitRecord(Buffer: pchar);
var
  i:     integer;
  TempT: TDateTimeRec;
  Stream: TMemoryStream;
begin
  DebugEnter('TASQLiteBaseQuery.InternalInitRecord');
  for i := 0 to FieldCount - 1 do
  begin
    case FieldDefs.Items[i].Datatype of
      ftMemo, ftGraphic, ftBlob, ftFmtMemo : begin
        Stream := TMemoryStream.Create;
        Move(Pointer(Stream), (Buffer + GetFieldOffset(i+1))^, sizeof(Pointer));
      end;
      ftString: pchar(Buffer + GetFieldOffset(i + 1))^    := #0;
      ftBoolean: pBoolean(Buffer + GetFieldOffset(i + 1))^ := false;
      ftFloat: pFloat(Buffer + GetFieldOffset(i + 1))^    := 0;
      ftSmallInt: pSmallInt(Buffer + GetFieldOffset(i + 1))^ := 0;
      ftInteger: pInteger(Buffer + GetFieldOffset(i + 1))^ := integer(nil);
      ftCurrency: pFloat(Buffer + GetFieldOffset(i + 1))^ := 0;
      ftDate:
      begin
        TempT := DateTimeToNative(ftDate, now);
        Move(TempT, (Buffer + GetFieldOffset(i + 1))^, sizeof(TDateTime));
      end;
      ftTime:
      begin
        TempT := DateTimeToNative(ftTime, now);
        Move(TempT, (Buffer + GetFieldOffset(i + 1))^, sizeof(TDateTime));
      end;
      ftDateTime:
      begin
        TempT := DateTimeToNative(ftDateTime, now);
        Move(TempT, (Buffer + GetFieldOffset(i + 1))^, sizeof(TDateTime));
      end;
    end;
  end;
  DebugLeave('TASQLiteBaseQuery.InternalInitRecord');
end;

{ Here we copy the data from the record buffer into a field's buffer.
  This function, and SetFieldData, are more complex when supporting
  calculated fields, filters, and other more advanced features.
  See TBDEDataSet for a more complete example. }

function TASQLiteBaseQuery.GetFieldData(Field: TField; Buffer: Pointer): boolean;
var
  SrcBuffer: pchar;
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('TASQLiteBaseQuery.GetFieldData');
{$ENDIF}
  Result := false;
  if GetActiveBuffer(SrcBuffer) then
    if (Field.FieldNo > 0) and (Assigned(Buffer)) and (Assigned(SrcBuffer)) then
    begin
      Move((SrcBuffer + GetFieldOffset(Field.FieldNo))^, Buffer^, GetFieldSize(Field.FieldNo));
//      pchar(pchar(Buffer) + GetFieldSize(Field.FieldNo))^:=#0; // dev
      Result := true;
    end
    else
    begin
      if (Field.FieldNo > 0) and (Assigned(SrcBuffer)) then begin
          if (Field.DataType <> ftDateTime) and ((SrcBuffer + GetFieldOffset(Field.FieldNo))^=#0) then
             Result := false
          else
             Result := true;
      end;
    end;
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TASQLiteBaseQuery.GetFieldData: ' + pchar(Buffer));
{$ENDIF}
end;

// The next two functions are added to increase compatibility with
// components that require it (like DevExpress)

function TASQLiteBaseQuery.GetFieldData(FieldNo: integer; Buffer: Pointer): boolean;
begin
  Result := GetFieldData(FieldByNumber(FieldNo), Buffer);
end;

{$IFDEF ASQLITE_D6PLUS}
//function TASQLiteBaseQuery.GetFieldData(Field: TField; Buffer: Pointer;
//  NativeFormat: boolean): boolean;
//begin
//  Result := GetFieldData(Field, Buffer);
//end;
{$ENDIF}

{ returns the field data back to callee }
procedure TASQLiteBaseQuery.SetFieldData(Field: TField; Buffer: Pointer);
var
  DestBuffer: pchar;
begin
  DebugEnter('TASQLiteBaseQuery.SetFieldData');
  GetActiveBuffer(DestBuffer);
  if (Field.FieldNo > 0) and (Assigned(Buffer)) and (Assigned(DestBuffer)) then
  begin
    Move(Buffer^, (ActiveBuffer + GetFieldOffset(Field.FieldNo))^,
      GetFieldSize(Field.FieldNo));
  end;
  DataEvent(deFieldChange, longint(Field));
  DebugLeave('TASQLiteBaseQuery.SetFieldData');
end;

{ Record Navigation / Editing }
{ =========================== }

{ This method is called by TDataSet.First.  Crack behavior is required.
  That is we must position to a special place *before* the first record.
  Otherwise, we will actually end up on the second record after Resync
  is called. }

procedure TASQLiteBaseQuery.InternalFirst;
begin
  DebugEnter('TASQLiteBaseQuery.InternalFirst');
  FCurRec := -1;
  DebugLeave('TASQLiteBaseQuery.InternalFirst');
end;

{ Again, we position to the crack *after* the last record here. }

procedure TASQLiteBaseQuery.InternalLast;
begin
  DebugEnter('TASQLiteBaseQuery.InternalLast');
  FCurRec := FResult.Count;
  DebugLeave('TASQLiteBaseQuery.InternalLast');
end;

function TASQLiteBaseQuery.GetLastInsertRow : integer;
begin
  if Assigned(Connection) then
     result := Connection.SQLite_LastInsertRow(Connection.DBHandle)
  else
     result := -1;
end;

{ This method is called by TDataSet.Post. }

procedure TASQLiteBaseQuery.InternalPost;
var
  ptr: Pointer;
begin
  DebugEnter('TASQLiteBaseQuery.InternalPost');
//  inherited;
  FSaveChanges := true;
  { For inserts, just update the data in the string list }
  if State = dsEdit then
  begin
    ptr := FResult.GetData(FCurrec);
    move(ActiveBuffer^, ptr^, FRecBufSize);
  end
  else
  begin
    { If inserting (or appending), increment the bookmark counter and
      store the data }
    FResult.Insert(FCurRec, ActiveBuffer,
      Connection.SQLite_LastInsertRow(Connection.DBHandle)); // ?????????
  end;
  DebugLeave('TASQLiteBaseQuery.InternalPost');
end;

{ This method is similar to InternalPost above, but the operation is always
  an insert or append and takes a pointer to a record buffer as well. }

procedure TASQLiteBaseQuery.InternalAddRecord(Buffer: Pointer; Append: boolean);
begin
  DebugEnter('TASQLiteBaseQuery.InternalAddRecord');
  FSaveChanges := true;
  if Append then
    InternalLast;
  FResult.Insert(FCurRec, pchar(Buffer), Connection.SQLite_LastInsertRow(
    Connection.DBHandle));
  DebugLeave('TASQLiteBaseQuery.InternalAddRecord');
end;

{ This method is called by TDataSet.Delete to delete the current record }

procedure TASQLiteBaseQuery.InternalDelete;
begin
  DebugEnter('TASQLiteBaseQuery.InternalDelete');
//  inherited;
  FSaveChanges := true;
  FResult.Delete(FCurRec);
  if FCurRec >= FResult.Count then
    Dec(FCurRec);
  DebugLeave('TASQLiteBaseQuery.InternalDelete');
end;

 { Optional Methods }
 { ================ }

{ The following methods are optional.  When provided they will allow the
  DBGrid and other data aware controls to track the current cursor postion
  relative to the number of records in the dataset.  Because we are dealing
  with a small, static data store (a stringlist), these are very easy to
  implement.  However, for many data sources (SQL servers), the concept of
  record numbers and record counts do not really apply. }

function TASQLiteBaseQuery.GetRecordCount: longint;
begin
  DebugEnter('TASQLiteBaseQuery.GetRecordCount');
  Result := FResult.Count;
  DebugLeave('TASQLiteBaseQuery.GetRecordCount ' + IntToStr(Result));
end;

function TASQLiteBaseQuery.GetRecNo: longint;
begin
  DebugEnter('TASQLiteBaseQuery.GetRecNo');
  UpdateCursorPos;
  if (FCurRec = -1) and (RecordCount > 0) then
    Result := 1
  else
    Result := FCurRec + 1;
  DebugLeave('TASQLiteBaseQuery.GetRecNo');
end;

procedure TASQLiteBaseQuery.SetRecNo(Value: integer);
begin
  DebugEnter('TASQLiteBaseQuery.SetRecNo');
  if (Value >= 0) and (Value < FResult.Count + 2) then // value < resultetc
  begin
    FCurRec := Value - 1;
    Resync([]);
  end;
  DebugLeave('TASQLiteBaseQuery.SetRecNo');
end;

procedure TASQLiteBaseQuery.SetFiltered(Value: Boolean);
begin
 try
   Inherited;
 finally
  try
   Open;
  except
    on E: Exception do begin
       Filtered := false;
       raise AsgError.Create('Filter error: '+E.Message);
    end;
  end;
 end;
end;

procedure TASQLiteBaseQuery.SetFilterText(const Value: string);
begin
  DebugEnter('TASQLiteBaseQuery.SetFilterText ' + Value);
  Close;
  inherited;
  DebugLeave('TASQLiteBaseQuery.SetFilterText');
end;

// ============================================================================= TASQLITE UPDATE SQL

constructor TASQLiteUpdateSQL.Create(AOWner: TComponent);
begin
  DebugEnter('TASQLiteUpdateSQL.Create');
  OutputDebugString('TASQLiteUpdateSQL ' + SQLiteVersion);
  inherited Create(AOwner);
  FInsertSQL := TStringList.Create;
  FUpdateSQL := TStringList.Create;
  FDeleteSQL := TStringList.Create;
  DebugLeave('TASQLiteUpdateSQL.Create');
end;

destructor TASQLiteUpdateSQL.Destroy;
begin
  DebugEnter('TASQLiteUpdateSQL.Destroy');
  inherited;
  if Assigned(FInsertSQL) then
    FInsertSQL.Free;
  if Assigned(FUpdateSQL) then
    FUpdateSQL.Free;
  if Assigned(FDeleteSQL) then
    FDeleteSQL.Free;
  DebugLeave('TASQLiteUpdateSQL.Destroy');
end;

procedure TASQLiteUpdateSQL.SetInsertSQL(const Value: TStrings);
begin
  DebugEnter('TASQLiteUpdateSQL.SetInsertSQL');
  if Assigned(FInsertSQL) then
    FInsertSQL.Assign(Value)
  else
    FInsertSQL := Value;
  DebugLeave('TASQLiteUpdateSQL.SetInsertSQL');
end;

procedure TASQLiteUpdateSQL.SetUpdateSQL(const Value: TStrings);
begin
  DebugEnter('TASQLiteUpdateSQL.SetUpdateSQL');
  if Assigned(FUpdateSQL) then
    FUpdateSQL.Assign(Value)
  else
    FUpdateSQL := Value;
  DebugLeave('TASQLiteUpdateSQL.SetUpdateSQL');
end;

procedure TASQLiteUpdateSQL.SetDeleteSQL(const Value: TStrings);
begin
  DebugEnter('TASQLiteUpdateSQL.SetDeleteSQL');
  if Assigned(FDeleteSQL) then
    FDeleteSQL.Assign(Value)
  else
    FDeleteSQL := Value;
  DebugLeave('TASQLiteUpdateSQL.SetDeleteSQL');
end;
// ============================================================================= TASQLITE QUERY

constructor TASQLiteQuery.Create(AOwner: TComponent);
begin
  DebugEnter('TASQLiteQuery.Create');
  OutputDebugString('TASQLiteQuery ' + SQLiteVersion);
  inherited Create(AOwner);
  FParams := TParams.Create(Self);
  TStringList(FSQL).OnChange := QueryChanged;
  DebugLeave('TASQLiteQuery.Create');
end;

destructor TASQLiteQuery.Destroy;
begin
  DebugEnter('TASQLiteQuery.Destroy');
  TStringList(FSQL).OnChange := nil;
  if Assigned(FParams) then
  begin
    FParams.Free;
    FParams := nil;
  end;
  inherited Destroy;
  DebugLeave('TASQLiteQuery.Destroy');
end;

procedure TASQLiteQuery.Notification(AComponent: TComponent; Operation: TOperation);
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('TASQLiteQuery.Notification');
{$ENDIF}
//  Application.ProcessMessages;
  if Assigned(AComponent) then
  begin
    if (Operation = opRemove) then begin
      if Assigned(FUpdateSQL) and (AComponent is TASQLiteUpdateSQL) then begin
        if TASQLiteUpdateSQL(AComponent) = FUpdateSQL then
          FUpdateSQL := nil;
      end else

      if Assigned(FConnection) then begin
        if (AComponent is TASQLiteDB) and
          (TASQLiteDb(AComponent) = FConnection) then begin
          Close;
          Connection := nil;
        end;
      end else

    end;
  end;
  Inherited;
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TASQLiteQuery.Notification');
{$ENDIF}
end;

procedure TASQLiteQuery.QueryChanged(Sender: TObject);
begin
  DebugEnter('TASQLiteQuery.QueryChanged');
  FNoResults := false;
  Close;
  if not FRawSQL then begin
    if assigned(FParams) then FParams.Clear;      // new
//    if not (csReading in ComponentState) then
       SQLStr := FParams.ParseSQL(SQL.Text, true)
//    else
//       SQLStr := FParams.ParseSQL(SQL.Text, false);
  end else SQLStr := SQL.Text;
  DebugLeave('TASQLiteQuery.QueryChanged');
end;

procedure TASQLiteQuery.SetSQL(const Value: TStrings);
begin
  DebugEnter('TASQLiteQuery.SetSQL');
  FNoResults := false;
  if Assigned(FSQL) then
    FSQL.Assign(Value)
  else
    FSQL := Value;
//  FText := FParams.ParseSQL(SQL.Text, False);
  DebugLeave('TASQLiteQuery.SetSQL');
end;

function TASQLiteQuery.GetSQL: TStrings;
begin
  DebugEnter('TASQLiteQuery.GetSQL');
  GetSQL := FSQL;
  DebugLeave('TASQLiteQuery.GetSQL');
end;

procedure TASQLiteQuery.InternalDelete;
var
  ErrMsg: pchar;
  MySQL: string;
  TempSQL: string;
  TheWord: string;
  TableId: string;
  FieldId: string;
  startpos: integer;
  vartype: integer;
  p: integer;
label
  Ende;
begin
  DebugEnter('TASQLiteQuery.InternalDelete');
  if Connection.FConnected then
  begin
    if FAutoCommit then
      Connection.StartTransaction;

    if not Assigned(FUpdateSQL) then
    begin
      raise AsgError.Create('Missing TASQLiteUpdateSQL component');
      goto ende;
    end;
 //     MyFieldList := TStringList.Create;
 //     MyFieldValues := TStringList.Create;
    MySQL    := FUpdateSQL.FDeleteSQL.Text;
    startpos := 1;

    TheWord := GetWord(MySQL, startpos, vartype); // delete
    if not SyntaxCheck(TheWord, 'delete') then
      goto ende;

    TheWord := GetWord(MySQL, startpos, vartype); // from
    if not SyntaxCheck(TheWord, 'from') then
      goto ende;

    Tableid := GetWord(MySQL, startpos, vartype); // tablename

    TheWord := GetWord(MySQL, startpos, vartype); // where
    if not SyntaxCheck(TheWord, 'where') then
      goto ende;

    SQLStr  := 'delete from ' + TableId + ' where ';
    TempSQL := Copy(MySQL, startpos, 999);

    p := pos(':', TempSQL);
    while p > 0 do
    begin
      SQLStr := SQLStr + Copy(TempSQL, 1, p - 1);
      System.Delete(TempSQL, 1, p);
      startpos := 1;
      FieldId := GetWord(TempSQL, startpos, vartype); // variable
      System.Delete(TempSQL, 1, startpos );                                     // Tzvetan
      SQLStr := SQLStr + QuotedStr(FieldByName(FieldId).AsString);
      p := pos(':', TempSQL);
    end;
    SQLStr := SQLStr + Copy(TempSQL, StartPos, 999);
//ShowMessage(SQLStr);
    Connection.SQLite_XExec(Connection.DBHandle, pchar(SQLStr),
      @ExecCallback, self, ErrMsg);
    if ErrMsg <> nil then
    begin
      raise AsgError.Create(ErrMsg);
      Connection.SQLite_FreeMem(ErrMsg);
      if FAutoCommit then
        Connection.RollBack;
      Abort;
    end
    else
    begin
      if FAutoCommit then
        Connection.Commit;
    end;
    inherited InternalDelete;
  end;
  Ende:
    DebugLeave('TASQLiteQuery.InternalDelete');
end;

 //==============================================================================
 // This is probabely the most difficult thing about these components.
 // To be able to have a live resultset a tupdatequery must be used to
 // supply the correct sql on the events. In the internalpost the insert and
 // update is handled. The routine will take the given sql and remodel it
 // to a workable sql which is executed. Keep in mind that this routine
 // is far more difficult then the TASQLiteTable, since the last one is depending
 // on a unique rownumber, available in the resultset, which might not be
 // available to user queries
 // There are several syntaxes allowed:
 //
 // insert into table *
 // this will generate an insert statement for each field and values
 // i.e. insert into table a,b,c values :a, :b, :c;
 //
 // insert into table (a, b, c) values *
 // this will generate an insert statement like
 // insert into table (a, b, c) values (:a, :b, :c);
 //
 // insert into table (a, b, c) values (:a, :b, :c);
 // insert into table (a, b, c) values (:a, "bvalue", :c) etc.
 //
 // update table set * where <criteria>
 // this will generate a update for all fields like
 // update a=:a, b=:b, c=:c where <criteria>
 //
 //==============================================================================

procedure TASQLiteQuery.InternalPost;
var
  i:      integer;
  p:      integer;
  startpos: integer;
  ErrMsg: pchar;
  MyFieldList: TStringList;
  MyFieldValues: TStringList;
  MySQL:  string;
  TheWord: string;
  TempSQL: string;
  TableId: string;
  FieldId: string;
  varType: integer;
begin
 DebugEnter('TASQLiteQuery.InternalPost');

 MyFieldList := nil;
 MyFieldValues := nil;
 try
  if not Connection.FConnected then
  begin
    DebugLeave('TASQLiteQuery.InternalPost');
    exit;
  end;
  if FAutoCommit then
    Connection.StartTransaction;
  if not Assigned(FUpdateSQL) then
  begin
    DebugLeave('TASQLiteQuery.InternalPost Exception');
    raise AsgError.Create('Missing TASQLiteUpdateSQL component');
    exit;
  end;

  if (State = dsEdit) and (FResult.Count > 0) then
  begin
    MyFieldList := TStringList.Create;
    MyFieldValues := TStringList.Create;
    MySQL    := FUpdateSQL.FUpdateSQL.Text;
    startpos := 1;
    TheWord  := GetWord(MySQL, startpos, vartype); // update
    if not SyntaxCheck(TheWord, 'update') then
      exit;

    Tableid := GetWord(MySQL, startpos, vartype); // tablename

    TheWord := GetWord(MySQL, startpos, vartype); // set or '*'
    if TheWord = '*' then
    begin
      for i := 0 to FieldList.Count - 1 do
      begin
        MyFieldList.Add(FieldList[i].FieldName);
        MyFieldValues.Add(':' + FieldList[i].FieldName);
      end;
      TheWord := GetWord(MySQL, startpos, vartype); // where
    end
    else
    begin
      if not SyntaxCheck(TheWord, 'set') then
      begin
        DebugLeave('TASQLiteQuery.InternalPost');
        exit;
      end;

      repeat
        TheWord := GetWord(MySQL, startpos, vartype); // fieldname
        MyFieldList.Add(TheWord);

        TheWord := GetWord(MySQL, startpos, vartype); // '='
        if not SyntaxCheck(TheWord, '=') then
        begin
          DebugLeave('TASQLiteQuery.InternalPost');
          exit;
        end;

        TheWord := GetWord(MySQL, startpos, vartype); // fieldvalue
        if TheWord = '*' then
          MyFieldValues.Add(':' + MyFieldList[MyFieldList.Count - 1])
        else
          MyFieldValues.Add(TheWord);

        TheWord := GetWord(MySQL, startpos, vartype); // , or 'where'
      until CompareText(TheWord, 'where') = 0;
    end;

    if not SyntaxCheck(TheWord, 'where') then
      exit;

    SQLStr := 'update ' + TableId + ' set ';
    for i := 0 to FieldList.Count - 1 do
    begin
      SQLStr  := SQLStr + FieldList[i].FieldName + '=';
      FieldId := MyFieldValues[i];
      if FieldId[1] = ':' then
      begin
        System.Delete(FieldId, 1, 1);
        SQLStr := SQLStr + QuotedStr(FieldByName(FieldId).AsString)+','
      end
      else
        SQLStr := SQLStr + QuotedStr(FieldId) + ','
    end;
    System.Delete(SQLStr, Length(SQLStr), 1); // get rid of ','
    TempSQL := ' where ' + Copy(MySQL, startpos, 999);

    p := pos(':', TempSQL);
    while p > 0 do
    begin
      SQLStr := SQLStr + Copy(TempSQL, 1, p - 1);
      System.Delete(TempSQL, 1, p);
      startpos := 1;
      FieldId := GetWord(TempSQL, startpos, vartype); // variable
      System.Delete(TempSQL, 1, startpos );                                     // Tzvetan
      SQLStr := SQLStr + QuotedStr(FieldByName(FieldId).AsString);
      p := pos(':', TempSQL);
    end;
    SQLStr := SQLStr + Copy(TempSQL, StartPos, 999);

    Connection.SQLite_XExec(Connection.DBHandle, pchar(SQLStr),
      @ExecCallback, self, ErrMsg);
    if ErrMsg <> nil then
    begin
      raise AsgError.Create(ErrMsg);
      Connection.SQLite_FreeMem(ErrMsg);
      Abort;
    end
    else
    begin
      inherited InternalPost; // rework internals
    end;
  end
  else
  begin
    { If inserting (or appending), increment the bookmark counter and
      store the data. Sytax should be: insert into <table> * or
      insert into <table> (field, field) values (field, field) | *
      The sql is parsed and a new (valid) sql generated
    }
    MyFieldList := TStringList.Create;
    MyFieldValues := TStringList.Create;
    MySQL    := FUpdateSQL.FInsertSQL.Text;
    startpos := 1;
    TheWord  := GetWord(MySQL, startpos, vartype); // insert
    if not SyntaxCheck(TheWord, 'insert') then
      exit;

    TheWord := GetWord(MySQL, startpos, vartype); // into
    if not SyntaxCheck(TheWord, 'into') then
      exit;

    Tableid := GetWord(MySQL, startpos, vartype); // tablename

    TheWord := GetWord(MySQL, startpos, vartype); // ( or *
    if TheWord = '*' then
    begin
      for i := 0 to FieldList.Count - 1 do
      begin
        MyFieldList.Add(FieldList[i].FieldName);
        MyFieldValues.Add(':' + FieldList[i].FieldName);
      end;
    end
    else if TheWord = '(' then
    begin
      repeat
        TheWord := GetWord(MySQL, startpos, vartype); // fieldname
        MyFieldList.Add(TheWord);
        TheWord := GetWord(MySQL, startpos, vartype); // ',' or ')'
      until theword = ')';
      TheWord := GetWord(MySQL, startpos, vartype); // values
      TheWord := GetWord(MySQL, startpos, vartype); // '(' or '*'
      if TheWord = '*' then
      begin
        for i := 0 to MyFieldList.Count - 1 do
          MyFieldValues.Add(':' + MyFieldList[i]);
      end
      else
      begin
        repeat
          TheWord := GetWord(MySQL, startpos, vartype); // fieldname
//          MyFieldValues.Add(FieldList[i].FieldName); // ??? {Marc} Which VALUE has i here ???
          MyFieldValues.Add(TheWord); // ??? {Marc} Which VALUE has i here ???
          TheWord := GetWord(MySQL, startpos, vartype); // ',' or ')'
        until theword = ')';
      end;
    end
    else
    begin
      raise AsgError.Create('SQL macro syntax error on insertsql, expected ( or *');
      //exit; // does raise need exit ??
    end;

    SQLStr := 'insert into ' + TableId + ' (';
    for i := 0 to FieldList.Count - 1 do
      SQLStr := SQLStr + FieldList[i].FieldName + ',';
    SQLStr[Length(SQLStr)] := ')';
    SQLStr := SQLStr + ' values (';
    for i := 0 to FieldList.Count - 1 do
    begin
      FieldId := MyFieldValues[i];
      if FieldId[1] = ':' then
      begin
        System.Delete(FieldId, 1, 1);
        SQLStr := SQLStr + QuotedStr(FieldByName(FieldId).AsString) + ','
      end
      else
        SQLStr := SQLStr + QuotedStr(FieldId) + ','
    end;
    SQLStr[Length(SQLStr)] := ')';

    Connection.SQLite_XExec(Connection.DBHandle, pchar(SQLStr),
      @ExecCallback, self, ErrMsg);
    FLastError := ErrMsg;
    if ErrMsg <> nil then
    begin
      ShowMessage(ErrMsg);
      Connection.SQLite_FreeMem(ErrMsg);
      if FAutoCommit then
        Connection.RollBack;
      Abort;
      exit;
    end
    else
    begin
      if FResult.Count = 0 then
        Inc(FCurrec);
      inherited InternalPost; // rework internals
    end;
  end;
  if FAutoCommit then
  begin
    try
      Connection.Commit;
    except
      Connection.RollBack;
      raise;
    end;
  end;
 finally
  if Assigned(MyFieldList) then MyFieldList.Free;
  if Assigned(MyFieldValues) then MyFieldValues.Free;
 end;
  DebugLeave('TASQLiteQuery.InternalPost');
end;

procedure TASQLiteQuery.InternalClose;
begin
  DebugEnter('TASQLiteQuery.InternalClose');
  FPrepared := '';
  inherited;
  DebugLeave('TASQLiteQuery.InternalClose');
end;

procedure TASQLiteQuery.InternalOpen;
var
  p: integer;

  function SetQueryParams(InStr: string): string;
  var
    i: integer;
    TempParam: string;
  begin
    for i := 0 to FParams.Count - 1 do begin
      TempParam := Fparams.Items[i].AsString;
      if (TempParam='') and (FParams.Items[i].bound) then begin
        InStr := StringReplace(Instr, '?', 'NULL', []);
      end else begin
        //Here we'll replace legitimate '?' characters with an unprintable character
        TempParam := StringReplace(TempParam, '?', #1, [rfReplaceAll]);
        InStr := StringReplace(Instr, '?', QuotedStr(TempParam), [rfIgnoreCase]);
      end;
    end;
    //Here we'll restore legitimate '?' characters
    InStr := StringReplace(Instr, #1, '?', [rfReplaceAll]);
    InStr := StringReplace(Instr, crlf, ' ', [rfReplaceAll]);
    SetQueryParams := InStr;
  end;

begin
  DebugEnter('TASQLiteQuery.InternalOpen');
  if Trim(FSQL.Text) = '' then
  begin
    ShowMessage('no query specified');
    abort;
  end;

  if (FMaxResults = 0) and (FStartResult <> 0) then
    FMaxResults := -1;

 // SQLStr contains the 'raw' interpreted SQL, with ? as parameterlist
 // This string has to be preserved, since it was parsed on entering the sql.
 // On close and open (i.e. in case of master-detail) the parsed data still
 // must be available

 // We'll prepare the SQL statement into FPrepared. This is also the var
 // containing the SQL statement to be executed.
  FPrepared := SQLStr;

  if (Filtered) and (Filter <> '') then
  begin
    p := pos('where', LowerCase(FPrepared)); // add filter code to usersql
    if p = 0 then
      FPrepared := FPrepared + ' where ' + Filter
    else
      FPrepared := FPrepared + ' and ' + Filter;
  end;

  if FParams.Count > 0 then
    FPrepared := SetQueryParams(FPrepared);

  if FMaxResults <> 0 then
    FPrepared := FPrepared + ' limit ' + IntToStr(FMaxResults);
  if FStartResult <> 0 then
    FPrepared := FPrepared + ' offset ' + IntToStr(FMaxResults);
  inherited;
  DebugLeave('TASQLiteQuery.InternalOpen');
end;

 // =============================================================================
 // The master-detail is implemented through the filter object
 // in the future perhaps a separate filter object will be used allowing
 // to add your own criteria too, but for the time being..
 //==============================================================================

procedure TASQLiteQuery.SQLiteMasterChanged;
var
  r, s: string;
  m, d: string;
  p:    integer;
  cAnd: string;
begin
  DebugEnter('TASQLiteQuery.SQLiteMasterChanged');
  Close;
  cAnd   := '';
  r      := FMasterFields;
  Filter := '';
  while r <> '' do
  begin // build the filter sql syntax
    p := pos(';', r);
    if p = 0 then
    begin
      if Trim(r) <> '' then
        s := r;
      r := '';
    end
    else
    begin
      s := Trim(Copy(r, 1, p - 1));
      System.Delete(r, 1, p);
    end;

    p := pos('=', s);
    if p = 0 then
    begin
      ShowMessage('Syntax error: Masterfields not build of a=b;... pairs');
    end
    else
    begin
      d := copy(s, 1, p - 1);
      m := copy(s, p + 1, 99);
    end;
    Filter := Filter + cAnd + d + '=' + FMasterSource.DataSet.FieldByName(m).AsString;
    cAnd   := ' and ';
  end;
  if Filter <> '' then
    filtered := true;
  Open;
  DebugLeave('TASQLiteQuery.SQLiteMasterChanged');
end;

 //==============================================================================
 // execsql is used for sql statements which do not require cursors. For this
 // reason the fnoresults is set, to prevent building a result set
 //==============================================================================

procedure TASQLiteQuery.ExecSQL;
begin
  DebugEnter('TASQLiteQuery.ExecSQL');
  FNoResults := true;
  Close;
  if FAutoCommit then
  begin
    Connection.StartTransaction;
    Open;
    try
      Connection.Commit
    except
      Connection.RollBack;
      raise;
    end;
  end
  else
    Open;
  DebugLeave('TASQLiteQuery.ExecSQL');
end;

procedure TASQLiteQuery.SetParamsList(Value: TParams);
begin
  DebugEnter('TASQLiteQuery.SetParamsList');
  FParams.AssignValues(Value);
  DebugLeave('TASQLiteQuery.SetParamsList');
end;

function TASQLiteQuery.GetParamsCount: word;
begin
  DebugEnter('TASQLiteQuery.GetParamsCount');
  Result := FParams.Count;
  DebugLeave('TASQLiteQuery.GetParamsCount');
end;

procedure TASQLiteTable.Notification(AComponent: TComponent; Operation: TOperation);
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('TASQLiteTable.Notification');
{$ENDIF}
//  Application.ProcessMessages;
  if Assigned(AComponent) then
  begin
    if (Operation = opRemove) then
    begin
      if (AComponent is TASQLiteDB) and Assigned(FConnection) then
      begin
        if TASQLiteDB(AComponent) = FConnection then begin
          Close;
          FConnection := nil;
        end;
      end else

    end;
  end;
  Inherited;
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TASQLiteTable.Notification');
{$ENDIF}
end;

procedure TASQLiteTable.InternalOpen;
begin
  DebugEnter('TASQLiteTable.InternalOpen');
  if FTableName = '' then
  begin
    ShowMessage('no table specified');
    exit;
  end;
  FSQL.Clear;
  FSQL.Add('select *, rowid from ' + TableName);
  if Filtered then
    if Filter <> '' then
      FSQL.Add(' where ' + Filter);
  if (FMaxResults = 0) and (FStartResult <> 0) then
    FMaxResults := -1;
  if FMaxResults <> 0 then
    FSQL.Add(' limit ' + IntToStr(FMaxResults));
  if FStartResult <> 0 then
    FSQL.Add(' offset ' + IntToStr(FStartResult));
  SQLStr    := FSQL.Text;
  FPrepared := SQLStr;
  inherited;
  DebugLeave('TASQLiteTable.InternalOpen');
end;

procedure TASQLiteTable.SQLiteMasterChanged;
var
  r, s: string;
  m, d: string;
  p:    integer;
  cAnd: string;
begin
  DebugEnter('TASQLiteTable.SQLiteMasterChanged');
  Close;
  cAnd   := '';
  r      := FMasterFields;
  Filter := '';
  while r <> '' do
  begin
    p := pos(';', r);
    if p = 0 then
    begin
      if Trim(r) <> '' then
        s := r;
      r := '';
    end
    else
    begin
      s := Trim(Copy(r, 1, p - 1));
      System.Delete(r, 1, p);
    end;

    p := pos('=', s);
    if p = 0 then
    begin
      ShowMessage('Syntax error: Masterfields not build of a=b;... pairs');
    end
    else
    begin
      d := copy(s, 1, p - 1);
      m := copy(s, p + 1, 99);
    end;
    Filter := Filter + cAnd + d + '=' + FMasterSource.DataSet.FieldByName(m).AsString;
    cAnd   := ' and ';
  end;
  if Filter <> '' then
    filtered := true;
  Open;
  DebugLeave('TASQLiteTable.SQLiteMasterChanged');
end;

procedure TASQLiteTable.InternalDelete;
var
  ErrMsg: pchar;
begin
  DebugEnter('TASQLiteTable.InternalDelete');
  if not Connection.FConnected then
    exit;
  if FAutoCommit then
    Connection.StartTransaction;

  SQLStr := '';
  CurrentRowId := FResult.GetRowId(FCurRec);
  FSQL.Clear;
  FSQL.Add('delete from ' + Tablename + ' where rowid=' + QuotedStr(IntToStr(CurrentRowId)));
  SQLStr := StringReplace(FSQL.Text, crlf, ' ', [rfReplaceAll, rfIgnoreCase]);
  Connection.SQLite_XExec(Connection.DBHandle, pchar(SQLStr),
    @ExecCallback, self, ErrMsg);
  if ErrMsg <> nil then
  begin
    ShowMessage(ErrMsg);
    Connection.SQLite_FreeMem(ErrMsg);
    Abort;
  end;

  inherited InternalDelete;

  if FAutoCommit then
  begin
    try
      Connection.Commit;
    except
      Connection.RollBack;
      raise;
    end;
  end;
  DebugLeave('TASQLiteTable.InternalDelete');
end;

procedure TASQLiteTable.InternalPost;
var
  i:      integer;
  ErrMsg: pchar;
  ThisDateFormat : string;

  // this function will return the fielvalue of an indicated fieldbyordinalnumber
  // if the fieldtype is tdatetime it is transfered to the right date notation as
  // indicated by jpierce.

  function GetFieldValue(FieldOrd : integer) : string;
  begin
    if FieldList[FieldOrd].DataType = ftDateTime then
       GetFieldValue := QuotedStr(FormatDateTime(ThisDateFormat,
              FieldByName(FieldList[FieldOrd].FieldName).AsDateTime))
    else
        GetFieldValue := QuotedStr(FieldByName(FieldList[FieldOrd].FieldName).AsString);
  end;

begin
  DebugEnter('TASQLiteTable.InternalPost');

// determine datetime style of dataset (if any)

  if FSQLiteDateFormat then
     ThisDateFormat := 'yyyy-mm-dd hh:nn:ss.zzz'
  else if (FTableDateFormat <> '') then
     ThisDateFormat := FTableDateFormat
  else
     ThisDateFormat := ShortDateFormat;

  if not Connection.FConnected then exit;
  if FAutoCommit then Connection.StartTransaction;

  if (State = dsEdit) and (FResult.Count > 0) then
  begin
    CurrentRowId := FResult.GetRowId(FCurRec);
    FSQL.Clear;
    FSQL.Add('update ' + Tablename + ' set ');
    SQLStr := '';
    for i := 0 to FieldList.Count - 1 do
       SQLStr := SQLStr + FieldList[i].FieldName + '=' +                       // jpierce
       GetFieldValue(i) + ',';        // jpierce

    SQLStr[Length(SQLStr)] := ' ';
    FSQL.Add(SQLStr);
    FSQL.Add(' where rowid=' + QuotedStr(IntToStr(CurrentRowId)));
    SQLStr := StringReplace(FSQL.Text, crlf, ' ', [rfReplaceAll, rfIgnoreCase]);
    Connection.SQLite_XExec(Connection.DBHandle, pchar(SQLStr),
      @ExecCallback, self, ErrMsg);
    if ErrMsg <> nil then
    begin
      ShowMessage(ErrMsg);
      Connection.SQLite_FreeMem(ErrMsg);
      Abort;
    end
    else
    begin
      inherited InternalPost; // rework internals
    end;
  end
  else
  begin
    { If inserting (or appending), increment the bookmark counter and
      store the data }
    FSQL.Clear;
    FSQL.Add('insert into ' + TableName + ' (');
    SQLStr := '';
    for i := 0 to FieldList.Count - 1 do
      SQLStr := SQLStr + FieldList[i].FieldName + ',';
    SQLStr[Length(SQLStr)] := ')';
    SQLStr := SQLStr + ' values (';
    FSQL.Add(SQLStr);
    SQLStr := '';

    if (FPrimaryAutoInc) and (FieldDefs[0].DataType = ftInteger) then
    begin
      SQLStr := SQLStr + 'null,';
      for i := 1 to FieldList.Count - 1 do
        SQLStr := SQLStr + GetFieldValue(i) + ',';
    end else begin
      for i := 0 to FieldList.Count - 1 do
        SQLStr := SQLStr + GetFieldValue(i) + ',';
    end;

    SQLStr[Length(SQLStr)] := ')';
    FSQL.Add(SQLStr);
    SQLStr := StringReplace(FSQL.Text, crlf, ' ', [rfReplaceAll, rfIgnoreCase]);
    Connection.SQLite_XExec(Connection.DBHandle, pchar(SQLStr),
      @ExecCallback, self, ErrMsg);

    if FPrimaryAutoInc then
      if FieldDefs[0].DataType = ftInteger then
        FieldByName(FieldList[0].FieldName).AsInteger :=
          Connection.SQLite_LastInsertRow(Connection.DBHandle);

    FLastError := ErrMsg;
    if ErrMsg <> nil then
    begin
      raise EDatabaseError.Create(ErrMsg);
      Connection.SQLite_FreeMem(ErrMsg);
      if FAutoCommit then
        Connection.RollBack;
      Abort;
    end
    else
    begin
      if FResult.Count = 0 then
        Inc(FCurrec);
      inherited InternalPost; // rework internals
    end;
  end;
  
  if FAutoCommit then
  begin
    try
      Connection.Commit;
    except
      Connection.RollBack;
      raise;
    end;
  end;
  DebugLeave('TASQLiteTable.InternalPost');
end;

// Blobfields in SQLite are in fact CLOB fields. However, since it is a large
// chunk of data for all types the ftBlob is used. Keep in mind that blobs are
// stored separately of TResult. Within the result structure only the memory
// handle of the blob is stored.

constructor TASQLiteBlobStream.Create(Field: TBlobField; Mode: TBlobStreamMode);
begin
//  inherited Create;
  FField := Field;
  FMode := Mode;
  FDataSet := FField.DataSet as TASQLiteBaseQuery;
  if Mode <> bmWrite then
     LoadBlobData;
end;

destructor TASQLiteBlobStream.Destroy;
begin
  DebugEnter('TASQLiteBlobStream.Destroy');
  if FModified then
    SaveBlobData;
  inherited Destroy;
end;

function TASQLiteBlobStream.Read(var Buffer; Count: Longint): Longint;
begin
  DebugEnter('ASQLiteBlobStream.Read');
  Result := inherited Read(Buffer, Count);
  FOpened := True;
end;

function TASQLiteBlobStream.Write(const Buffer; Count: Longint): Longint;
begin
  DebugEnter('ASQLiteBlobStream.Write');
  Result := inherited Write(Buffer, Count);
  FModified := True;
  FDataSet.SetModified(true);
end;

procedure TASQLiteBlobStream.LoadBlobData;
var
  Stream: TMemoryStream;
  Offset: Integer;
  RecBuffer: PChar;
begin
  DebugEnter('ASQLiteBlobStream.LoadBlobData');
  Self.Size := 0;
  FDataset.GetActiveBuffer(RecBuffer);

//  recbuffer := nil;

  if RecBuffer <> nil then
    begin
      Offset := FDataset.GetFieldOffset(FField.FieldNo);
      Move((RecBuffer + Offset)^, Pointer(Stream), sizeof(Pointer));
      Self.CopyFrom(Stream, 0);
    end;
  Position := 0;
end;

procedure TASQLiteBlobStream.SaveBlobData;
var
  Stream: TMemoryStream;
  Offset: Integer;
  RecBuffer: Pchar;
begin
  DebugEnter('ASQLiteBlobStream.SaveBlobData');
  FDataset.GetActiveBuffer(RecBuffer);
  if RecBuffer <> nil then
    begin
      Offset := FDataset.GetFieldOffset(FField.FieldNo);
      Move((RecBuffer + Offset)^, Pointer(Stream), sizeof(Pointer));
      Stream.Size := 0;
      Stream.CopyFrom(Self, 0);
      Stream.Position := 0;
    end;
end;

// Inline sql can be used to store sqlstatements outside of the pascal source.
// it prevents large 'sql.add' rows. Also it can be used to generate an in-memory
// database structure if needed

constructor TASQLiteInlineSQL.Create;
begin
 Inherited;
 FSQL := TStringList.Create;
end;

destructor TASQLiteInlineSQL.Destroy;
begin
 if Assigned(FSQL) then FSQL.Free;
 Inherited;
end;

procedure TASQLiteInlineSQL.SetSQL(const Value: TStrings);
begin
  if Assigned(FSQL) then
    FSQL.Assign(Value)
  else
    FSQL := Value;
end;

function TASQLiteInlineSQL.GetSQL: TStrings;
begin
  GetSQL := FSQL;
end;

// save resultset as text, html or xml. Depending on type the following
// will happen:
//
// text: all rows will be output, separated by the given separation symbol
// xml: all rows will be output, tags are the fieldnames
//      <queryname>
//              <rowid>
//                      <fieldid> fieldvalue </fieldid>
//                      ....
//
// html: a table will be generated with the given classnames (if available)

constructor TASQLiteOutput.Create;
begin
 Inherited;
 FOutput := TStringList.Create;
end;

destructor TASQLiteOutput.Destroy;
begin
 if Assigned(FOutput) then FOutput.Free;
 Inherited;
end;

procedure TASQLiteOutput.SetFActive(Active : boolean);
begin
 FActive := Active;
 if FActive = false then begin
 end else begin
   if Assigned(FDataSource) then begin
     if Assigned(FDataSource.DataSet) then begin
        Execute(FDataSource.DataSet);
     end else raise AsgError.Create('Missing Datasource.Dataset');
   end else raise AsgError.Create('Missing Datasource');
 end;
end;

procedure TASQLiteOutput.SetOutput(const Value: TStrings);
begin
  if Assigned(FOutput) then
    FOutput.Assign(Value)
  else
    FOutput := Value;
end;

function TASQLiteOutput.GetOutput : TStrings;
begin
  GetOutput := FOutput;
end;

procedure TASQLiteOutput.Notification(AComponent: TComponent; Operation: TOperation);
begin
{$IFDEF DEBUG_VERY_LOUD}
  DebugEnter('TASQLiteOutput.Notification');
{$ENDIF}
  if Assigned(AComponent) then begin
    if (Operation = opRemove) then begin
      if (AComponent is TDataSource) then begin
        if Assigned(FDataSource) then begin
          if TDataSource(AComponent) = FDataSource then
             FDataSource:= nil;
        end;
      end
    end;
  end;
  Inherited;
{$IFDEF DEBUG_VERY_LOUD}
  DebugLeave('TASQLiteDB.Notification');
{$ENDIF}
end;

procedure TASQLiteOutput.Execute(MyDataSet: TDataSet);
const eXML = 0;
      eHTML = 1;
      eTXT = 2;
var FType : integer;
    i     : integer;
    Line  : string;
    Sep   : string;
//    Indent : integer;
begin
 if Assigned(MyDataset) then begin
    if MyDataSet.Active = false then MyDataSet.Open;
    Output.Clear;
    FType := ETxt;
    Line := '';

    if CompareText(FOutputType[1],'X')=0 then begin
       FType := eXML;
       Line := Line + '<table id="'+MyDataSet.Name+'" CreationDate="'+DateToStr(Now)+'">'+#10;
    end else if CompareText(FOutputType[1],'H')=0 then begin
       FType := eHTML;
       Line := Line + '<html>'+#10+'<head>'+#10+
                      '<title>Table '+ MyDataSet.Name+'</title>'+#10+
                      '<meta name="generator" content="Aducom Software ASQLite">'+#10+
                      '<table class="'+FTableClass+'"cellpadding="0" cellspacing="0">'+#10+
                      '<tr>'+#10;
    end else if CompareText(FOutputType[1],'T')=0 then begin
       FType := eTXT;
    end;

    Sep := '';

    for i := 0 to MyDataSet.FieldDefs.Count - 1 do begin
       case FType of
         eXML :  begin
                 end;
         eHTML : begin
                  Line := Line + '<td>'+ MyDataSet.FieldDefs[i].Name+'</td>';
                 end;
         eTXT :  begin
                  Line := Line + Sep + MyDataSet.FieldDefs[i].Name;
                 end;
       end;
       Sep := FSeparator;
    end;

    Output.Add(Line); Line := ''; Sep := '';
    MyDataSet.First;

//    Indent := 0;
    while not MyDataSet.Eof do begin

      case FType of
       eXML : Line := Line + '   <record>'+#10;
       eHTML : Line := Line +'<tr>'+#10;
      end;

      for i := 0 to MyDataSet.FieldDefs.Count - 1 do begin
       case FType of
         eXML :  begin
                  Line := Line + '      <'+MyDataSet.FieldDefs[i].Name+'>'+
                                 MyDataSet.FieldByName(MyDataSet.FieldDefs[i].Name).AsString+
                                 '</'+MyDataSet.FieldDefs[i].Name+'>'+#10;
                 end;
         eHTML : begin
                  Line := Line + '<td>'+ MyDataSet.FieldByName(MyDataSet.FieldDefs[i].Name).AsString+'</td>';
                 end;
         eTXT :  begin
                  Line := Line + Sep + MyDataSet.FieldByName(MyDataSet.FieldDefs[i].Name).AsString;
                 end;
       end;
       Sep := FSeparator;
      end;

      case FType of
       eXML : Line := Line + '   </record>'+#10;
       eHTML : Line := Line + '</tr>'+#10;
      end;

      Output.Add(Line); Line := ''; Sep := '';
      MyDataSet.Next;
    end;

   case FType of
    eXML : Line := Line + '   </table>'+#10;
    eHTML : Line := Line + '</table>'+#10+'</body>'+#10+'</html>'+#10;
   end;
   Output.Add(Line); 
  end;
end;

end.
