
{                                                                              }
{ IB_Components                                                                }
{                                                                              }

{***************************************************************}
{                                                               }
{          IB Objects Components and Resources Library          }
{                                                               }
{          Copyright (C) 1996-2007 Jason Wharton                }
{          and Computer Programming Solutions, Mesa, AZ         }
{                                                               }
{  This source code unit is released under the terms of the     }
{  CPS Trustware License.                                       }
{  It may be distributed or deployed as source code or in       }
{  compiled form only in compliance with the terms and          }
{  conditions of the CPS Trustware License which may be         )
{  examined at http://www.ibobjects.com/ibo_trustware.html      )
{                                                               }
{***************************************************************}

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{                                                                              }
{  Magnus Johansson <trans-x@algonet.se>                                       }
{  20-Nov-2004                                                                 }
{     Added LoginAttemptsDone to reflect how many attempts to login were done. }
{     Changed LoginAttempts so that 0 allowed infinite attempts.               } 
{                                                                              }
{  Jason Wharton <jwharton@ibobjects.com>                                      }
{  18-Aug-2004                                                                 }
{     I eliminated the record re-fetch operation on an Insert or Edit when a   }
{     blob column is involved if there are no KeyLinks defined. This avoids    }
{     the buffer getting wiped out because it is unable to fetch without       }
{     knowing what the KeyLinks are.                                           }
{                                                                              }
{  Steven Beames <s.beames@griffith.edu.au>                                    }
{  6-Mar-2003                                                                  }
{     Changed csLoading to csReading in functions TIB_Statement.SysPrepare     }
{     and TIB_Dataset.SysOpen to allow a custom component to initialise owned  }
{     TIB components in its Loaded() method. (Based on a suggestion by         }
{     Mahris Vasilevskis)                                                      }
{                                                                              }
{  Jason Wharton <jwharton@ibobjects.com>                                      }
{  08-Nov-2003                                                                 }
{     I fixed the handling of input parameters such that it will respect an    }
{     externally supplied value using the Bound property of TParam or          }
{     TIB_Column.                                                              }
{                                                                              }
{  Jason Wharton <jwharton@ibobjects.com>                                      }
{  11/07/2003                                                                  }
{     I the callback event handle a request to abort fetching work more        }
{     robust in regard to nested fetching requests. Now it will cause all      }
{     levels prior to the request to abort to recognize the request. Before,   }
{     it was getting mixed up since only a toggle was managing this state.     }
{     It now uses an integer counter that will properly roll-over.             }
{                                                                              }
{  Jason Wharton <jwharton@ibobjects.com>                                      }
{  11/06/2003                                                                  }
{     I made the RPL$SYNC_ID column show up as a COMPUTED column.              }
{                                                                              }
{     Added an exception on the BeforeEdit buffer synchro event if it is       }
{     unable to lock the record. Before it would just ignore the condition.    }
{                                                                              }
{     Fixed parsing to properly handle the FIRST and SKIP tokens in a SELECT   }
{     statement. There were problems with the keylinks being setup as DB_KEY   }
{     columns as well as various odd cases that tripped up proper              }
{     identification of these clauses.                                         }
{            Copyright (C) 2004 Jason Wharton                                  }
{                                                                              }
{  Jason Wharton <jwharton@ibobjects.com>                                      }
{  11/03/2003                                                                  }
{     I added a new way of dealing with the parameter ordering situation       }
{     In Firebird 1.5 the order of the input parameters was changed so that    }
{     the are now in byte order in the SQL string. Before there was a weird    }
{     priority such that the order was more difficult. This new way of         }
{     ordering input parameters was made the default. IBO will now try and use }
{     the method the server is configured to use. I submit a statement to the  }
{     server and examine the order the parameters come back in and then use    }
{     them accordingly. It is also possible to hard-code which ever way you    }
{     want to save the cost of sending this test query for each new connect.   }
{            Copyright (C) 2004 Jason Wharton                                  }
{                                                                              }
{  Jason Wharton <jwharton@ibobjects.com>                                      }
{  10/28/2003                                                                  }
{     I took out the call to RoundDown() when getting values out of the        }
{     column buffers. There was a problem with interpretation of the floating  }
{     point value for certain values. It has to do with the differences        }
{     between the double and extended types.                                   }
{            Copyright (C) 2004 Jason Wharton                                  }
{                                                                              }
{  Jason Wharton <jwharton@ibobjects.com>                                      }
{  10/??/2003                                                                  }
{     I added the CancelQuery method for those using IB 7.x that would like to }
{     be able to asynchronously cancel the currently running query.            }
{                                                                              }
{     I added a check on the record count so that if it is a select procedure  }
{     it will not parse together a new COUNT( * ) statement but instead make   }
{     sure to just fetch all records for the dataset and return the row count. }
{                                                                              }
{     I made it so that if NULL was declared as the default value for a column }
{     on the server that it will make it be properly applied on the client if  }
{     GetServerDefaults is applied.                                            }
{            Copyright (C) 2004 Jason Wharton                                  }
{                                                                              }
{  Christopher Hart <cjh@profiletechgroup.com>                                 }
{  11/24/2003                                                                  }
{     Updated support for Boolean Assignment.  Was a problem with TIB_Checkbox }
{     and display of Boolean values.                                           }
{     Updates applied to TIB_COLUMN.GetAsVariant and TIB_COLUMN.SetAsVariant.  }
{                                                                              }
{------------------------------------------------------------------------------}
{  Place your credits and information in the top of the section                }
{  below with any other descriptions or explanations desired.                  }
{------------------------------------------------------------------------------}
{                                                                              }
{                                                                              }
{******************************************************************************}

{$INCLUDE IB_Directives.inc}

{: This unit contains the foundational IB Native Core and Access components.}
unit
  IB_Components;

interface

uses
  SysUtils, Classes, Forms, Windows, Messages, Graphics, Dialogs,

{$ifdef IBO_VCL60_OR_GREATER}
  Variants, MaskUtils,
{$else}
  Mask,
{$endif}
  IB_MaskInterface,

  IB_Constants,
  IB_Header,
  IB_Session,
  IB_NodeList;
//IB_Monitor
//IB_SessionProps
//IB_ConnectionSource
//IB_TransactionSource
//IB_StatementSource
//IB_FieldDataLink
//IB_Process
//IB_DataScan
//IB_DataPump
//IB_SyncCursor
//IB_Controls
//IB_LocateEdit
//IB_ParamEdit
//IB_SearchEdit
//IB_SearchPanel
//IB_IncSearch
//IB_StatementGrid
//IB_CursorGrid
//IB_Grid
//IB_CGrid
//IB_DatasetBar
//IB_UpdateBar
//IB_SearchBar
//IB_NavigationBar
//IB_TransactionBar
//IB_StatementBar
//IB_Events
//IB_Export
//IB_Schema
//IB_Script
//IB_StoredProc
//IB_Dialogs
//IB_Ledger
//IB_Editors
//IBODatasetEditors
//IBODataset
//IBWeb
//IPIBDataset
//IB_Registration
//Db
//DbTables

const
  ssSQL = 0;
  ssSelect = 1;
  ssFrom = 2;
  ssWhere = 3;
  ssGroup = 4;
  ssHaving = 5;
  ssUnion = 6;
  ssPlan = 7;
  ssOrder = 8;
  ssForUpdate = 9;
// For loop controls
  ssLowSQLSection = 0;
  ssHighSQLSection = 9;

type
  TIB_Session = class;
  TIB_Component = class;
  TIB_SchemaCache = class;
  TIB_Connection = class;
  TIB_ConnectionLink = class;
  TIB_Transaction = class;
  TIB_TransactionInternal = class;
  TIB_TransactionLink = class;
  TIB_Statement = class;
  TIB_StatementLink = class;
  TIB_Dataset = class;
  TIB_BDataset = class;
  TIB_FilterCursor = class;
  TIB_UpdateSQL = class;
  TIB_DataSource = class;
  TIB_DataLink = class;
  TIB_KeyDataLink = class;
  TIB_MasterDataLink = class;
  TIB_Row = class;
  TIB_Column = class;
  TIB_ColumnBlob = class;
  TIB_ColumnArray = class;
  TIB_ColumnRef = ^TIB_Column;
// C++Builder does not issue the pre-declarations of types (as issued above)
// until it decides the declaration is required.  However TIB_Transaction
// uses TIB_BDataset in its ApplyUpdates procedure as an array parameter
// which, it appears, C++Builder does not recognise properly for the purposes
// of issuing predeclarations.  So by creating this dummy type declaration I
// force C++Builder to see that the pre-declaration is required.  I could have
// just used HPPEMIT directives, but I think this dummy operation should
// provide better long-term compatibility with future releases, and will be
// totally harmless if the problem is ever fixed.
  TIB_BDatasetRef = ^TIB_BDataset;

{                                                                              }
{ IBA_StringList                                                               }
{                                                                              }

{: This class is used heavily throughout IB Objects to store information that
is keyed to a column name. Generally, it contains a list of strings, each
comprising a column name connected by an '=' symbol to a value associated
with it. Such a string is referred to as a "link entry".
<br><br>
It is possible for multiple values to be associated with a column in a single
link entry. They are accessed by using link entry params. It can be thought of
as parallel to command-line parameters, where there are multiple settings on one
line of text.
<br><br>
This storage technique makes it very easy to preserve settings between sessions,
to maintain user preferences.  Examine the code in the IB_SQL application to see
how settings are streamed between the IB_Stringlists and the system registry
from session to session.
<br><br>
IBF_Query should be the best example in this project. Be sure to look through
IBF_Base to see how a foundation for streaming has been built. You will find
the WriteStrings() and ReadStrings() methods very useful to put and get string
list data in/out of the system registry.
<br><br>
Some stringlist properties that are referred to heavily are mirrored to
variables for direct reading (via properties) without the overhead of parsing
the link entries for an individual entry each time it is wanted. It may appear
that duplication exists in the component interface but, in fact, IBO keeps the
variables, including those which are writable, fully synchronized.
<br><br>
The BNF of the syntax for the link entries are as follows:
<br><br>
Since I don't exactly recall the proper syntax for this I'll give the key
that I use:
<br>
<br>< > denotes a logical item of the BNF
<br>[]  denotes an optional portion of an item
<br>( | ) denotes that only one of the items separated by the | are valid
<br>''  denote literal characters
<font face="Courier New">
<ul>
<li>< link entry >      ::= < link name >< equal sign >< link value >
<li>< link name >       ::= [< table name >< period >]< column name >
<li>< link value >      ::= (< string literal >|< param list >)
<li>< param list >      ::= < param name >[=< literal >]
                                             [< param delimiter >< param list >]
<li>< literal >         ::= < char list item >[< literal >]
<li>< char list item >  ::= (< char >|< char pair >)
<li>< char pair >       ::= ( '\=' | '\;' | '\\' | '\n' | '\0' )
<li>< char >            ::= < any character except 2nd char of char pair >
<li>< param delimiter > ::= ';'
<li>< equal sign >      ::= '='
<li>< period >          ::= '.'
<li>< table name >      ::= < IB identifier >
<li>< column name >     ::= < IB identifier >
<li>< param name >      ::= < IB identifier >
<li>< IB identifier >   ::= < string with no spaces or funky characters, etc. >
</ul>
</font>
<br><br>
The special characters are used in order to prevent the stringlist from
becoming corrupted and to simplify parsing of each string list item.
<br><br>
Don't confuse the notion of "param" above with the input parameters of a
SQL statement. They are used in a different context here.  Certain link entries
can take a refining value (or a comma-separated list) as "parameters".  As an
example, a ColumnAttributes link entry for a Boolean column might look like
this:
<br><br>
MYCOLUMN=BOOLEAN='T','F'
<br><br>
Here, the values which IBO is to recognize as True and False are
parameters to this link entry.
<br><br>
When using the properties to interface with the contents of the stringlist
the special characters are all converted for you.  It is only if you access the
raw contents of the list directly in your own code that you will need to take
the special characters into consideration.
<br><br>
Please take care with punctuating these link entries. In general, avoid adding
extra spaces since the parsing may not be so forgiving as to allow them.  In
particular, do not leave spaces either side of the "=" symbol in a link entry.}
TIB_StringList = class(TStringList)
private
  FAllowBlankLines: boolean;
  FRemovingBlankLines: boolean;
  FUpdateState: boolean;
  FQuickSortParam: string;
  FQuickSortNumeric: boolean;
  function GetIndexName( Index: integer ): string;
  function GetIndexNameRel( Index: integer ): string;
  function GetIndexNameCol( Index: integer ): string;
  procedure SetIndexName( Index: integer; AValue: string );
  function GetIndexValue( Index: integer ): string;
  procedure SetIndexValue( Index: integer; AValue: string );
  function GetLinkIndex( ALink: string ): integer;
  function GetLinkValue( ALink: string ): string;
  procedure SetLinkValue( ALink: string; AValue: string );
  function GetIndexParamValue( Index: integer; AParam: string ): string;
  procedure SetIndexParamValue( Index: integer; AParam, AValue: string );
  function GetLinkParamValue( ALink, AParam: string ): string;
  procedure SetLinkParamValue( ALink, AParam: string; AValue: string );
  function GetLinkParamIsSet( ALink, AParam: string ): boolean;
  procedure SetLinkParamIsSet( ALink, AParam: string; AValue: boolean );
  function GetIndexParamIsSet( AIndex: integer; AParam: string ): boolean;
  procedure SetIndexParamIsSet( AIndex: integer;
                                AParam: string; AValue: boolean );
  procedure SetAllowBlankLines( AValue: boolean );
  function GetSafeCommaText: string;
  procedure SetSafeCommaText( AValue: string );
  procedure RemoveBlankLines;
protected
  procedure Changing; override;
  procedure Changed; override;
  procedure SetUpdateState(Updating: Boolean); override;
  procedure Put(Index: Integer; const S: string); override;
  property RemovingBlankLines: boolean read FRemovingBlankLines;
  property UpdateState: boolean read FUpdateState;
  procedure SysParamQuickSort( L, R: Integer );
  function SysStringListParamSort( Index1: integer; Index2: string ): Integer;
public
  constructor Create; virtual;

{: Cover up a bug in Delphi 2.}
  procedure SaveToStream(Stream: TStream); override;
  procedure RemoveBlankEntries;
{: This will allow you to order the entries in the string list according to a
particular parameter in the link entries.}
  procedure ParamQuickSort( AParam: string; IsNumeric: boolean );
{: Hook in needed functionality to this sub class by overriding.}
  function Add(const S: string): Integer; override;
{: Allow checking for validity of returned value.  ie. A blank/null return
string could mean that the attribute was defined as blank OR it could
mean that the value was not found.  Use the IsSet parameter to check
whether the attribute was truly found. }
  function GetLinkParamValueEx(     ALink, AParam: string;
                                var IsSet: boolean ): string;
{: This property is used to trim out blank lines from the string list.}
  property AllowBlankLines: boolean read FAllowBlankLines
                                    write SetAllowBlankLines;
{: Just what it implies. Safe from what? It has to do with the SQL Dialect 3
capabilities. Because a comma is a valid part of a delimited identifier the
comma text feature needs to be sensitive to that.}
  property SafeCommaText: string read GetSafeCommaText write SetSafeCommaText;
{: If Sorted this will perform an optimized search for the Index of the name.}
  function FindIndex( const AName: string; var AIndex: integer ): boolean;
{: If Sorted this will perform an optimized search for the Value of the name.}
  function FindValue( const AName: string; var AValue: string ): boolean;
{: Indexed reference to the name portion of the link entry.}
  property IndexNames[ Index: integer ]: string read GetIndexName
                                                write SetIndexName;
{: Indexed reference to the relation of the name portion of the link entry.}
  property IndexNamesRel[ Index: integer ]: string read GetIndexNameRel;
{: Indexed reference to the column of the name portion of the link entry.}
  property IndexNamesCol[ Index: integer ]: string read GetIndexNameCol;
{: Indexed reference to the value portion of the link entry.}
  property IndexValues[ Index: integer ]: string read GetIndexValue
                                                 write SetIndexValue;
{: Value of a parameter from the value portions of a link entry that
corresponds with the given index reference.}
  property IndexParamValue[ Index: integer;
                            AParam: string ]: string read GetIndexParamValue
                                                     write SetIndexParamValue;
{: Indicates whether, in the value portions of the link entry having the given
index reference, a value exists corresponding to the given parameter.}
  property IndexParamIsSet[ Index: integer;
                            AParam: string ]: boolean read GetIndexParamIsSet
                                                      write SetIndexParamIsSet;
{: Index of the Link in the list of link entries.}
  property LinkIndex[ ALink: string ]: integer read GetLinkIndex;
{: Value portion associated with the Link in the list of link entries.}
  property LinkValues[ ALink: string ]: string read GetLinkValue
                                               write SetLinkValue;
{: Value of a parameter from the value portions of a link entry that
corresponds with the given link.}
  property LinkParamValue[ ALink,
                           AParam: string ]: string read GetLinkParamValue
                                                    write SetLinkParamValue;
{: Indicates whether, in the value portions of the link entry that corresponds
to the given link, a value exists.}
  property LinkParamIsSet[ ALink,
                           AParam: string ]: boolean read GetLinkParamIsSet
                                                     write SetLinkParamIsSet;
end;

{: This is used internally by various properties in IBO components. This class
uses default values appropriate for what each property needs to accomplish.}
TIB_StringProperty = class(TIB_StringList)
public
  constructor Create; override;
end;

{: This is used internally by various properties in IBO components. This class
uses default values appropriate for what each property needs to accomplish.}
TIB_StringCache = class(TIB_StringList)
public
  constructor Create; override;
end;

// IBA_StringList.IMP

{                                                                              }
{ IBA_BlobStream                                                               }
{                                                                              }

{: This class is used to access and modify the contents of BLOB columns.
<br><br>
In general this is a class that is used behind the scenes. It should not
be necessary to explicitly create a direct instance of this class.
<br><br>
@seeAlso <See Class="TIB_Statement"> CreateBlobStream() method for more
information.
<br><br>}
TIB_BlobStream = class(TStream)
private
  FRow: TIB_Row;
  FPSQLVAR: XSQLVAR;
  FBlobNode: PIB_BlobNode;
  FPBlobHead: PPIB_BlobNode;
  FMode: TIB_BlobStreamMode;
  FColumn: TIB_Column;
  FFieldNo: integer;
  FPosition: longint;
  FModified: boolean;
  procedure Initialize;
  procedure SetModified;
public
  constructor Create( ARow: TIB_Row;
                      ASQLVAR: XSQLVAR;
                      AFieldNo: integer;
                      ABlobNode: PIB_BlobNode;
                      APBlobHead: PPIB_BlobNode;
                      AMode: TIB_BlobStreamMode ); virtual;
  constructor CreateForColumn( AColumn: TIB_Column;
                               AMode: TIB_BlobStreamMode );
  destructor Destroy; override;
  function Read( var Buffer; Count: longint ): longint; override;
  function Seek( Offset: longint; Origin: Word ): longint; override;
  function Write( const Buffer; Count: longint ): longint; override;
  property BlobNode: PIB_BlobNode read FBlobNode;
  property Mode: TIB_BlobStreamMode read FMode;
  property Modified: boolean read FModified;
  property PBlobHead: PPIB_BlobNode read FPBlobHead;
  property Position: longint read FPosition;
  property Row: TIB_Row read FRow;
  property SQLVAR: XSQLVAR read FPSQLVar;
end;

// IBA_Stream.IMP

{                                                                              }
{ IB_Session                                                                   }
{                                                                              }

{: Event type used to process the session timer notification.}
TIB_SessionTimerEvent = procedure (     Sender: TComponent;
                                    var IsDone,
                                        IsWaiting,
                                        Terminate: boolean ) of object;
{: Event type defined to TIB_Session and TIB_SessionProps but used by
TIB_DataLink to override retrieval of colors used in ColorScheme information.
TIB_DataLink will already have set the standard color scheme color before this
is called, simply override where necessary or leave as provided by standard
processing. }
TIB_GetDataLinkColorEvent = procedure(     Sender: TIB_Session;
                                           DataLink: TIB_DataLink;
                                       var StateColor: TColor ) of object;

{ : This class is used in order to make the cursor behave more appropriately
without the need to pepper code with the BeginBusy() and EndBusy calls.
<br><br>
It will attempt to buffer out the flickering effect when a process does many
consecutive actions repeatedly.}
{
TIB_SessionCursorThread = class( TThread )
private
  FSession: TIB_Session;
  FExecuting: boolean;
  FSleepTicks: DWORD;
  FBusyOnTicks: DWORD;
  FYieldOnTicks: DWORD;
  FBusyOffTicks: DWORD;
  FYieldOffTicks: DWORD;
protected
  procedure Execute; override;
  procedure DoTerminate; override;
public
  constructor Create( ASession: TIB_Session );
  destructor Destroy; override;
end;
}

{: This class serves as a critical foundation for IB Objects components. If you
are not doing anything with multi-threading then it will not be necessary to
instantiate this component yourself, since the system provides a default session
for the main thread automatically.
<br><br>
This component can be used on a data module to isolate all data access
components together in an isolated session.  This is especially suitable for
an ISAPI web module that will have a web-module instance cached and used in a
multi-threading environment.<br><br>
IMPORTANT !!  When doing this, make sure that it is the first in the module's
creation order and that ONLY ONE is placed on a module.
<br><br>
It is possible to override the default component with an alternative component
session if you should need to have two threads accessing the same session. This
should really only be necessary when binding to DLLs that contain IB Objects
components that should act as though they were part of the main process.
<br><br>
See the various properties and methods for more information. Pay particularly
close attention to the TimerInterval property.}
TIB_Session = class(TIB_SessionBase)
private
{ General storage }
  FIsDefaultSession: boolean;
  FDefaultConnection: TIB_Connection;
  FFocusedConnection: TIB_Connection;
  FFocusedTransaction: TIB_Transaction;
  FFocusedDataset: TIB_Dataset;
  FFocusedDataSource: TIB_DataSource;
  FStoreActive: boolean;
  FBusyLevel: integer;
  FYieldLevel: integer;
  FLockCursorLevel: integer;
  FUseCursor: boolean;
  FBusyCursor: smallint;
  FYieldCursor: smallint;
  FOldCursor: smallint;
  FAllowDefaultConnection: boolean;
  FAllowDefaultTransaction: boolean;
  FCursorNameSeed: cardinal;
  cntComponents: integer;
  FCallbackFreezeLevel: integer;
{ Event Storage }
  FOnBeginBusy: TNotifyEvent;
  FOnEndBusy: TNotifyEvent;
  FOnBeginYield: TNotifyEvent;
  FOnEndYield: TNotifyEvent;
  FOnSessionTimer: TIB_SessionTimerEvent;
  FOnWakeup: TNotifyEvent;
{ Timer Stuff }
  FTimerCS: TRTLCriticalSection;
  FTimerItems: TList;
  FTimerItemIndex: integer;
  FTimerHandle: HWND;
  FTimerInterval: cardinal;
  FTimerIsBusy: boolean;
  FTimerIsDone: boolean;
  FTimerIsWaiting: boolean;
  FTimerTerminated: boolean;
  procedure WndProc( var AMsg: TMessage );
  procedure SetTimerInterval( AValue: cardinal );
  function GetWakeup: TNotifyEvent;
  procedure SetWakeup( AValue: TNotifyEvent );
{ System Methods }
  function GetIsBusy: boolean;
  function GetIsYielding: boolean;
  procedure BeginBusy( Yield: boolean );
  procedure EndBusy;
{ Property access methods }
  procedure SetFocusedConnection( C: TIB_Connection );
  procedure SetFocusedTransaction( T: TIB_Transaction );
  procedure SetFocusedDataset( D: TIB_Dataset );
  procedure SetFocusedDataSource( DS: TIB_DataSource );
private
  FEditingColor: TColor;
  FInsertingColor: TColor;
  FDeletingColor: TColor;
  FSearchingColor: TColor;
  FReadOnlyColor: TColor;
  FSelectedColor: TColor;
  FInvalidColor: TColor;
  FPreparedColor: TColor;
  FBrowsingColor: TColor;
  FOnGetDataLinkColor: TIB_GetDataLinkColorEvent;
protected
  FInDa: PXSQLDA;
  FOutDa: PXSQLDA;
  FInCnt: integer;
  FOutCnt: integer;
protected
  procedure Loaded; override;
  procedure Notification( AComponent: TComponent;
                          Operation: TOperation); override;
  procedure DoGetDataLinkColor(     DataLink: TIB_DataLink;
                                var FieldColor: TColor );
protected
{ Event Dispatch Methods }
  procedure DoBeginBusy;
  procedure DoEndBusy;
  procedure DoBeginYield;
  procedure DoEndYield;
  procedure DoWakeUp( Sender: TObject );
public
{ Focusing storage }
  Session_Components: TList;
  Session_Connections: TList;
  Session_ConnectionLinks: TList;
  Session_Transactions: TList;
  Session_TransactionLinks: TList;
  Session_Statements: TList;
  Session_Datasets: TList;
  Session_DataSources: TList;
  Session_DataLinks: TList;
{ Inherited Events }
  procedure DoHandleError(       Sender: TObject;
                           const errcode: longint;
                                 ErrorMessage,
                                 ErrorCodes: TStringList;
                           const SQLCODE: longint;
                                 SQLMessage,
                                 SQL: TStringList;
                             var RaiseException: boolean); override;
{: Introduced this method to centalize the handling of interaction with the
Application object.}
  function DoAppCallback: boolean;override;
{: Allow an external source to trigger a timer-based mechanism.
<br><br>
This also will activate all passive TIB_Process components, check for passive
event alerters and check the transaction OAT status, taking actions to advance
it if possible.
<br><br>
Each call to this routine will execute one item in the array of queued items.
In order to ensure all pending items are taken care of call, this method
repeatedly until it returns True for the IsDone parameter.
<br><br>
although it
Even if IsDone becomes True and it has done all it can do, it may be useful to
check also the IsWaiting parameter to see if some items may actually be waiting
on other things to happen before the method can finish up what it needs to do.
<br><br>
The Application.OnIdle event handler is a very appropriate place to use this
method, where it can hook into IBO's utilization of idle cpu cycles to process
all of its passive work items. <br><br>
The Session.WakeUp method has been designed to enable an asynchronous event to
wake it up, even if the application goes idle, and let the processing of items
proceed.
The TIB_Events component has the capability to post an automatic signal to the
idle CPU telling it to wake up and go to work again. It is not necessary to use
the internal TTimer to check for events when using ProcessPassiveTasks and the
idle CPU cycles.}
  procedure ProcessPassiveTasks( var IsDone, IsWaiting, Terminated: boolean );
{ Focusing methods }
  procedure AnnounceConnection;
  procedure AnnounceTransaction;
  procedure AnnounceDataSource;
{ Inherited methods }
  constructor Create( AOwner: TComponent ); override;
  destructor Destroy; override;

{ Methods }

{: This procedure is designed to retrieve the BDE settings for an alias that
exists in a valid IDAPI configuration file.}
  procedure GetAliasParams( const AliasName: String; List: TStrings );
{: This method will return a reference to a TIB_Connection component based on
its DataBaseName property.  It returns Nil if a matching Databasename is not
found.}
  function GetConnectionByName( const ADBName: string ): TIB_Connection;
{: Used to temporarily disable the cursor twiddling.}
  procedure BeginLockCursor;
{: Used to re-enable the cursor twiddling after it has been disabled with a call
to BeginLockCursor.}
  procedure EndLockCursor;
{: Call this to have an object evaluated to see if it belongs in the timer
notification list, where it would stop or start receiving notifications
appropriately.
<br><br>
Under most conditions this is called automatically for you.}
  procedure ResetTimerNotification( AComponent: TIB_Component );
{: This begins a nested bracket during which all callbacks will be suspended.}
  procedure BeginCallbackFreeze;
{: This ends a nested bracket during which callbacks have been suspended.}
  procedure EndCallbackFreeze;

{ Properties }

{: Indicates the nested level of processes pending cancellation or completion.}
  property BusyLevel: integer read FBusyLevel;
{: Indicates whether callbacks have been frozen.
<br><br>
If it returns Zero, callbacks will be performed if CallbackInc is not -1.
<br><br>
See also the BeginCallbackFreeze and EndCallbackFreeze methods.}
  property CallbackFreezeLevel: integer read FCallbackFreezeLevel;
{: Level to which the cursor has been locked.}
  property LockCursorLevel: integer read FLockCursorLevel;
{: Indicates the nested level of processes pending cancellation or
completion that have been started since the first call to BeginBusy() was
made with Yield set True.}
  property YieldLevel: integer read FYieldLevel;
{: Indicates whether the IB_Session is currently engaged in a process.
<br><br>
It is very important to check this property when a form or the application is
about to be closed.  All pending processes that are running should be properly
terminated before allowing the components that are in an active process to be
destroyed.
<br><br>
It may also be very useful to test this property in order to avoid starting
up a subsequent process that may conflict with a current process.}
  property IsBusy: boolean read GetIsBusy;
{: Indicates whether the IB_Session is currently in a process that is yielding.
<br><br>
It can be very useful to check this property in a form's CloseQuery event, in
case there could be a process running when the form close button is pressed.
This way, the user could be prompted to wait or cancel the process and try
closing the form again.
<br><br>
It could also be used to make sure that an export process will not be started if
there is is one running already.  Similarly with reports, since reporting tools
like Report Printer Pro allow the user to continue working with the interface
while the report is running on the main thread.
<br><br>
The UI activity is all handled in "forked" processes originating from the
Application.ProcessMessages call.  If Application.ProcessMessages is called
between each n rows exported, fetched, reported on, etc., this is what I call a
Yielding situation. The export, report, etc. process yields some processor
cycles to the UI in order to process a button click or some other form of message
 in a "forked process".}
  property IsYielding: boolean read GetIsYielding;
{: Determines the cursor that will appear on the screen when a process is
active and there is no Yield indication.
<br><br>
The UseCursor property must be True for this to happen.}
  property BusyCursor: smallint read FBusyCursor
                                write FBusyCursor
                                default -17; // crSQLWait
{: Determines the cursor that will appear on the screen when a process is
active and there is a Yield indication.  See IsYielding for a description of
what is considered a Yielding situation.
<br><br>
The UseCursor property must be true for this to happen.}
  property YieldCursor: smallint read FYieldCursor
                                 write FYieldCursor
                                 default -19; // crYield
{: This property indicates whether the session is currently processing a timer
notification.}
  property TimerIsBusy: boolean read FTimerIsBusy;
{: Item in the TimerItems list that is currently receiving or due for attention.}
  property TimerItemIndex: integer read FTimerItemIndex;
{: The list of TIB_Component objects that are included in the timer list.}
  property TimerItems: TList read FTimerItems;

{ Class Methods }

{: This class function is used to return which TIB_Session is being used for the
current thread or process. It is a critical foundation upon which IB Objects
depends.
<br><br>
By default, each new thread will automatically generate its own component
session. So, if you are doing multi-threaded database access just make sure
that each new thread has its own connection, transaction, etc. and that it
does not access any resource of other threads. You do not need have a visible
TSession in your Datamodule, as the VCL requires. IB Objects handles all of
this for you automatically.
<br><br>
It is a different story altogether if you need multiple threads active in
the same component session. It will be up to you as a developer to properly
use multi-threading features such as Critical Sections, Mutexes, etc. in
order to prevent multi-threading corruptions.
<br><br>
It is possible for multiple threads to share the same default component session
by using the SetAlternateSession() before any session-dependent components are
created. I have not yet attempted to do such a thing so take careful attention
and don't be surprised if it is a can of worms. I think it would be an
indication that something is wrong with your program design if it compelled you
to resort to this.
<br><br>
Because this depends on thread local storage members, its behavior could be
very difficult to trace and follow.}
{$IFDEF ALLOW_DEFAULT_SESSION}
  class function DefaultSession: TIB_Session;
{$ENDIF}
{: This class method is used to make two processes share a common default
component session. This can be used in order to make DLL instances share the
same component session as the main EXE that is calling into the DLL.
<br><br>
Without this, the global focusing mechanisms would not behave properly because
they all operate within the context of a single component session.  The
assumption is that all user interface manipulations should be done in the
main process and not in sub-processes.
<br><br>
Any DLL containing IB Objects code that will interact with IB Objects code in
the main process should export a procedure which will give it the DefaultSession
of the main process.  The DLL can set this as its AlternateSession. This will
make them all operate within the context of the very same component session.}
{$IFDEF ALLOW_ALT_SESSION}
  class procedure SetAlternateSession( ASession:TIB_Session );
{$ENDIF}

public

{: Each session has a global focusing system where data aware controls
announce to the session when they become focused. Beginning with the DataLink
objects they announce their DataSource, which then announces its Dataset and
then the Dataset announces its IB_Transaction and IB_Connection.
<br><br>
This property reflects the IB_Connection or IB_Database that is currently
the focused instance for the session.}
  property FocusedConnection: TIB_Connection read FFocusedConnection
                                             write SetFocusedConnection;
{: Each session has a global focusing system where data aware controls
announce to the session when they become focused. Beginning with the DataLink
objects they announce their DataSource, which then announces its Dataset and
then the Dataset announces its IB_Transaction and IB_Connection.
<br><br>
This property reflects the IB_Transaction that is currently the focused instance
for the session.}
  property FocusedTransaction: TIB_Transaction read FFocusedTransaction
                                               write SetFocusedTransaction;
{: Each session has a global focusing system where data aware controls
announce to the session when they become focused. Beginning with the DataLink
objects they announce their DataSource, which then announces its Dataset and
then the Dataset announces its IB_Transaction and IB_Connection.
<br><br>
This property reflects the IB_Cursor or IB_Query that is currently the focused
instance for the session.}
  property FocusedDataset: TIB_Dataset read FFocusedDataset
                                       write SetFocusedDataset;
{: Each session has a global focusing system where data aware controls
announce to the session when they become focused. Beginning with the DataLink
objects they announce their DataSource, which then announces its Dataset and
then the Dataset announces its IB_Transaction and IB_Connection.
<br><br>
This property reflects the IB_DataSource that is currently the focused instance
for the session.}
  property FocusedDataSource: TIB_DataSource read FFocusedDataSource
                                             write SetFocusedDataSource;

published

{: This property determines whether IBO will permit the default connection for
the session to be automatically assigned to a statement or dataset that does not
have its IB_Connection property explicitly assigned.}
  property AllowDefaultConnection: boolean read FAllowDefaultConnection
                                           write FAllowDefaultConnection;
{: This property determines whether IBO will permit a default internal
transaction to be automatically assigned to a statement or dataset that does not
have its IB_Transaction property explicitly assigned.}
  property AllowDefaultTransaction: boolean read FAllowDefaultTransaction
                                            write FAllowDefaultTransaction;
{: A statement or dataset or other connection-dependent component will attempt
to reference this DefaultConnection upon demand if it does not have an
IB_Connection or IB_Database component assigned then it .
<br><br>
By default, the first TIB_Connection instance becomes the DefaultConnection
for the component session.  TIB_Database and TIBODatabase objects are
considered as instances of TIB_Connection, since they are descendants of
TIB_Connection.}
  property DefaultConnection: TIB_Connection read FDefaultConnection
                                             write FDefaultConnection;
{: If ColorScheme is set to true for any IB_Dataset component then this is the
color which will be used by all data aware controls that reference that dataset
when it enters dssEditing state.}
  property EditingColor: TColor read FEditingColor
                                write FEditingColor
                                default $00AAFFFF;   // clLemon
{: If ColorScheme is set to true for any IB_Dataset component then this is the
color which will be used by all data aware controls that reference that dataset
when it enters dssInserting state.}
  property InsertingColor: TColor read FInsertingColor
                                  write FInsertingColor
                                  default $00C0FFC0; // clApple
{: If ColorScheme is set to true for any IB_Dataset component then this is the
color which will be used by all data aware controls that reference that dataset
when it enters dssDeleting state.}
  property DeletingColor: TColor read FDeletingColor
                                 write FDeletingColor
                                 default $00C8C8FF;  // clPink
{: If ColorScheme is set to true for any IB_Dataset component then this is the
color which will be used by all data aware controls that reference that dataset
when it enters dssSearching state.}
  property SearchingColor: TColor read FSearchingColor
                                  write FSearchingColor
                                  default $00F0F0C8; // clLtAqua
{: If ColorScheme is set to true for any IB_Dataset component then this is the
color which will be used by all data aware controls that reference that dataset
when it enters a state above dssBrowse state and the control is a ReadOnly
control.}
  property ReadOnlyColor: TColor read FReadOnlyColor
                                 write FReadOnlyColor
                                 default clSilver;
{: When multi-selecting rows in a grid this is the color used for the
selected rows.}
  property SelectedColor: TColor read FSelectedColor
                                 write FSelectedColor
                                 default clBlue;
{: This is the color of a control when it is not attached to a datasource/dataset.}
  property InvalidColor: TColor read FInvalidColor
                                write FInvalidColor
                                default clNone;
{: This is the color of a control when it is attached to a prepared dataset.}
  property PreparedColor: TColor read FPreparedColor
                                 write FPreparedColor
                                 default clGrayText;
{: This is the color of a control when it is attached to a dataset that is
browsing a record.}
  property BrowsingColor: TColor read FBrowsingColor
                                 write FBrowsingColor
                                 default clWindow;
{: This property determines whether the following group of properties is to be
stored in the DFM of the forms and datamodules. Leaving this set to False will
avoid exceptions when loading forms that have dependencies in other forms
and a live connection cannot be obtained. Setting it True is mostly for
design-time convenience.
<br>
<br>TIB_Connection.Connected
<br>TIB_Statement.Prepared
<br>TIB_Dataset.Active}
  property StoreActive: boolean read FStoreActive write FStoreActive;
{: This property controls the frequency at which the session generates events to
the components included in the passive processing queue.
<br><br>
It is an interval in milliseconds.  For example, for five seconds, enter 5000.
This is the default value under normal circumstances. (GUI apps with a mainform)
For explicit sessions the default is 0 which disables the timer, to avoid
problems with some models of threading which may not work with the timer.
<br><br>
This property will be initialised to 5000 in the primary/process thread and to
0 in any secondary threads.
<br><br>
If you are creating a DataModule for use in secondary threads, setting the value
to either 0 or 5000 will result in no timer active in the secondary thread.
This is recommended, for the reason described above.  What you should do is to
make the thread which owns the session call the DoTimer method directly, as a
part of its own loop or workload.
<br><br>
For example, I wrote a service application in which there is a loop that checks
for items to process before being put to sleep.  It calls the session.DoTimer
method immediately before being put to sleep, because the timer that normally
triggers calling this method is not triggering any calls to it.
<br><br>
If this is omitted, it is possible that a transaction might be left open in the
schema cache system.}
  property TimerInterval: cardinal read FTimerInterval write SetTimerInterval;
{: Determines whether the Screen.Cursor should be adjusted when a process
becomes active.}
  property UseCursor: boolean read FUseCursor write FUseCursor;
  
{ Events }

{: Event hook to give access to the moment when the first process becomes
active.
<br><br>
This event is triggered only when the FIRST process becomes active and not when
other processes that are nested become active.}
  property OnBeginBusy: TNotifyEvent read FOnBeginBusy write FOnBeginBusy;
{: Event hook to give access to the moment when the last process is completed or
aborted.}
  property OnEndBusy: TNotifyEvent read FOnEndBusy write FOnEndBusy;
{: Event hook to give access to the moment when the first process that is
initiated with Yield as true becomes active.
<br><br>
This event is triggered only when the first yielding process becomes active and
not when other processes that are nested become active.
<br><br>
The first yielding process is the target of this event, regardless of whether
the Yield settings of other processes that are nested after it are True or
False.}
  property OnBeginYield: TNotifyEvent read FOnBeginYield write FOnBeginYield;
{: Event hook to give access to the moment when the last process, that was
initiated with Yield as true, is completed or aborted.}
  property OnEndYield: TNotifyEvent read FOnEndYield write FOnEndYield;
{: Event hook ability to override default colorscheme assignments by
datalinks.}
  property OnGetDataLinkColor: TIB_GetDataLinkColorEvent
      read FOnGetDataLinkColor
     write FOnGetDataLinkColor;
{: This provides a hook into the session timer that operates in the background.
<br><br>
Do not check the Session's IsBusy flag because, when this event is fired, the
IsBusy flag is already set to true.  It is possible to check the
IB_Session.BusyLevel = 0 setting, since the normal counter is not incremented
while a session timer event in progress. This way you can determine if it is, in
fact, really busy.}
  property OnSessionTimer: TIB_SessionTimerEvent read FOnSessionTimer
                                                 write FOnSessionTimer;
{: Global event to trap all potential exceptions due to error codes being
returned from the API.}
  property OnError;
{: General purpose event used to tell the session to wake up.
<br><br>
Here, the session takes any action that is necessary to wake up the application,
so that its events can be processed.}
  property OnWakeup: TNotifyEvent read GetWakeup write SetWakeup;

end;

// IBA_Session.IMP

{                                                                              }
{ IB_Component                                                                 }
{                                                                              }

{: This component is a foundation level class for many of IBO's components and
serves to provide a reference to the Session.
<br><br>
It also gives a hook into the error processing for the session. Errors can be
trapped globally and processed using the TIB_SessionProps component.
<br><br>
The version of the components is reported by this class.}
TIB_Component = class(TComponent)
private
{ Property Storage Fields }
  FBusyLevel: integer;
  FCheckingSession: boolean;
  FHasNilAncestry: boolean;
  FChildOfProcess: boolean;
  FOnError: TIB_ErrorEvent;
  FOnSessionTimer: TIB_SessionTimerEvent;
  procedure SetVersion( const AValue: string );
  function GetVersion: string;
  procedure SetOnSessionTimer( AValue: TIB_SessionTimerEvent );
protected
  FIB_Session: TIB_Session;
  FIsProcess: boolean;
  procedure SetOnError( AValue: TIB_ErrorEvent ); virtual;
  function GetSession: TIB_Session; virtual;
  procedure SetSession( ASession: TIB_Session ); virtual;
  function IsSessionStored: boolean;
  procedure DoHandleError(       Sender: TObject;
                           const errcode: isc_long;
                                 ErrorMessage,
                                 ErrorCodes: TStringList;
                           const SQLCODE: isc_long;
                                 SQLMessage,
                                 SQL: TStringList;
                             var RaiseException: boolean); virtual;
  procedure DoWakeUp( Sender: TObject );
  function NeedTimerNotifications: boolean; virtual;
  property BusyLevel: integer read FBusyLevel;
  function CheckSession( AllowDefault: boolean ): boolean;
  procedure FindSession( var ASession: TIB_Session;
                             AllowDefault: boolean ); virtual;
  procedure Notification( AComponent: TComponent;
                          Operation: TOperation); override;
public

{ Inherited Methods }

{: This constructor method allows an object being created to be made a part of
a session other than the default session for the thread creating it.
<br><br>
Email author if you need additional information.}
  constructor CreateForSession( AOwner: TComponent;
                                ASession: TIB_Session );
  constructor Create( AOwner: TComponent ); override;
{: Standard destructor.}
  destructor Destroy; override;
{: This method is used to indicate that the IB_Session has an active
process pending completion. In some cases it can lead to a performance
improvement if the application code runs a batch of separate processes all
nested within a call to BeginBusy() and EndBusy().
<br><br>
Yield is a flag that lets the session know whether or not there will be a
call to Application.ProcessMessages. If this is true then a different cursor
is used when screen cursor handling is enabled.}
  procedure BeginBusy( Yield: boolean );
{: This method is used to indicate to the IB_Session that a process it
was waiting on has been aborted or completed.
<br><br>
It is very important to use the try..finally..end exception handling construct
when calling these methods.}
  procedure EndBusy;
{: This method allows access to the internal passive task processing. Under
normal circumstances, it is called automatically by the session.}
  procedure ProcessPassiveTasks( var IsDone,
                                     IsWaiting,
                                     Terminate: boolean ); virtual;
{: This flag is used to tell if the component is owned by a process component.
It is necessary to know this, to prevent the passive processing from visiting
components which are internal to a process and corrupting their state.}
  property ChildOfProcess: boolean read FChildOfProcess;
{: This property indicates whether the component can be tied to a session via
the ownership chain.}
  property HasNilAncestry: boolean read FHasNilAncestry;
{: Raw reference to the Session to which this component belongs.}
  property IB_Session_Raw: TIB_Session read FIB_Session;
{: Reference to the Session to which this component belongs.}
  property IB_Session: TIB_Session read GetSession
                                   write SetSession
                                   stored IsSessionStored;
{: Event to trap an exception about to be generated by the component session
as a result of a call to HandleException().
<br><br>
This would be a good place from which to feed an error log file.
<br><br>
It is also possible to prevent a Delphi exception from being raised.
<br><br>
Of course, you may also choose to raise your own custom exception class.
<br><br>
Another role of this event could be to provide alternative text for certain
error messages that are anticipated.}
  property OnError: TIB_ErrorEvent read FOnError write SetOnError;

{: By default the session produces a continual series of timer notifications in
order to serve as the backbone to timing-related issues in IBO.
<br><br>
By default this event will trigger once a second and is controlled at the
session level. See the TIB_SessionProps component for controlling the default
session in a GUI application.
<br><br>
If you are using an explicit TIB_Session component it will not trigger any
events by default. See the TimerInterval property if you want to enable the
explicit session's OnSessionTimer event.
<br><br>
It is recommended that you examine the TCustomForm.UpdateActions method.  Because
that method is hooked into the Application.OnIdle notification system, it would
prove to be more effective than a session timer as a mechanism to hook into for
notifications.}
  property OnSessionTimer: TIB_SessionTimerEvent read FOnSessionTimer
                                                 write SetOnSessionTimer;
published

{: Version of InterBase Objects implemented.}
  property Version: string read GetVersion write SetVersion stored false;

end;

// IBA_Component.IMP

{                                                                              }
{ TIB_SchemaCache                                                              }
{                                                                              }

TIB_SchemaCacheItems = ( sciDomainNames,
                         sciGeneratorNames,
                         sciTableNames,
                         sciViewNames,
                         sciProcedureNames,
                         sciConstraintNames,
                         sciTriggerNames,
                         sciIndexDefs,
                         sciIndexNames,
                         sciPrimaryKeys,
                         sciForeignKeys,
                         sciRequired,
                         sciDefaults,
                         sciComputed,
                         sciFieldDomains,
                         sciTableKeys,
                         sciTableFields,
                         sciProcedureParams,
                         sciRelationsByID,
                         sciDatabaseSchemaVer,
                         sciClientSchemaVer );

TIB_SchemaCacheItemsSet = set of TIB_SchemaCacheItems;

{: This is used to store metadata information about the database connected to
locally such that the information is available very quickly.}
TIB_SchemaCache = class( TPersistent ) 
private
  FTransaction: TIB_TransactionInternal;
  FValidInfoFlags: TIB_SchemaCacheItemsSet;
  FValueLists: array[ TIB_SchemaCacheItems ] of TIB_StringList;
  FDomainNameCursor: TIB_Dataset;
  FTableKeyCursor: TIB_Dataset;
  FTableFieldsCursor: TIB_Dataset;
  FFieldSourceCursor: TIB_Dataset;
  FDomainSourceCursor: TIB_Dataset;
  FProcedureParamsCursor: TIB_Dataset;
  FProcParamsAttrCursor: TIB_Dataset;
  FLocalDir: string;
protected
  procedure DoHandleError( Sender: TObject; const ERRCODE: longint;
    ErrorMessage, ErrorCodes: TStringList; const SQLCODE: longint;
    SQLMessage, SQL: TStringList; var RaiseException: Boolean);
  function GetConnection: TIB_Connection;
  function GetDomainNameCursor: TIB_Dataset;
  function GetTableKeyCursor: TIB_Dataset;
  function GetTableFieldsCursor: TIB_Dataset;
  function GetFieldSourceCursor: TIB_Dataset;
  function GetDomainSourceCursor: TIB_Dataset;
  function GetProcedureParamsCursor: TIB_Dataset;
  function GetProcParamsAttrCursor: TIB_Dataset;
  procedure SetLocalDir( const AValue: string );
  property DomainNameCursor: TIB_Dataset read GetDomainNameCursor;
  property TableKeyCursor: TIB_Dataset read GetTableKeyCursor;
  property TableFieldsCursor: TIB_Dataset read GetTableFieldsCursor;
  function GetSchemaCacheList( AList: integer ): TIB_StringList;
  function GetItemName( ALst: TIB_SchemaCacheItems ): string;
  function GetLocalFileName( ALst: TIB_SchemaCacheItems ): string;
  function GetGDBFilename: string;
  procedure CheckTransactionEnded;
public
  constructor Create( AConnection: TIB_Connection );
  destructor Destroy; override;
  procedure FreeResources;
{: This method creates the IBO$SCHEMA_VERSION table if needed, and adds any
missing records that are required for versioning to operate correctly.
<br><br>
If the table IBO$SCHEMA_VERSION exists in the database and it has records
present, schema versioning is performed on the basis of values stored in this
table.  Updating the version numbers in this table will cause all clients
to refresh the associated schema cache items when next loaded.}
  procedure CheckSchemaVersionTable;
{: Calling this method will delete the local schema cache files.  The files
will be regenerated automatically the next time the information is accessed.
<br><br>
To cause the schema cache to be reloaded immediately, call the
InvalidateAllItems method.}
  procedure DeleteLocalFiles;
{: Calling this method will invalidate all schema cache items and cause them
to be reloaded on demand from either local schema files or directly from the
database.  The source for reloading depends on whether local files exist and, if
so, whether the IBO$SCHEMA_VERSION table exists and contains records that are
more recent.
<br><br>
If metadata changes are made by a application using an IB_Script or via the
ExecuteImmediate method of a statement, the application may subsequently discard
old schema cache information by updating records in the IBO$SCHEMA_VERSION table
or delete specific schema in the local cache.  Calling this method will allow
immediate access to the new metadata information.}
  procedure InvalidateAllItems;
{: Calling this method loads the RelationsByID information, which is used by
the IB_Profiler to report table names in performance counts.}
  procedure LoadRelationsByIDInfo;
  function GetDomainName( const ARelName, ASQLName: string ): string;
  procedure GetProcedureParamNames( const AProcName: string;
                                    const AParamInput: boolean;
                                          AStrings: TStrings );
  procedure GetProcParamAttr( const AProcName,
                                    AParamName: string;
                                    AStrings: TStrings );
  procedure GetTableKeys( const TableName: string; const AKeys: TStrings );
  procedure GetTableFields( const TableName: string; const AFields: TStrings );
  property FieldSourceCursor: TIB_Dataset read GetFieldSourceCursor;
  property DomainSourceCursor: TIB_Dataset read GetDomainSourceCursor;
  property Connection: TIB_Connection read GetConnection;
  property GDBFilename: string read GetGDBFilename;
  property LocalDir: string read FLocalDir write SetLocalDir;
  property Transaction: TIB_TransactionInternal read FTransaction;
published
  property Computed: TIB_StringList index integer( sciComputed )
                                    read GetSchemaCacheList;
  property ConstraintNames: TIB_StringList index integer( sciConstraintNames )
                                           read GetSchemaCacheList;
  property Defaults: TIB_StringList index integer( sciDefaults )
                                    read GetSchemaCacheList;
  property DomainNames: TIB_StringList index integer( sciDomainNames )
                                       read GetSchemaCacheList;
  property ForeignKeys: TIB_StringList index integer( sciForeignKeys )
                                       read GetSchemaCacheList;
  property GeneratorNames: TIB_StringList index integer( sciGeneratorNames )
                                          read GetSchemaCacheList;
  property IndexDefs: TIB_StringList index integer( sciIndexDefs )
                                     read GetSchemaCacheList;
  property IndexNames: TIB_StringList index integer( sciIndexNames )
                                      read GetSchemaCacheList;
  property PrimaryKeys: TIB_StringList index integer( sciPrimaryKeys )
                                       read GetSchemaCacheList;
  property ProcedureNames: TIB_StringList index integer( sciProcedureNames )
                                          read GetSchemaCacheList;
  property Required: TIB_StringList index integer( sciRequired )
                                    read GetSchemaCacheList;
  property TableNames: TIB_StringList index integer( sciTableNames )
                                      read GetSchemaCacheList;
  property RelationsByID: TIB_StringList index integer( sciRelationsByID )
                                         read GetSchemaCacheList;
  property TriggerNames: TIB_StringList index integer( sciTriggerNames )
                                        read GetSchemaCacheList;
  property ViewNames: TIB_StringList index integer( sciViewNames )
                                     read GetSchemaCacheList;
  property FieldDomains: TIB_StringList index integer( sciFieldDomains )
                                        read GetSchemaCacheList;
  property TableKeys: TIB_StringList index integer( sciTableKeys )
                                     read GetSchemaCacheList;
  property TableFields: TIB_StringList index integer( sciTableFields )
                                       read GetSchemaCacheList;
  property ProcedureParams: TIB_StringList index integer( sciProcedureParams )
                                           read GetSchemaCacheList;
  property DatabaseSchemaVer: TIB_StringList
      index integer( sciDatabaseSchemaVer ) read GetSchemaCacheList;
  property ClientSchemaVer: TIB_StringList
      index integer( sciClientSchemaVer ) read GetSchemaCacheList;
end;

// IBA_SchemaCache.IMP

{                                                                              }
{ TIB_DMLCache                                                                 }
{                                                                              }

TIB_DMLCacheItemType = ( ditEdit, ditInsert, ditDelete );

TIB_DMLCacheFlags = ( dcfAnnounceEdit,
                      dcfAnnounceInsert,
                      dcfAnnounceDelete,
                      dcfReceiveEdit,
                      dcfReceiveInsert,
                      dcfReceiveDelete );
                      
TIB_DMLCacheFlagsSet = set of TIB_DMLCacheFlags;                      

TIB_DMLCacheItem = class( TCollectionItem )
private
  FConnection: TIB_Connection;
  FKeyFieldNames: string;
  FKeyFieldValues: variant;
  FDMLCacheItemType: TIB_DMLCacheItemType;
protected
  procedure AnnounceToConnection;
  procedure AnnounceToDataset( const DS: TIB_Dataset );
  procedure AnnounceToTransaction( const TS: TIB_Transaction;
                                   const ExcludeDS: TIB_Dataset );
public
  property Connection: TIB_Connection read FConnection;
  property KeyFieldNames: string read FKeyFieldNames;
  property KeyFieldValues: variant read FKeyFieldValues;
  property DMLCacheItemType: TIB_DMLCacheItemType read FDMLCacheItemType;
end;

TIB_TransactionDMLCache = class( TCollection )
private
  FTransaction: TIB_Transaction;
  function GetDMLItem(Index: Integer): TIB_DMLCacheItem;
  procedure SetDMLItem(Index: Integer; Value: TIB_DMLCacheItem);
protected
  function Add: TIB_DMLCacheItem;
  procedure ProcessItems( Announce: boolean );
  procedure ProcessItem( const AItem: TIB_DMLCacheItem );
public
  constructor Create( ATransaction: TIB_Transaction );
  destructor Destroy; override;
  procedure AddItem( AConnection: TIB_Connection;
                     ADataset: TIB_Dataset;
                     AKeyFieldNames: string;
                     AKeyFieldValues: variant;
                     ADMLCacheItemType: TIB_DMLCacheItemType );
  property Items[ Index: integer ]: TIB_DMLCacheItem read GetDMLItem
                                                     write SetDMLItem; default;
  property Transaction: TIB_Transaction read FTransaction;
end;

// IBA_DMLCache.IMP

{                                                                              }
{ TIB_Connection                                                               }
{                                                                              }

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  27-Apr-2003                                                                 }
{     Added extract of Firebird specific version info to characteristics.      }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  09-Sep-2001                                                                 }
{     Added psKeyFromEnviron option to TIB_PasswordStorage type.               }
{                                                                              }
{  Wassim Haddad <lobolo2000@yahoo.com>                                        }
{  22-Aug-2001                                                                 }
{     Added support to import column defaults from the server upon request.    }
{     ImportServerDefaults is a public method that imports column defaults     }
{     from the server.                                                         }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  10-Aug-2001                                                                 }
{     Implemented new PasswordStorage property to control if and how the       }
{     Password property is saved to the DFM (via the SavedPassword property).  }
{     See TIB_PasswordStorage type for detailed explanation.                   }
{     Deprecating PasswordRemembered - some special streaming implemented      }
{     so that it can still be read, but now it simply sets PasswordStorage     }
{     to psKeyFromUserReg.                                                     }
{     SavedPassword property set back to public (from published) and special   }
{     streaming code provided to read/write to/from the DFM.                   }
{                                                                              }
{******************************************************************************}

{: Exception class for the TIB_Connection component.}
EIB_ConnectionError = class( EIB_Error );

{: Status of the connection.
<br><br>
csForcedConnectPending occurs when a live dbHandle is written to the dbHandle
or dbHandleShared property.
<br><br>
The rest should be self-explanatory.}
TIB_ConnectionStatus = ( csDisconnected,        csConnected,
                         csDisconnectPending,   csConnectPending,
                         csDropPending,         csCreatePending,
                         csForcedConnectPending );

{: All of the different notification messages generated by the
TIB_Connection component and propagated to the TIB_ConnectionLink class.}
TIB_ConnectionEventType = ( cetBeforeAssignment,
                            cetAfterAssignment,
                            cetBeforeExecDDL,
                            cetAfterExecDDL,
                            cetConnectedChanged,
                            cetBeforeCreateDatabase,
                            cetAfterCreateDatabase,
                            cetBeforeConnect,
                            cetAfterConnect,
                            cetBeforeDisconnect,
                            cetAfterDisconnect,
                            cetBeforeDropDatabase,
                            cetAfterDropDatabase );
{: Determines how connection passwords are stored.
<br><br>
psNone - password is not stored, must be re-entered via a login
prompt or assigned to the connection in code at runtime.
<br><br>
psKeyFromUserReg - the password is stored in a jumbled form in
the DFM and therefore the executable.  The key to unjumble this
password is stored in the registry under HKEY_CURRENT_USER, so
that only the current user can decrypt the password.  If the
application is executed on another machine, or used by a different
user on the same machine, it will NOT be able to open the connection.
<br><br>
psKeyFromMachineReg - the password is stored in a jumbled form in
the DFM and therefore the executable.  The key to unjumbling this
password is stored in the registry under HKEY_LOCAL_MACHINE, so that
only users on the local machine can decrypt the password.  If the
application executed on another machine it will NOT be able to
open the connection.
<br><br>
psNotSecure - the password is stored in a jumbled form in the DFM
and therefore the executable.  The key to unjumbling this password
is hardcoded into IBObjects.  Any user on any machine will be able
to open the connection (presuming they have network connection to
the database).
<br><br>
psKeyFromEnviron - the password is stored in a jumbled form in
the DFM and therefore the executable.  The key to unjumbling this
password expected to be found in the environment variable
IBO_PASSKEY.  If no such variable is found then the processing
reverts to psNotSecure.
<br><br>
NOTE: The psNotSecure setting will allow you to share your
applications with any user/machine that has access to the database.
It also means that almost anyone can easily determine what the real
password is and use it to access the database from other applications.
<br><br>
NOTE 2: On Windows NT and Windows 2000 machines there may be security
issues with creating entries in the HKEY_LOCAL_MACHINE area of the
registry.  Users *may* be able to read entries stored in that location,
but they will not be able to create the entry.  If the password does
not seem to being stored on your machine, try using the psUser option
or check the registry security with RegEdt32.
<br><br>
NOTE 3: See the JumbleString function in IB_Utils.pas for information
on the encryption mechanism used by the psKeyFrom* options, and also
the GetJumbleKey function for registry access methods used.
}
TIB_PasswordStorage = (
  psNone,
  psParams,
  psKeyFromUserReg,
  psKeyFromMachineReg,
  psNotSecure,
  psKeyFromEnviron
  );

{: This property provides the options for the ParameterOrder property.
<br>
This is designed to allow IBO to properly handle the server and how it deals
with input parameters. The old style had it based in a convoluted nested way
and FB 1.5 has been changed to deal with parameters in a straight linear way.
<br>
Because of this change, IBO will not work with the new method now so it needs
to know which behavior the server has.}
TIB_ParameterOrderFlag = ( poAuto, poOld, poNew );
{: This type is used in setting DPB settings for a connection. }
TIB_DPBFlag = ( dpbDefault, dpbTrue, dpbFalse );
{: Event type used by most events for the TIB_Connection component.}
TIB_ConnectionEvent = procedure( Sender: TIB_Connection ) of object;
{: Event type used by the TIB_Connection class for logging in a user.}
TIB_LoginEvent = procedure(     Sender: TIB_Connection;
                            var AbortLogin: boolean ) of object;
{: Event type used by the TIB_Connection class to pass in custom DPB settings.}
TIB_CustomizeDPBEvent = procedure (     Sender: TIB_Connection;
                                    var ABufPtr: integer;
                                    var ABuffer: array of char ) of object;

{: This event is used in order to provide custom handling of search criteria.
It is triggered before IBO's default search buffer handling code.  It is
possible therefore to design your own extensions and syntax for use in search
mode.  In this event check for your special syntax and manipulate it into a
format that IBO can handle by default.
<br><br>
It is also possible to override IBO's default handling completely, by setting
the reference variable for it to False. This assumes that you provide your own
entries into the SQLWhereItems and ParamValueLinks string lists.}
TIB_ProcessSearchBuffer = procedure(     Sender: TObject;
                                         IB_Field: TIB_Column;
                                     var SearchBuffer: string;
                                         WhereClause,
                                         Parameters,
                                         Macros: TStrings;
                                     var DefaultHandling: boolean ) of object;
{: This event type is used to process the DMLCacheItems in order to maintain
cross-connection buffer synchronization.
<br><br>
It is possible to change the contents of the cache item or reject it altogether.}
TIB_ReceiveDMLCacheEvent = procedure ( AConnection: TObject;
                                   var AKeyFieldNames: string;
                                   var AKeyFieldValues: variant;
                                   var ADMLCacheItemType: TIB_DMLCacheItemType;
                                   var Accept: boolean ) of object;

{: Event type to define SoundEx parsing routines for a connection.}
TIB_SoundExParse = procedure(       Sender: TObject;
                              const SourceStr: string;
                              var   ResultStr: string ) of object;

{: Event to use custom string comparisons instead of (Ansi)CompareText.
<br>
Returns -1, 0, 1, less than, equal to or greater than.}
TIB_CompareTextEvent = function( IB_Field: TIB_Column;
                                 const S1, S2: string ): integer of object;

{: Event to use custom high char in order to support different collations along
with the horizontal dataset refinement. In some cases #255 is considered at a
lesser position in the collation order than 'z' is. In such a case, the
dataset is going to appear truncated if the Last method is called.}
TIB_GetHighCollateChar = procedure(     IB_Field: TIB_Column;
                                    var AChar: char ) of object;

{: Structure used to retrieve various settings for a connection.}
TIB_ConnectCharacteristics = packed record
  dbAllocation: longint;
  dbLevelPrefix: byte;
  dbBase_Level: byte;
  dbFilePrefix: byte;
  dbFile: string;
  dbSite: string;
  dbImplementationPrefix: byte;
  dbImplementation: byte;
  dbClass: byte;
  dbNo_Reserve: byte;
  dbForced_Writes: byte;
  dbODS_Minor_Version: word;
  dbODS_Version: word;
  dbPage_Size: word;
  dbVersionPrefix: byte;
  dbVersion: string;
  dbFBVersionPrefix: byte;
  dbFBVersion: string;
  dbPlatform: string;
  dbBuildType: string;
  dbBuildInfo: string;
  dbServer_Major_Version:integer;
  dbServer_Minor_Version:integer;
  dbServer_Release:integer;
  dbServer_Build:integer;
  dbSweep_Interval: integer;
  dbPage_Buffers: integer;
  dbSQLDialect: integer;
  dbReadOnly: boolean;
  dbSizeInPages: integer;
end;

{: This indicates whether DomainName and SQLType entries are included in any of
the stringlist properties.
<br><br>
This property exists to avoid unnecessary processing if these more advanced
features are not in use by your application.}
TIB_ConnectionFieldEntryTypes = ( fetDomainName, fetSQLType );
{: Set type for the field entry types property.}
TIB_ConnectionFieldEntryTypeSet = set of TIB_ConnectionFieldEntryTypes;

{: This component obtains a persistent connection to an InterBase database.
<br><br>
It is responsible for providing all of the properties and methods that are
associated with an InterBase database.
<br><br>
It also provides many useful features to assist with database administration.
<br><br>
See the dbHandleShared property to see how you can share a single connection to
an InterBase database with BDE based components TDatabase, TQuery, etc.
<br><br>
IBO includes a lot of features that were designed to preserve compatibility with
applications which must retain a connection through the BDE.  The benefit of
this is the ability to have additional concurrent transactions using IB's full
transaction capabilities and even cross-database transactions involving the
connection being used by the BDE.}
TIB_Connection = class(TIB_Component)
private
{ Property storage fields }
  FSchemaCache: TIB_SchemaCache;
  FColumnAttributes: TIB_StringProperty;
  FDefaultValues: TIB_StringProperty;
  FFieldsAlignment: TIB_StringProperty;
  FFieldsCharCase: TIB_StringProperty;
  FFieldsDisplayLabel: TIB_StringProperty;
  FFieldsGridLabel: TIB_StringProperty;
  FFieldsGridTitleHint: TIB_StringProperty;
  FFieldsDisplayFormat: TIB_StringProperty;
  FFieldsDisplayWidth: TIB_StringProperty;
  FFieldsEditMask: TIB_StringProperty;
  FFieldsReadOnly: TIB_StringProperty;
  FFieldsTrimming: TIB_StringProperty;
  FFieldsVisible: TIB_StringProperty;
  FGeneratorLinks: TIB_StringProperty;
  FConnectAfterLoad: boolean;
  FdbHandle: isc_db_Handle;
  FIsHandleShared: boolean;
  FParams: TIB_StringList;
  FPassword: string;
  FPasswordStorage: TIB_PasswordStorage;
  FConnectedDatabase: string;
  FConnectedUserName: string;
  FConnectedPassword: string;
  FConnectedSQLRolename: string;
  FUsers: TIB_StringList;
  FStatementList: TList;
  FDatasetList: TList;
  FTransactionList: TList;
  FActiveTransactionList: TList;
  FConnectionLinkList: TList;
  FConnectionStatus: TIB_ConnectionStatus;
  FAnnounceFocus: boolean;
  FLoginCaption: string;
  FLoginSQLRoleList: string;
  FLoginSQLRolePrefix: string;
  FLoginUsernamePrefix: string;
  FLoginRegKey: string;
  FLoginPrompt: boolean;
  FLoginAttempts: word;
  FLoginAttemptsDone: word;
  FLoginAbortedShowMessage: boolean;
  FLoginDBReadOnly: boolean;
  FLoginWasUsed: boolean;
  FLoginHelpContext: integer;
  FDatabaseName: string;
  FStmtHandleCache: pointer;
  FStmtHandleCount: integer;
  FStmtHandleCapacity: integer;
  FCacheStatementHandles: boolean;
  FEscapeCharacter: char;
  FFieldEntryTypes: TIB_ConnectionFieldEntryTypeSet;
  FRequestReconnect: boolean;
  FDefaultNoCase: boolean;
  FDefaultNoTrailing: boolean;
  FDefaultNoTrimming: boolean;
  FDefaultNoLengthCheck: boolean;
  FDefaultTransaction: TIB_Transaction;
  FStartedTransactionCount: integer;
  FLastOpened: TDateTime;
  FLastClosed: TDateTime;
  FAliasName: string;
  FAliasParams: TIB_StringList;
  FKeepConnection: boolean;
  FLastConnected: TDateTime;
  FMacroBegin: string;
  FMacroEnd: string;
  FParameterOrder: TIB_ParameterOrderFlag;
  FOldParameterOrder: boolean;
{ Database characteristics }
  FCharacteristics: TIB_ConnectCharacteristics;
  FCharacteristicsValid: boolean;
  FConnectionWasLost: boolean;
{ Event Storage }
  FBeforeExecDDL: TIB_ConnectionEvent;
  FAfterExecDDL: TIB_ConnectionEvent;
  FBeforeConnect: TIB_ConnectionEvent;
  FAfterConnect: TIB_ConnectionEvent;
  FBeforeDisconnect: TIB_ConnectionEvent;
  FAfterDisconnect: TIB_ConnectionEvent;
  FBeforeCreateDatabase: TIB_ConnectionEvent;
  FAfterCreateDatabase: TIB_ConnectionEvent;
  FBeforeDropDatabase: TIB_ConnectionEvent;
  FAfterDropDatabase: TIB_ConnectionEvent;
  FOnConnectedChanged: TIB_ConnectionEvent;
  FOnLogin: TIB_LoginEvent;
  FOnLoginFailure: TIB_ConnectionEvent;
  FOnGainFocus: TIB_ConnectionEvent;
  FOnLoseFocus: TIB_ConnectionEvent;
  FOnCustomizeDPB: TIB_CustomizeDPBEvent;
  FOnProcessSearchBuffer: TIB_ProcessSearchBuffer;
  FOnSoundExParse: TIB_SoundExParse;
  FOnSoundExMaxParse: TIB_SoundExParse;
  FOnReceiveDMLCache: TIB_ReceiveDMLCacheEvent;
  FOnCustomCompareText: TIB_CompareTextEvent;
  FOnCustomCompareStr: TIB_CompareTextEvent;
  FOnGetHighCollateChar: TIB_GetHighCollateChar;
{ Property Access Methods }
  procedure GetSessionName( AReader: TReader );
  procedure LoadSavedPassword( AReader: TReader );
  procedure StoreSavedPassword( AWriter: TWriter );
  procedure LoadPasswordRemembered( AReader: TReader );
  procedure SetAliasName( const AValue: string );
  function GetAliasParams: TIB_StringList;
  procedure SetKeepConnection( AValue: boolean );
  function IsConnectedStored: boolean;
  procedure SetParams( Value: TIB_StringList );
  function GetPrm( Index: integer ): string;
  procedure SetPrm( Index: integer; const Value: string );
  function GetPassword: string;
  procedure SetPassword( AValue: string );
  function GetForcedWrites: TIB_DPBFlag;
  procedure SetForcedWrites( AValue: TIB_DPBFlag );
  function GetDBKeyScope: TIB_DPBFlag;
  procedure SetDBKeyScope( AValue: TIB_DPBFlag );
  function GetReservePageSpace: TIB_DPBFlag;
  procedure SetReservePageSpace( AValue: TIB_DPBFlag );
  function GetPageSize: word;
  procedure SetPageSize( AValue: word );
  function GetSweepInterval: word;
  procedure SetSweepInterval( AValue: word );
  function GetTransactionCount: integer;
  function GetTransaction( Index: integer ): TIB_Transaction;
  function GetStatementCount: integer;
  function GetStatement( Index: integer ): TIB_Statement;
  function GetDatasetCount: integer;
  function GetDataset( Index: integer ): TIB_Dataset;
  function GetConnectionLinkCount: integer;
  function GetPdbHandle: pisc_db_handle;
  procedure SetdbHandle( AValue: isc_db_handle );
  procedure SetdbHandleShared( AValue: isc_db_handle );
  function GetUsers: TStrings;
  function GetProtocol: TIB_Protocol;
  procedure SetProtocol( AValue: TIB_Protocol );
  function GetCharacteristics: TIB_ConnectCharacteristics;
  procedure SetColumnAttributes( AValue: TIB_StringProperty );
  procedure SetDefaultValues( AValue: TIB_StringProperty );
  procedure SetFieldsAlignment( AValue: TIB_StringProperty );
  procedure SetFieldsCharCase( AValue: TIB_StringProperty );
  procedure SetFieldsDisplayLabel( AValue: TIB_StringProperty );
  procedure SetFieldsGridLabel( AValue: TIB_StringProperty );
  procedure SetFieldsGridTitleHint( AValue: TIB_StringProperty );
  procedure SetFieldsDisplayFormat( AValue: TIB_StringProperty );
  procedure SetFieldsDisplayWidth( AValue: TIB_StringProperty );
  procedure SetFieldsEditMask( AValue: TIB_StringProperty );
  procedure SetFieldsReadOnly( AValue: TIB_StringProperty );
  procedure SetFieldsTrimming( AValue: TIB_StringProperty );
  procedure SetFieldsVisible( AValue: TIB_StringProperty );
  procedure SetGeneratorLinks( AValue: TIB_StringProperty );
  procedure AllocateStmtHandle( const PstHandle: pisc_stmt_handle );
  procedure DeallocateStmtHandle( const PstHandle: pisc_stmt_handle );
  procedure SetCacheStatementHandles( AValue: boolean );
  function GetSchemaCacheDir: string;
  procedure SetSchemaCacheDir( const AValue: string );
  function GetLoginUsername: string;
  procedure SetLoginUsername( const AValue: string );
  function GetLoginSQLRole: string;
  procedure SetLoginSQLRole( const AValue: string );
  function GetSQLDialect: smallint;
  procedure SetSQLDialect( AValue: smallint );
  function GetHasActiveTransaction: boolean;
  procedure SetLostConnection;
  procedure SetMacroBegin(AValue: string);
  procedure SetMacroEnd(AValue: string);
  function IsMacroBeginStored: boolean;
  function IsMacroEndStored: boolean;
protected
{ Utility storage }
  flag_junk_value: integer;
  FLoginAborted: boolean;
{ Inherited Methods }
  procedure DefineProperties( AFiler: TFiler ); override;
  procedure Loaded; override;
  procedure Notification( AComponent: TComponent;
                          Operation: TOperation ); override;
  function NeedTimerNotifications: boolean; override;
  procedure SetSession( ASession: TIB_Session ); override;
{ Property Access methods }
  function GetDatabase: string; //~virtual;
  procedure SetDatabase( const AValue: string ); //~virtual;
  function GetDatabaseName: string; //~virtual;
  procedure SetDatabaseName( const AValue: string ); //~virtual;
  function IsDatabaseNameStored: boolean; //~virtual;
  procedure SetConnected( Value: boolean); //~virtual;
  function GetConnected: boolean; //~virtual;
  procedure SetAnnounceFocus( AValue: boolean ); virtual;
  procedure SetDefaultTransaction( AValue: TIB_Transaction ); virtual;
  function GetSavedPassword: string; virtual;
  procedure SetSavedPassword( AValue: string ); virtual;
  function IsSavedPasswordStored: boolean; virtual;
  procedure SetPasswordStorage( AVal: TIB_PasswordStorage );
{ System Methods }
  procedure SysInvalidateCachedInformation; //~virtual;
  procedure SysUpdateCharacteristics; //~virtual;
  procedure SysConnectAfterLoad; //~virtual;
  procedure SysBeforeExecDDL; //~virtual;
  procedure SysAfterExecDDL; //~virtual;
  procedure SysBeforeConnect; //~virtual;
  procedure SysLogin; //~virtual;
  procedure SysBeforeDisconnect; //~virtual;
  function SysConnect( FromScript: boolean ): boolean;
  procedure SysDisconnect; //~virtual;
  procedure SysAfterConnect; //~virtual;
  procedure SysAfterDisconnect; //~virtual;
  procedure SysCreateDatabase; //~virtual;
  procedure SysDropDatabase; //~virtual;
  procedure SysBeforeCreateDatabase; //~virtual;
  procedure SysBeforeDropDatabase; //~virtual;
  procedure SysAfterCreateDatabase; //~virtual;
  procedure SysAfterDropDatabase; //~virtual;
{ API level calls }
  procedure API_Connect;
  procedure API_Disconnect;
  procedure API_Database_Info( var Items: array of Char;
                               var Buffer: array of Char );
{ Event Dispatch Methods }
  procedure DoBeforeExecDDL; //~virtual;
  procedure DoAfterExecDDL; //~virtual;
  procedure DoLogin; virtual;
  procedure DoLoginFailure( E: Exception ); virtual;
  procedure DoBeforeConnect; virtual;
  procedure DoAfterConnect; virtual;
  procedure DoBeforeDisconnect; virtual;
  procedure DoAfterDisconnect; virtual;
  procedure DoBeforeCreateDatabase; //~virtual;
  procedure DoAfterCreateDatabase; //~virtual;
  procedure DoBeforeDropDatabase; //~virtual;
  procedure DoAfterDropDatabase; //~virtual;
  procedure DoConnectedChanged; //~virtual;
  procedure DoGainFocus; //~virtual;
  procedure DoLoseFocus; //~virtual;
  procedure DoCustomizeDPB( var bufptr: integer;
                            var buffer: array of char ); //~virtual;
  procedure DoProcessSearchBuffer(     IB_Field: TIB_Column;
                                   var SearchBuffer: string;
                                       WhereClause,
                                       Parameters,
                                       Macros: TStrings;
                                   var DefaultHandling: boolean ); virtual;
  procedure DoSoundExParse( const SourceStr: string;
                              var ResultStr: string ); virtual;
  procedure DoSoundExMaxParse( const SourceStr: string;
                               var   ResultStr: string ); virtual;
{ Link Dispatch Method }
  procedure DoLinkEvent( AEvent: TIB_ConnectionEventType );
{ Link handling methods }
  procedure AddConnectionLink( NewLink: TObject );
  procedure RemoveConnectionLink( OldLink: TObject );
{ Protected properties }
  property ConnectionLinkCount: integer read GetConnectionLinkCount;

public

{$IFNDEF HELPSCAN}

{ Inherited Methods }
  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;

{$ENDIF}

{ New methods }

{: Method which will cause all transactions for the connection to be closed.
See the Close method of TIB_Transaction to see what behavior will take place.}
  procedure CloseTransactions; //~virtual;
{: Method which will cause all statements to be deallocated. Implicitly this
means that all datasets will close and all statements will be unprepared.}
  procedure DeallocateStatements; //~virtual;
{: This method will add/modify/delete a user to the server specified
in the Database property, incorporating the Protocol, Username and Password
properties in the process.}
  procedure AlterUser( Action: TIB_AlterUserAction;
                       AUserName,
                       AUserPass,
                       AGroupName,
                       AFirstName,
                       AMiddleName,
                       ALastName: string );
{: Method used to build the DPB when obtaining a connection to the database.}
  procedure BuildDPB( var BufPtr: integer;
                      var Buffer: array of char;
                          Item: byte;
                          Contents: string);
{: Method to attach to the database.}
  procedure Connect; dynamic;
{: Methods to detach from the database.}
  procedure Disconnect; dynamic;
{: This method will attempt to disconnect three times and will ignore any
exceptions that may be raised in the process.}  
  procedure ForceDisconnect;
{: Methods to detach from the database and store the handle in the connection
pool.}
  procedure DisconnectToPool; dynamic;
{: Function to return a value from a generator.}
  function Gen_ID( const AGenerator: string; Increment: integer ): ISC_INT64;
{: Method to attach to the database.
<br><br>
Included for VCL compatibility.}
  procedure Open; dynamic;
{: Method to detach from the database.
<br><br>
Included for VCL compatibility.}
  procedure Close; dynamic;
{: Method to create a database using the properties and attributes defined for
the current database connection.}
  procedure CreateDatabase; dynamic;
{: Method to drop (delete) a database.
<br><br>
There must be an existing connection and no others users may be logged in
or an exception is raised.}
  procedure DropDatabase; dynamic;
{: This method will discard all schema cache information and cause it to be
reloaded on demand.  If the SchemaCacheDir property is being used, this will
delete all of the local cache files.
<br><br>
For finer control of which local cache information is deleted from the
SchemaCacheDir, see the help for the following properties/methods...
<br>  TIB_Connection.SchemaCacheDir
<br>  TIB_SchemaCache.CheckSchemaVersionTable
<br>  TIB_SchemaCache.InvalidateAllItems
<br><br><br>
The FlushSchemaCache method can be used to reload all schema information if an
alteration to the database's metadata structure was executed through a means
other than using a IB_DSQL or other query component and calling ExecSQL.
<br><br>
For example, if you use the ExecuteImmediate() method or execute an IB_Script,
this method can be called to ensure that the consequent changes will be
accurately reflected in the internal schema cache.
<br><br>
When executing DDL statements through an IB_DSQL or other dataset component, IBO
prepares the statement individually, establishes that it is a DDL statement and
and takes the appropriate action automatically.  Such an operation will cause
the entire schema cache to be reloaded.  You might wish to consider avoiding it
in low-bandwidth environments.}
  procedure FlushSchemaCache;
{: This method will trim the number of statement handles in the cache.
<br><br>
It is necessary when a large form or many forms with lots of statements have
been freed and there is a good possibility that they will not get used for a
while. It is merely a matter of conservation since the server is holding
resources for each statement handle that is in the cache.   Forms are freed much
more quickly when they don't have to deallocate immediately but, instead, simply
pass the live handle to the connection.
<br><br>
I hope to include some timer driven worker threads that will periodically peruse
the various caches and buffers of IBO and trim things up according to some
tuning parameters which I will put together.
<br><br>
When dropping a table or other resources, it is possible to get an "object in
use" error when attempting to commit the change, if a cached statement handle
is holding the resource.  IBO checks for this problem now.  It automatically
calls this method and retries the commit, which will succeed if a cached handle
was holding the resources.  Consequently, the "Object in Use" error should now
occur only if another user is holding the resource.
<br><br>
I'm hoping that I can figure out a way to propagate a notice to other users to
free up their extra server resources as well.}
  procedure FreeStmtHandleCache( MaxHandles: integer );
{: These methods are used to access the Soundex conversion functionality
built into the connection.}
  function GetSoundEx( SourceStr: string ): string; //~virtual;
{: These methods are used to access the Soundex conversion functionality
built into the connection.}
  function GetSoundExMax( SourceStr: string ): string; //~virtual;
{: BDE emulation property.}
  function IsSQLBased: boolean;
{: This method is used to initiate the announcement of a DMLCacheItem so that
all the datasets in this connection can synchronize with changes that have taken
place externally to the connection.
<br><br>
It is ideally fed by an IB_Events alerter component that reads the pertinent
information from a table containing a log of recent changes in the database,
which itself fed by triggers configured on the tables for which you want to
maintain synchronization.}
  procedure ProcessDMLCacheItem( var AKeyFieldNames: string;
                                 var AKeyFieldValues: variant;
                                 var ADMLCacheItemType: TIB_DMLCacheItemType);
{: This method sets a flag that will cause the connection to do a quick
reconnect and disconnect upon disconnecting from the database.
<br><br>
This is used primarily to avoid a bug in the event alerter mechanisms. I put a
call to this method in the AfterRegister event of the IB_Events component
when I use events in order to prevent the system from freezing when closing the
application.}
  procedure RequestReconnect;
{: Make this database connection the currently focused connection for the
session.}
  procedure SetFocus; dynamic;
{: I plan to use this as a way to check and make sure that the connection is
still valid and that a network error or server crash hasn't made the client's
connection invalid.
<br><br>
This method will return False when something is actually wrong.  If it detects a
lost connection, it will set the ConnectionWasLost flag as well.
<br><br>
Call this method sparingly because it does actually hit the wire. How else can
I test the connection if I don't use it somehow?}
  function VerifyConnection: boolean;
{: Method to populate the DefaultValues property with table & domain defaults.}
  procedure ImportServerDefaults;
{: This function returns a string that is suitable for use as an identifier in a
DDL script.}
  function mkIdent( const AString: string ): string;
{: This function returns a string that is suitable for use as an identifier in a
DDL script.}
  function mkFldLst( const AString: string ): string;
{: This function returns a string that is suitable for use as an identifier in a
DDL script, from the character array that the SQLVAR structure returns.}
  function mkVarIdent( const AString: string ): string;
{: Work in progress.}
  procedure ProcessPassiveTasks( var IsDone,
                                     IsWaiting,
                                     Terminate: boolean ); override;
{: During the OnLogin event you may still want the default or a custom form
defined in an overriden sub-class to appear.  Calling this method will invoke
the form that would normally appear if an OnLogin event were not assigned.}
  procedure ShowLoginForm; virtual;
{ properties }

{: Flag to indicate if a connection was lost.}
  property ConnectionWasLost: boolean read FConnectionWasLost;
{: Native InterBase connection handle.}
  property dbHandle: isc_db_Handle read FdbHandle write SetdbHandle;
{: Native InterBase connection handle.
<br><br>
<br>A BDE-acquired handle can be assigned to this property in order to
piggyback an IB Objects IB_Connection onto the BDE-acquired handle.  Because the
handle is shared, a call to Disconnect() will not attempt to call
isc_detach_database().
@example
<code>
function GetNativeHandle(Database: TDatabase): Pointer;
var
  length: word;
  APtr: Pointer;
begin
// This is how you get a native IB handle out of a TDatabase.
  APtr := nil;
  if Assigned( Database ) and
     Database.Connected and
     ( Database.DriverName = 'INTRBASE' ) then
    Check( DBIGetProp( HDBIOBJ( Database.Handle ), dbNATIVEHNDL,
                                                   Addr(APtr),
                                                   SizeOf( Pointer ),
                                                   length ));
  Result := APtr;
end;

procedure TfrmEvents.FormCreate(Sender: TObject);
begin
// An IB_Connection can take a raw handle from a TDatabase
  dbEvents.Connected := True;
  ibEvents.dbHandleShared := GetNativeHandle(dbEvents);
// Register the events to receive notification.
  IB_Events.RegisterEvents;
end;

procedure TfrmEvents.FormDestroy(Sender: TObject);
begin
// Before the TDatabase closes be sure to clear the dbHandle out of the
// IB_Connection. Otherwise, it may attempt an operation after the handle
// has become invalid.
  ibEvents.dbHandleShared := nil;
end;
</code>}
  property dbHandleShared: isc_db_Handle write SetdbHandleShared;
{: Pointer to the native InterBase connection handle.}
  property PdbHandle: pisc_db_Handle read GetPdbHandle;
{: Property that returns True if the current connection is shared.}
  property IsHandleShared: boolean read FIsHandleShared write FIsHandleShared;
{: This property returns the time at which the connection was last connected.}
  property LastConnected: TDateTime read FLastConnected;
{: How many LoginAttempts were made?.}
  property LoginAttemptsDone: word read FLoginAttemptsDone;
{: This property returns the name that the user typed into the login dialog.
<br><br>
If the LoginUsernamePrefix is not used then this will be the same as Username.}
  property LoginUsername: string read GetLoginUsername write SetLoginUsername;
{: This property returns the Role that the user typed into the login dialog.
<br><br>
If the LoginSQLRolePrefix is not used then this will be the same as SQLRole.}
  property LoginSQLRole: string read GetLoginSQLRole write SetLoginSQLRole;
{: The version of the password that will be saved to the DFM file.  You should
really ignore this property.  It is public for access if required but you must
remember that the value may be obscured or encrypted.}
  property SavedPassword: string read GetSavedPassword
                                 write SetSavedPassword;
{: Returns the number of transactions that are associated with the connection.}
  property TransactionCount: integer read GetTransactionCount;
{: Returns a transaction by its index within the array of all transactions
associated with the connection.}
  property Transactions[index: integer]: TIB_Transaction read GetTransaction;
{: This property reveals how many transactions the connection has started. It
is the actual count of physical transaction handles that have been requested
from the server, not the number of explicit transactions the user may be in.}
  property StartedTransactionCount: integer read FStartedTransactionCount;
{: Returns the number of statements that are associated with the connection.
<br><br>
This also includes datasets since they are inherited from statements.}
  property StatementCount: integer read GetStatementCount;
{: Returns a statement by its index within the array of all statements
associated with the connection.
<br><br>
Be aware that <ul>
<li>because the TIB_Dataset class is a descendant of the TIB_Statement
class, they will all show up in this array together.
<li>the two internal cursors that each TIB_BDataset object maintains will show
up as additional statements in this array.
<li>if you are using the TDataset-based components, only the internal cursors
of these components will show up in this index of statements. There is not yet a
way to get an index of just TDataset-based components.
</ul>}
  property Statements[index: integer]: TIB_Statement read GetStatement;
{: Returns the number of datasets that are associated with the connection.}
  property DatasetCount: integer read GetDatasetCount;
{: Returns a dataset by its index within the array of all datasets
associated with the connection.
Be aware that <ul>
<li>the two internal cursors that each TIB_BDataset object maintains will show
up as additional statements in this array.
<li>if you are using the TDataset-based components, only the internal cursors
of these components will show up in this index of statements. There is not yet a
way to get an index of just TDataset-based components.
</ul>}
  property Datasets[index: integer]: TIB_Dataset read GetDataset;
{: Status of this component's connection.
<br><br>
It can be very useful to reference this property in code that is associated
with events generated by this component.}
  property ConnectionStatus: TIB_ConnectionStatus read FConnectionStatus;
{: Property that returns a number of connection characteristics.}
  property Characteristics: TIB_ConnectCharacteristics
      read GetCharacteristics;
{: This property determines the string used to locate the database to
attach to.
<br><br>
It is coordinated with the other properties like Path, Protocol, Server, etc.
If you change one, the change is automatically reflected in the other related
properties.
<br><br>
DatabaseName has also been included for VCL compatibility.}
  property Database: string read GetDatabase write SetDatabase stored false;
{: This property returns True if the connection has an active transaction
in it.}
  property HasActiveTransaction: boolean read GetHasActiveTransaction;
{: Setting to tell if the server is using the old parameter ordering method or
the corrected one.}
  property OldParameterOrder: boolean read FOldParameterOrder;  
{: Property to get the usernames logged on to the current database.
<br><br>
This seems to only provide a listing of users who are logged in to the server
from the machine that is making the request. You only get a complete listing
if you run the program from the server locally.
<br><br>
I suspect that this is an IB bug and that it will be fixed in a future release.}
  property Users: TStrings read GetUsers;
{: This property stores all of the information that IBO needs to know about the
metadata of the database. Since it is cached here, all metadata queries will be
performed once per each connection only.
<br><br>
Ideally, for very low-bandwidth connections I want to have the metadata
information stored persistently so that the queries do not need to be done.}
  property SchemaCache: TIB_SchemaCache read FSchemaCache;
{: Property where the parameter values of the current AliasName are stored.  It
is only valid to access this property from a client that has the BDE
configuration file (IDAPI.cfg or IDAPI32.cfg) on board which contains a correct
BDE Alias configuration for this connection.  The BDE itself is not required.}
  property AliasParams: TIB_StringList read GetAliasParams;

published

{: Determine the session object this connection uses.}
  property IB_Session;

{: This property provides the ability to use a BDE Alias for the database
connection string and other limited values.
<br><br>
The only items that are considered right now are the following parameters:
<br>
<br>   BDE_SERVER_NAME - for the complete connection path to the database.
<br>   BDE_USER_NAME - for the username used to connect to the database.
<br>   BDE_PASSWORD - for the password to be used.
<br>   BDE_ROLE_NAME - for the SQL ROLE that is to be used to connect.
<br><br>
It is only valid to access this property from a client that has the BDE
configuration file (IDAPI.cfg or IDAPI32.cfg) on board which contains a correct
BDE Alias configuration for this connection.  The BDE itself is not required.}
  property AliasName: string read FAliasName write SetAliasName;

{: This property is work in progress.
<br><br>
It will be used to allow a connection handle to be dropped and picked back up
again as needed. For now it is forced to always be true.}
  property KeepConnection: boolean read FKeepConnection
                                   write SetKeepConnection
                                   default true;
{: Property that determines whether this component should have its focusing
announced throughout the session when it becomes the focused connection.}
  property AnnounceFocus: boolean read FAnnounceFocus
                                  write SetAnnounceFocus
                                  default false;
{: Determines whether or not IBO will cache acquired statement handles.
<br><br>
This can help improve performance of applications closing and opening forms,
where each form allocates and deallocates statement handles.}
  property CacheStatementHandles: boolean read FCacheStatementHandles
                                          write SetCacheStatementHandles
                                          default true;
{: Designed to allow a transaction to be used by statements and datasets that
don't explicitly define their own.}
  property DefaultTransaction: TIB_Transaction read FDefaultTransaction
                                               write SetDefaultTransaction;
{: Provides the escape character to be used in "LIKE" specifications during
ExtractSQLWhere (the building of select statements during search mode).}
  property EscapeCharacter: char read FEscapeCharacter
                                 write FEscapeCharacter
                                 default '\';
{: This property is required to tell whether or not entries have been made in
these connection-level properties of using domain names or SQL types:
<br>
<br>  ColumnAttributes
<br>  DefaultValues
<br>  FieldsAlignment
<br>  FieldsCharCase
<br>  FieldsDisplayLabel
<br>  FieldsGridLabel
<br>  FieldsGridTitleHint
<br>  FieldsDisplayFormat
<br>  FieldsDisplayWidth
<br>  FieldsEditMask
<br>  FieldsReadOnly
<br>  FieldsTrimming
<br>  FieldsVisible
<br>  GeneratorLinks}
  property FieldEntryTypes: TIB_ConnectionFieldEntryTypeSet
    read FFieldEntryTypes
    write FFieldEntryTypes
    default [];
{: This property can be used to default all string/text fields to being
used as though field=NOCASE was specified in the column attributes.  The
default can be overridden in the column attributes by the use of field=YESCASE.
<br>
<br>IMPORTANT: You must still specify field=NOCASE=dupfield in the column
attributes if a special uppercase duplicate field exists for the column. }
  property DefaultNoCase: boolean read FDefaultNoCase
                                  write FDefaultNoCase
                                  default false;
{: This property sets the default behavior of whether or not the length of
strings shall be checked for an overflow of the storage space provided.  The
current default behavior is to silently ignore the problem while truncating
the new string to fit in the given storage.  If you desire to have an
exception produced instead of this silent truncation then set this property to
false.
<br><br>
Setting the FieldsTrimming property of any column will customize it and this
setting will no longer apply to those columns.}
  property DefaultNoLengthCheck: boolean read FDefaultNoLengthCheck
                                         write FDefaultNoLengthCheck
                                         default true;                                      
{: This property can be used to default all string/text fields to being
searched as though field=NOTRAILING was specified in the column attributes.
This default can be overridden in the column attributes by the use of
field=YESTRAILING.}
  property DefaultNoTrailing: boolean read FDefaultNoTrailing
                                      write FDefaultNoTrailing
                                      default false;
{: This property sets the default string trimming behavior for text columns.
You may set this to True to emulate the VCL behavior, which is to retain
the trailing blank-space padding on all char and varchar output column values,
which expands them to their maximum byte size.}
  property DefaultNoTrimming: boolean read FDefaultNoTrimming
                                      write FDefaultNoTrimming
                                      default false;
{: This is the caption used on the default login dialog.}
  property LoginCaption: string read FLoginCaption write FLoginCaption;
{: This will provide an optional help context number so that help can be
provided to the user upon logging in to their application.}
  property LoginHelpContext: integer read FLoginHelpContext
                                     write FLoginHelpContext
                                     default 0;
{: In order to have a list of SQL Role names to choose from, include them in
this property, separated by semi colons.  The user will be able to choose a
SQL Role from a combobox with these values in it.}
  property LoginSQLRoleList: string read FLoginSQLRoleList
                                    write FLoginSQLRoleList;
{: For convenience, I have resources defined with a prefix to keep them separate
per application. This makes it so that the actual value including its prefix
does not need to be displayed in full. The prefix will be added to whatever
SQL role the user selects.
<br><br>
For example, if the user selects USER from the SQL Role drop down and the
prefix is set to MYAPP_ then the database will use the SQL Role of MYAPP_USER.}
  property LoginSQLRolePrefix: string read FLoginSQLRolePrefix
                                      write FLoginSQLRolePrefix;
{: For convenience, a prefix can be added to the actual Username that the
user enters into the login dialog. I usually give all my users a prefix and
separate login for all the applications that they use. This way I know what
database they are logged into and they don't know that I have different
logins for each of the applications they use. This prefix makes it so that they
are not aware that they are actually using different sign-ons.}
  property LoginUsernamePrefix: string read FLoginUsernamePrefix
                                       write FLoginUsernamePrefix;
{: If true a login prompt will be presented to the user for entering a username
and password.
<br><br>
Use the OnLogin event if you want to take specific control over how the user
is prompted to enter in their login information.}
  property LoginPrompt: boolean read FLoginPrompt
                                write FLoginPrompt
                                default false;
{: Property that indicates whether the user aborted the login proocess.}
  property LoginAborted: boolean read FLoginAborted;
{: Property that indicates whether the user will be informed of the aborted
login proocess.}
  property LoginAbortedShowMessage: boolean read FLoginAbortedShowMessage
                                            write FLoginAbortedShowMessage
                                            default false;
{: Property that determines how many failed login attempts a user may have
before the database will be made inaccessible.
Set this to 0 for infinite attempts.}
  property LoginAttempts: word read FLoginAttempts
                               write FLoginAttempts
                               default 3;
{: Property that determines whether the user is permitted to alter the path of
the database that will be used for the connection.}
  property LoginDBReadOnly: boolean read FLoginDBReadOnly
                                    write FLoginDBReadOnly
                                    default false;
{: This property is used to locate information in the system registry
for storing various information relating to login. If it is left blank, no
action will be taken to store and retrieve preferences.}
  property LoginRegKey: string read FLoginRegKey write FLoginRegKey;
{: Username of the person logging into a database. }
  property Username: string index 1 read GetPrm write SetPrm stored false;
{: Password of person logging in to the database.}
  property Password: string read GetPassword write SetPassword stored false;
{: Controls how the password is stored.
<br><br>
See the TIB_PasswordStorage type definition for detailed explanation of
each option.
<br><br>
The recommended approach is to only deploy applications with PasswordStorage
set to psNone and prompt the user for the password.  This way the password is
not actually stored in the DFM and executable at all.
<br><br>
If you want to save the password while developing the application, to prevent
needing to login every time, then set the PasswordStorage to psKeyFromUserReg.
In this mode the password is stored, but it can only be unjumbled/decrypted
on your current machine with your current operating system logon.  This will
help protect your password if you accidently distribute a DFM or executable
with the password saved.  When you have finished development and are ready to
deploy, reset the PasswordStorage to psNone.
<br><br>
IMPORTANT:  The encryption is only fairly basic, so dont rely on this to
be fully secure.  See the JumbleString function in IB_Utils.pas for more
details on how it works.
<br><br>
If security does not matter to you or your client at all, then you can set
PasswordStorage to psNotSecure.  In this mode the password is obscured, but
the key is hardcoded in IBObjects, which means that anyone could reveal the
password and use it directly in other applications to access the database.}
  property PasswordStorage: TIB_PasswordStorage read FPasswordStorage
                                                write SetPasswordStorage
                                                default psNone;
{: For remote connections, this is the name of the server where the database
resides. IF the protocol is cpLocal, this property is ignored.}
  property Server: string index 3 read GetPrm write SetPrm stored false;
{: This property allows the SQL Dialect feature of InterBase to be used.}
  property SQLDialect: smallint read GetSQLDialect
                                write SetSQLDialect;
{: This property allows the SQL ROLE feature of InterBase to be used.}
  property SQLRole: string index 4 read GetPrm write SetPrm stored false;
{: Physical path where the database resides on the server that hosts it.
<br><br>
Do not confuse this with a client's path to a GDB file on a remote server via
a mapped drive. The InterBase connect string must include the path for the
server upon which the file resides and will fail if a mapped path is provided.}
  property Path: string index 5 read GetPrm write SetPrm stored false;
{: Not sure how this is implemented but it gives the DPB as documentd in the
API guide.}
  property License: string index 6 read GetPrm write SetPrm stored false;
{: Name of the SYSDBA for the connection.
<br><br>
I'm not sure of the effect of including a SYSDBA with a USERNAME in the same
connection. This property is as documented in the API guide.}
  property SysDBA: string index 7 read GetPrm write SetPrm stored false;
{: Property to determine the character set to use for the connection.
<br><br>
If this property is not used then character transliterations are not performed
by the API and an exception can result.
<br><br>
The BDE did some of its own transliterations which seemed to make it appear
unnecessary to deal with it at the database level but this is not the case.}
  property CharSet: string index 9 read GetPrm write SetPrm stored false;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.
<br><br>
Settings made in the statement or dataset's ColumnAttributes property will
take precedence over this for each individual parameter.}
  property ColumnAttributes: TIB_StringProperty read FColumnAttributes
                                                write SetColumnAttributes;
{: Global property used to centralize settings for column default values.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property DefaultValues: TIB_StringProperty read FDefaultValues
                                             write SetDefaultValues;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsAlignment: TIB_StringProperty read FFieldsAlignment
                                               write SetFieldsAlignment;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsCharCase: TIB_StringProperty read FFieldsCharCase
                                              write SetFieldsCharCase;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsDisplayLabel: TIB_StringProperty read FFieldsDisplayLabel
                                                  write SetFieldsDisplayLabel;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsGridLabel: TIB_StringProperty read FFieldsGridLabel
                                               write SetFieldsGridLabel;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsGridTitleHint: TIB_StringProperty read FFieldsGridTitleHint
                                                   write SetFieldsGridTitleHint;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsDisplayFormat: TIB_StringProperty read FFieldsDisplayFormat
                                                   write SetFieldsDisplayFormat;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsDisplayWidth: TIB_StringProperty read FFieldsDisplayWidth
                                                  write SetFieldsDisplayWidth;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsEditMask: TIB_StringProperty read FFieldsEditMask
                                              write SetFieldsEditMask;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsReadOnly: TIB_StringProperty read FFieldsReadOnly
                                              write SetFieldsReadOnly;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsTrimming: TIB_StringProperty read FFieldsTrimming
                                              write SetFieldsTrimming;
{: Global property used to centralize settings for column attributes.
<br><br>
It is possible to put entries in here based on domain names if the
FieldEntryType property is set to include handling of domain names.}
  property FieldsVisible: TIB_StringProperty read FFieldsVisible
                                             write SetFieldsVisible;
{: Global property used to centralize settings for generator mappings.}
  property GeneratorLinks: TIB_StringProperty read FGeneratorLinks
                                              write SetGeneratorLinks;
{: Property that allows an alternative message file to be used.}
  property MessageFile: string index 10 read GetPrm write SetPrm stored false;
{: This porperty determines the network protocol used for the connection.}
  property Protocol: TIB_Protocol read GetProtocol
                                  write SetProtocol
                                  stored false;
{: Property to return or set the Forced Writes flag for a database.
<br><br>
Beware how this is used.  If you set it to True or False instead of Default, the
database will change to use that particular setting every time your application
is subsequently run.
<br><br>
See IB documentation for more details.}
  property ForcedWrites: TIB_DPBFlag read GetForcedWrites
                                     write SetForcedWrites
                                     stored false;
{: This property is used to determine how the DB_KEY scope should be treated
for the database connection.
<br><br>
By default, a DB_KEY is only valid for and during a single transaction but it
is possible to be able to obtain a DB_KEY and have it remain a valid (in
scope) reference to a record by setting this property to true.
<br><br>
Caution should be used when setting this to true, because InterBase internally
holds open a transaction for the duration of the connection, thus preventing
the server from advancing the OAT (oldest active transaction). This can lead to
serious performance degradation and eventual server crash. If you use this
setting, please be sure to cycle connections periodically. I'm not sure how much
this matters in low volume update systems but it would be mandatory to avoid
these problems in a high volume system.}
  property DBKeyScope: TIB_DPBFlag read GetDBKeyScope
                                   write SetDBKeyScope
                                   stored false;
{: Property to return or set the reserve page space flag for a database.
<br><br>
Beware how this is used.  If you set it to True or False instead of Default, the
database will change to use that particular setting every time your application
is subsequently run.
<br><br>
This property isn't intended to be changed while the database is connected.
Rather, it should be set before connecting because it uses special codes in the
request to connect to the database to make these changes.
<br><br>
See IB documentation for more details.}
  property ReservePageSpace: TIB_DPBFlag read GetReservePageSpace
                                         write SetReservePageSpace
                                         stored false;
{: This gives an alias-like name to the connection. It displays the Database
property value by default.  It will even allow a new database connection string
to be supplied, although this is not the intended use of this property.
<br><br>
Included for VCL compatibility.
<br><br>
IBO checks to make sure that this name is unique for the session. If a
duplicate is entered, it will have a numeric suffix appended in order to make
it unique.}
  property DatabaseName: string read GetDatabaseName
                                write SetDatabaseName
                                stored IsDatabaseNameStored;
{: Page size for the database connected to or about to be created.
<br><br>
It is generally recognized that 4096 to 8192 are the best settings.
The InterBase default page size of 1024 is too small to be useful, while page
sizes around 10Kb and higher hamper the effectiveness of caching.}
  property PageSize: word read GetPageSize write SetPageSize stored false;
{: Sweep Interval for the database connected to. This isn't taken into
consideration when a database is created.
<br><br>
This property isn't intended to be changed while the database is connected.
Rather, it should be set before connecting because it uses special codes in the
request to connect to the database to make these changes.}
  property SweepInterval: word read GetSweepInterval
                               write SetSweepInterval
                               stored false;
{: This property gives direct access to the connection parameters stored
in the string list. It is not necessary to use this property since all of its
settings are correlated using other more convenient, type checked properties.
<br><br>
If there is an existing connection it will not be closed by making changes to
this property. Changes will affect the other related properties that resolve to
this in stringlist-based storage and will not take effect until the next time a
connection is established.
<br><br>
It could be a good way to strip a password out of memory after a connection
has been obtained.
<br><br>
Use these Delphi constants as index references for the Values[ ] array:
<br><br>
<br>  IBO_USER_NAME
<br>  IBO_PASSWORD
<br>  IBO_SERVER
<br>  IBO_PATH
<br>  IBO_LICENSE
<br>  IBO_SYSDBA
<br>  IBO_ENCRYPT_KEY
<br>  IBO_CHARACTER_SET
<br>  IBO_MESSAGE_FILE
<br>  IBO_FORCED_WRITES
<br>  IBO_BUFFERS
<br>  IBO_PAGE_SIZE
<br>  IBO_RESERVE_PAGE_SPACE
<br>  IBO_PROTOCOL
<br>  IBO_DB_KEY_SCOPE
<br><br>
  Blank is default or none.
<br><br>
IBO_TRUE and IBO_FALSE can be used for the boolean settings.

<br>
<br>
IBO_BUFFERS - Number of page buffers that the server allocates for
caching data. The effect (if any) of this setting depends on many
variables: the engine (Interbase/Firebird), the version of the engine,
the architecture (Classic/SuperServer), and server configuration
directives.
<br>
Unless you know exactly how this setting affects your particular
server, it is best not use it (simply omit it from the Params list).
<br>
For more information see:
<br>
http://www.cvalde.net/document/cache_settings_priority.htm"
}
  property Params: TIB_StringList read FParams write SetParams;
{: If this property is set to a valid path, IBO will maintain a local cache of
metadata information that will avoid the need to make metadata queries in order
to accomplish certain tasks.
<br><br>
This is a very helpful feature if your network connection is slow..
<br><br>
It is possible to use a path as generic as c:\temp because IBO will
automatically add the filename of the GDB file as a sub-directory so that all
database caches will reside in their own designated directory and not overwrite
one another's files.
<br><br>
UPDATING THE SCHEMA CACHE TO REFLECT NEW METADATA
<br><br>
If the structure of your database changes, the schema cache needs to be
regenerated.  If the IBO$SCHEMA_VERSION table is present and populated with
records (see TIB_SchemaCache.CheckSchemaVersionTable), modifying the version
numbers in this table will make sure that the corresponding items are rebuilt
the next time the schema cache is loaded by the client application.  Simply
update the records in this table in your database and IBO will do the rest!
<br><br>
On the other hand, if the IBO$SCHEMA_VERSION table is not present or has no
records, it is necessary to delete the local files that the schema cache
creates.  This can be done by calling the SchemaCache.DeleteLocalFiles
method.  The schema cache files will be automatically regenerated the next
time cached information is accessed.
<br><br>
You could also distribute the cache files with the application and place them
in the directory for the user.  Note that the version information for loaded
schema is saved in a file along with the other cache files, so you can
distribute the cache files with your application even when using the
IBO$SCHEMA_VERSION table to manage schema versioning.
<br><br>
NOTES ON MAKING CHANGES TO METADATA
<br><br>
When executing DDL statements through an IB_DSQL or other dataset component,
IBO prepares the statement individually and can know that it is a DDL type
statement being executed.  Changing metadata in this manner from within a
application will cause the entire schema cache to be reloaded.
<br><br>
To make changes to metadata from within a application without causing a reload
of the entire schema cache, use a IB_Script component to make the changes, or
use the ExecuteImmediate() method.  If the IBO$SCHEMA_VERSION table is being
used for cache version control, the appropriate items should be updated there;
otherwise, individual files can be deleted from the schema cache directory.}
  property SchemaCacheDir: string read GetSchemaCacheDir
                                  write SetSchemaCacheDir;
{: Returns or determines whether or not a connection has been obtained.}
  property Connected: boolean read GetConnected
                              write SetConnected
                              stored IsConnectedStored;
{: Text to signify the beginning of a macro. Statements that are connected to
to TIB_Connection will assume the same MacroBegin value if a different text is
not implicitly assigned to the MacroBegin property of the statement.
<br>
If a single character is used then it will automatically be doubled. So, if
you set this to '<' you will need to use '<<' in your SQL statement.}
  property MacroBegin: string read FMacroBegin
                              write SetMacroBegin
                              stored IsMacroBeginStored;
{: Text to signify the end of a macro. Statements that are connected to
TIB_Connection will assume the same MacroEnd value if a different text value is
not implicitly assigned to the MacroBegin property of the statement.
<br>
If a single character is used then it will automatically be doubled. So, if
you set this to '>' you will need to use '>>' in your SQL statement.}
  property MacroEnd: string read FMacroEnd
                            write SetMacroEnd
                            stored IsMacroEndStored;

{: This is designed to allow IBO to properly handle the server and how it deals
with parsing and ordering input parameters. The old style had it based in a
convoluted nested way and FB 1.5 has been changed to deal with parameters in a
straight linear way.
<br>
Because of this change, IBO will not work with the new method now so it needs
to know which behavior the server has.
<br>
Setting it to poAuto will have IBO try and figure out what is being used.
There is a slight overhead because it will submit a statement to the server
for each new connection made to figure this out. If you set it to either
poOld or poNew then this will suppress that query to determine it and
just proceed with that behavior. Of course you will have to be correct or else
your application will fail to operate correctly. poAuto is recommended so that
your application will behave appropriately no matter what server and version
and settings you are operating with.}
  property ParameterOrder: TIB_ParameterOrderFlag read FParameterOrder
                                                  write FParameterOrder
                                                  default poAuto;

{ New Events }

{: Connection event notification.}
  property BeforeExecDDL: TIB_ConnectionEvent
     read FBeforeExecDDL
    write FBeforeExecDDL;
{: Connection event notification.}
  property AfterExecDDL: TIB_ConnectionEvent
     read FAfterExecDDL
    write FAfterExecDDL;
{: Connection event notification.}
  property BeforeConnect: TIB_ConnectionEvent
     read FBeforeConnect
    write FBeforeConnect;
{: Connection event notification.}
  property AfterConnect: TIB_ConnectionEvent
     read FAfterConnect
    write FAfterConnect;
{: Connection event notification.}
  property BeforeDisconnect: TIB_ConnectionEvent
     read FBeforeDisconnect
    write FBeforeDisconnect;
{: Connection event notification.}
  property AfterDisconnect: TIB_ConnectionEvent
     read FAfterDisconnect
    write FAfterDisconnect;
{: Connection event notification.}
  property BeforeCreateDatabase: TIB_ConnectionEvent
     read FBeforeCreateDatabase
    write FBeforeCreateDatabase;
{: Connection event notification.}
  property AfterCreateDatabase: TIB_ConnectionEvent
     read FAfterCreateDatabase
    write FAfterCreateDatabase;
{: Connection event notification.}
  property BeforeDropDatabase: TIB_ConnectionEvent
     read FBeforeDropDatabase
    write FBeforeDropDatabase;
{: Connection event notification.}
  property AfterDropDatabase: TIB_ConnectionEvent
     read FAfterDropDatabase
    write FAfterDropDatabase;
{: Connection event notification.}
  property OnConnectedChanged: TIB_ConnectionEvent
     read FOnConnectedChanged
    write FOnConnectedChanged;
{: This event is triggered when a database connection is being established
and the LoginPrompt property is set to true.
<br><br>
It allows a custom dialog to be shown to the user instead of the default one.
If this event is assigned then the default dialog is not shown.}
  property OnLogin: TIB_LoginEvent
     read FOnLogin
    write FOnLogin;
{: This event is triggered when the user fails all attempts to log in.
<br><br>
It is fired not at each failed attempt but only upon an ultimate failure.}
  property OnLoginFailure: TIB_ConnectionEvent
     read FOnLoginFailure
    write FOnLoginFailure;
{: This event is triggered when a control that is attached through this
connection becomes focused.}
  property OnGainFocus: TIB_ConnectionEvent read FOnGainFocus
                                            write FOnGainFocus;
{: This event is triggered when a control that is attached to another
connection becomes focused and this connection is losing focus.}
  property OnLoseFocus: TIB_ConnectionEvent read FOnLoseFocus
                                            write FOnLoseFocus;
{: This event is used to further refine the DPB that is used in the API call
to establish the database connection.
<br><br>
The IB_SQL sample application has an example of how to use this event.}
  property OnCustomizeDPB: TIB_CustomizeDPBEvent read FOnCustomizeDPB
                                                 write FOnCustomizeDPB;
{: This event gives the developer the ability to enhance and extend the search
buffer processing capabilities of IBO.}
  property OnProcessSearchBuffer: TIB_ProcessSearchBuffer
      read FOnProcessSearchBuffer
     write FOnProcessSearchBuffer;
{: This event gives the developer the ability to define a SoundEx parsing
routine to be used during searches.
<br><br>
Simply parse the SourceStr parameter using the same routine as implemented on
this connection's server.  Return the value as a string.
<br><br>
So if your SoundEx algorithm usually returns an integer or small-integer value
return ResultStr = IntToStr( val );
<br><br>
The ReturnStr value is used in a DSQL WHERE clause and would have been
converted to text anyway.  By providing the interface in this way IBO supports
SoundEx routines that do want to return string values.
<br><br>
For more information refer to the TC_SoundEx and TC_SoundExMax routines
provided in the IB_Parse unit. }
  property OnSoundExParse: TIB_SoundExParse
      read FOnSoundExParse
     write FOnSoundExParse;
{: This event gives the developer the ability to define a SoundEx parsing
routine that supports range selection.  This is NOT a standard SoundEx
capability, so if your algorithm does not support this simply leave this
property undefined - and the OnSoundExParse property will be used in its
place, giving you the same result as usual.  If your algorithm does support
range selection then, if the NOTRAILING attribute is defined on a search
column the SQL generated will select WHERE Fld_SX >= SoundExValue AND
Fld_SX <= SoundExMaxValue.
<br><br>
For more information refer to the TC_SoundEx and TC_SoundExMax routines
provided in the IB_Parse unit. }
  property OnSoundExMaxParse: TIB_SoundExParse
      read FOnSoundExMaxParse
     write FOnSoundExMaxParse;
{: This event gives an opportunity to reject or modify a DML cache entry that
is coming in from the external environment. These will typically be generated
from the TIB_SyncCursor but not necessarily so.}
  property OnReceiveDMLCache: TIB_ReceiveDMLCacheEvent
      read FOnReceiveDMLCache
     write FOnReceiveDMLCache;
{: This event is used in order to hook in your own customized routine in order
to handle non-standard collations for incremental searching and other areas
where IBO needs to know what string is greater than another string.
<br><br>
This one is for handling strings in a case insensitive manner.}
  property OnCustomCompareText: TIB_CompareTextEvent
      read FOnCustomCompareText
     write FOnCustomCompareText;
{: This event is used in order to hook in your own customized routine in order
to handle non-standard collations for incremental searching and other areas
where IBO needs to know what string is greater than another string.
<br><br>
This one is for handling strings in a case sensitive manner.}
  property OnCustomCompareStr: TIB_CompareTextEvent
      read FOnCustomCompareStr
     write FOnCustomCompareStr;

{: This property is used in order to return the highest char for the current
column's collation order. This is important for horizontal dataset refinement.
If this isn't properly handled then it is possible to see a truncated dataset
when the Last method is called.}     
  property OnGetHighCollateChar: TIB_GetHighCollateChar
      read FOnGetHighCollateChar
     write FOnGetHighCollateChar;

{$IFNDEF HELPSCAN}
  property _ConnectionLinkCount: integer read GetConnectionLinkCount
                                         write flag_junk_value
                                         stored false;
  property _TransactionCount: integer read GetTransactionCount
                                      write flag_junk_value
                                      stored false;
  property _StatementCount: integer read GetStatementCount
                                    write flag_junk_value
                                    stored false;
  property _DatasetCount: integer read GetDatasetCount
                                  write flag_junk_value
                                  stored false;
{$ENDIF}
{$I IB_Component.PBL}
end;

// IBA_Connection.IMP

{                                                                              }
{ TIB_ConnectionLink                                                           }
{                                                                              }

{: Event type for the TIB_ConnectionLink component.}
  TIB_ConnectionLinkEvent = procedure( Sender: TIB_ConnectionLink;
                                       AConnection: TIB_Connection ) of object;

{: Event type for the TIB_ConnectionLink component.}
  TIB_ConnectionLinkNotifyEvent = procedure( Sender: TIB_ConnectionLink;
                                             AEvent: TIB_ConnectionEventType )
                                                                      of object;

{: This component provides a way to make forms and components or controls
that contain an instance of this component connection-aware.}
TIB_ConnectionLink = class( TIB_Component )
private
{ Property Storage Fields }
  FIB_Connection: TIB_Connection;
  FReceiveFocus: boolean;
{ Event Storage }
  FOnReceiveFocus: TIB_ConnectionLinkEvent;
  FOnProcessEvent: TIB_ConnectionLinkNotifyEvent;
{ Property Access Methods }
  function GetConnected: boolean;
  function GetConnectionStatus: TIB_ConnectionStatus;
  procedure SetReceiveFocus( AValue: boolean );

protected
{ Inherited Methods }
  procedure Loaded; override;
  procedure SetSession( ASession: TIB_Session ); override;
{ System Methods }
  procedure DoReceiveFocus( C: TIB_Connection ); virtual;
  procedure ProcessEvent( AEvent: TIB_ConnectionEventType ); virtual;
{ Property Access Methods }
  procedure SetConnection( AValue: TIB_Connection ); virtual;

public
{ Inherited Methods }
  destructor Destroy; override;
{: Returns True if the referenced IB_Connection is connected.
<br><br>
If a connection is not referenced then it returns False.}
  property Connected: boolean read GetConnected;
{: Returns the status of the referenced connection.
<br><br>
If a connection is not referenced then it returns csDisconnected.}
  property ConnectionStatus: TIB_ConnectionStatus read GetConnectionStatus;

{: Reference to the IB_Connection or IB_Database for which to notify events.}
  property IB_Connection: TIB_Connection read FIB_Connection
                                         write SetConnection;
{: Determines whether this component will listen to and respond to the
focusing of IB_Connection or IB_Database components.}
  property ReceiveFocus: boolean read FReceiveFocus
                                 write SetReceiveFocus
                                 default false;
  
{ Events }

{: If ReceiveFocus is true when a new IB_Connection or IB_Database
component receives focus, this event will be triggered and an action can
take place.
<br><br>
By default the newly focused connection will be assigned as the referenced
connection.}
  property OnReceiveFocus: TIB_ConnectionLinkEvent read FOnReceiveFocus
                                                   write FOnReceiveFocus;
{: This provides a hook so that events can be captured and processed from this
component. }
  property OnProcessEvent: TIB_ConnectionLinkNotifyEvent read FOnProcessEvent
                                                         write FOnProcessEvent;
end;

// IBA_ConnectionLink.IMP

{                                                                              }
{ TIB_Transaction                                                              }
{                                                                              }

{: Exception class for the TIB_Transaction component.}
EIB_TransactionError = class( EIB_Error );
{: Status of the transaction.
<br><br>
<br>tsNone - No transaction has been physically or logically started.

<br>tsInactivePending - A transaction is in the process of physically starting.

<br>tsInactive - Transaction has been started but no DML or DDL has been
executed causing it to become active.

<br>tsActivePending - A dataset in the transaction is in an edit state that will
cause the transaction to become active if it is to be successfully posted.

<br>tsActive - DML or DDL has been executed causing transaction
activation. Either AutoCommit is false or the StartTransaction method was
called allowing DML & DDL chages to be held within transaction control of the
application. If AutoCommit were true then there would be an immediate SavePoint
and the state would go back to tsInactive or tsActivePending.
<br>
<br>tsSavePointPending - A call to SavePoint has been made and it is in the
process of executing it.

<br>tsCommitRetainingPending - Transaction is about to commit but be retained.

<br>tsCommitPending - Transaction is about to commit and end.

<br>tsCommitRefreshPending - Transaction is about to commit and end except the
datasets will all be refreshed. This happens when Refresh( true ) is called.

<br>tsRollbackRetainingPending - Transaction is about to rollback and retained.

<br>tsRollbackPending - Transaction is about to rollback and end.

<br>tsRollbackRefreshPending - Transaction is about to rollback and end except
all datasets will be refreshed in the process.
This happens when Refresh( false ) is called.}
TIB_TransactionState = ( tsNone,
                         tsInactivePending,
                         tsInactive,
                         tsActivePending,
                         tsActive,
                         tsSavePointPending,
                         tsLosePointPending,
                         tsCommitRetainingPending,
                         tsCommitPending,
                         tsCommitRefreshPending,
                         tsRollbackRetainingPending,
                         tsRollbackPending,
                         tsRollbackRefreshPending );
{: All of the different notification messages generated by the
TIB_Transaction component and propagated to the TIB_TransactionLink class.}
TIB_TransactionEventType = ( tetBeforeAssignment,
                             tetAfterAssignment,
                             tetBeforeStart,
                             tetAfterStart,
                             tetBeforeCommit,
                             tetAfterCommit,
                             tetBeforeCommitRetaining,
                             tetAfterCommitRetaining,
                             tetBeforeSavePoint,
                             tetAfterSavePoint,
                             tetBeforeRollback,
                             tetAfterRollback,
                             tetBeforeRollbackRetaining,
                             tetAfterRollbackRetaining,
                             tetBeforeLosePoint,
                             tetAfterLosePoint,
                             tetBeforeEnd,
                             tetAfterEnd,
                             tetOnPostPendingChanged,
                             tetOnCachedUpdatePendingChanged,
                             tetOnStatusChanged );
{: Isolation levels for the transaction.
<br><br>
<br>tiConcurrency - Read-Repeatable or Snapshot.
<br>tiConsistency - Forced-Repeatable or Forced Snapshot.
<br>tiCommitted - Read-Committed.}
TIB_Isolation = ( tiConcurrency,
                  tiConsistency,
                  tiCommitted );
{: Event type used by most events for the TIB_Transaction component.}
TIB_TransactionEvent = procedure( Sender: TIB_Transaction ) of object;
{: Event type used by some events for the TIB_Transaction component.}
TIB_TransactionCanPauseEvent = procedure(     Sender: TIB_Transaction;
                                          var AllowPause: boolean  ) of object;
{: Event type used by TIB_Transaction class to pass in custom TPB settings.
<br><br>
AConnection is either nil or the connection to which the TPB will be applied.
<br><br>
If it is nil then TPB customizations will be applied to all of the
connections that belong to this transaction.}
TIB_CustomizeTPBEvent = procedure (     Sender: TIB_Transaction;
                                        AConnection: TIB_Connection;
                                    var BufInd: longint;
                                    var Buffer: pchar ) of object;
{: This component is used in order to store all the settings necessary for
management of transaction timeouts.}
TIB_TimeoutProps = class ( TPersistent )
private
  FAllowCheckOAT: word;
  FAttempt: word;
  FAttemptMaxRows: longint;
  FAttemptTicks: word;
  FAttemptRetry: word;
  FNextAttempt: word;
  FPromptUser: dword;
  FPromptUserDuration: word;
  FPromptUserRetry: word;
  FNextPromptUser: word;
  FForceClosed: dword;
  FDisableCheckOATFromError: boolean;
published
{: This property determines the amount of time in seconds IBO will wait 
before it will physically end a transaction that is no longer needed by the
system.
<br><br>
This is a useful tuning mechanism for dealing with IBO's automatic OAT
advancement capabilities.
<br><br>
While it is desirable to avoid having a transaction stay open for too long it is
also undesirable to have to start and stop a transaction for every little thing
that is done.
<br><br>
What I have done is hooked up a session level timer to feed the transaction with
notifications in which it will check if there are transactions that could be
ended that haven't been yet. This is the amount of time IBO will wait before it
kills an unneeded transaction.
<br><br>
Use the ForceClosed property to setup a time to force the transaction to
end in order to guarantee that the OAT will be able to advance.}
  property AllowCheckOAT: word read FAllowCheckOAT
                               write FAllowCheckOAT
                               default 120;
{: This is the point in time when IBO will begin to attempt to close the
transaction if at all possible. By default it will check
for datasets that are set to CommitAction caFetchAll but not yet fetched
to EOF. It will start causing fetches to take place in the background, to the
point that eventually all the records are fetched, thus allowing the cursor to
be closed.  When that happens, the transaction can then be closed.
<br><br>
This mode still behaves in a non-intrusive manner but it tends to get more
aggressive about what it tries to accomplish so that the transaction can be
ended.
<br><br>
The Attempt behavior is to check for any datasets set to caFetchAll.  For any
that it finds, it begins fetching records to try and get the transaction freed
up.  It will try and do anything else in a non-intrusive manner to get conditions right for allowing the transaction to close.
<br><br>
It is performing "background" fetches without using multi-threading.
Multi-threading is out since I don't want to impose critical sections all
over the place. <br><br>
It hooks into the session timer to feed individual row fetches.  It allows the
dataset to fetch only for the duration of time that you specify in Attempt.
To make it a totally background procedure, callbacks are disallowed during that
time.}
  property Attempt: word read FAttempt write FAttempt default 1200;
{: While attempting to fetch in records from datasets that do not have the
caFetchAll CommitAction set it is possible for a user to open a very
large dataset containing millions of rows. This could result in pulling down
potentially all those rows from the server without ever reaching the end of the
dataset.  The client machine's memory would probably be exhausted before EOF was
 reached, anyway.
<br><br>
This property is to limit the number of rows that IBO will fetch while in
the process of automatically attempting to create the conditions for allowing
the transaction to be cycled.}
  property AttemptMaxRows: longint read FAttemptMaxRows
                                   write FAttemptMaxRows default 5000;
{: This is the duration allowed for whatever activities are going on in order
to create the conditions for allowing the transaction to be closed.  It is in
milliseconds (1000ths of a second), giving very fine control over just how much
time the system is interrupted while attempting to process the stuff holding
the transaction up.
<br><br>
It is multiplied a little when trying to get caRefresh configured datasets to
cooperate and attempt to get them fetched to the EOF. Its better if they can
avoid doing a refresh if possible so that the dataset won't keep a transaction
open right after a Commit takes place. So, I just buy a little time that I
assume would go into the process of actually refreshing the dataset. It's
kind of a gamble I guess. Perhaps I should put this behavior under property
control as well...}
  property AttemptTicks: word read FAttemptTicks
                              write FAttemptTicks default 150;
{: This is the Retry allowed for whatever activities are going on in order to
create the conditions for allowing the transaction to be closed. It is
the time of inactivity between the background operations working towards getting
all dataset's cursors closed.  If any attempt to fetch rows fails to get them
all, it will try again in after the period of time defined by this property.}
  property AttemptRetry: word read FAttemptRetry write FAttemptRetry default 5;
{: This is the point in time that IBO will actually get intrusive about
prompting the user to get on with their work and get the transaction closed.
<br><br>
The default behavior is to invoke the EndWithConfirm method if the
OnTimeoutPromptUser event is not assigned. If they choose Cancel, nothing
will happen. They will be prompted again after the PromptUserRetry period of
seconds has transpired.
<br><br>
It would be appropriate to plug in your own custom algorithm for interacting
with the user of your application to get them to resolve their transaction.}
  property PromptUser: dword read FPromptUser write FPromptUser default 0;
{: Period in seconds between IBO's prompts to the user to resolve the
outstanding transaction.
<br><br>
At present, no system action is triggered by this setting. It is here for your
own convenience and use. I plan eventually to plug a procedure in here that
allows a timeout on the EndWithConfirm method and provides a default value upon
the dialog timing out.}
  property PromptUserDuration: word read FPromptUserDuration
                                    write FPromptUserDuration
                                    default 15;
{: Amount of time in seconds that IBO waits before retrying prompting the user
to resolve their transaction.}
  property PromptUserRetry: word read FPromptUserRetry
                                 write FPromptUserRetry
                                 default 60;
{: This property determines the period in seconds IBO will wait before it forces
 a transaction to end, without regard for user activity.  Once the transaction
 has been physically started for the specified period of time it will be forced
 closed.
<br><br>
By default it is set to 0 so that no action will be taken.
<br><br>
<b>All datasets of this transaction will be closed as a result of this
action.</b>  If AutoCommit is set true, a transaction Close will attempt a
commit and if an exception is raised it will force a rollback.}
  property ForceClosed: dword read FForceClosed write FForceClosed default 0;
end;

{: This component is designed to encapsulate all of the InterBase API's
transaction functionality. It provides all properties, methods and events
necessary to have full control over transaction capabilities.
<br><br>
Here are some hints for working with IB Objects transactions:
<br><br>
With IBO three conceptual aspects of transaction need to be considered.
They are the physical transaction, the logical transaction and whether the
transaction is explicit.  Each has its own behavior and in many cases the
behaviors overlap.  Other factors, such as isolation, also affect the behaviors.
<br><br>
<b>Physical Transactions</b>
<br><ul>
<li>IBO automatically takes care of starting the physical transaction.  It is
not necessary to start the physical transaction explicitly yourself.
<li>It is the physical transaction which needs to be kept as short as possible
so that the server is not prevented from doing its garbage collection.
<li>It is where the settings that provide the isolation, lockwait, recversion,
and so on, take effect.
<li>The physical transaction forms the foundation for the logical and explicit
transactions.</ul>
<br><br>
<b>Long Transactions</b>
A physical transaction of long duration can lead to severe performance
degradation and, eventually, a server crash if it is left for more than a day or
 so in a moderately active database environment. A high-volume database could
 show signs of degradation within a day.
<br><br>
<b>End of the Physical Transaction</b>
A physical transaction is ended when Commit[Retaining], Rollback[Retaining],
Refresh() or Close is called. The same methods also end a logical unit of
work and an explicit transaction. 
<br><br>
<b>Oldest Active Transaction (OAT) Management</b>
OAT (Oldest Active Transaction) Management is IBO's set of automated features
that close the physical transaction for you.  Provided you avoid or carefully
manage the cases where the OAT management is suspended, you will not have any
problems with long physical transactions in your applications.
<br><br>
Here are the cases that prevent IBO from automatically advancing the OAT:
<ol>
<li><b>You are not using cached updates, AutoCommit is false and you post any
change to the database.</b>
<br>This flags the transaction as active (tsActive).  The OAT stuff is suspended
whilst a transaction's state is tsActive. You must call Commit[Retaining],
Rollback[Retaining], LosePoint, SavePoint or Refresh() to resolve the active
state of the transaction and allow the OAT stuff to resume.
<br>
<li><b>You are using PessimisticLocking and you have a row in dssEdit state.</b>
<br>In order to give duration to the lock, the transaction must be held open.
As soon as the row is posted or cancelled, the lock ends, the former rules apply
and OAT behavior resumes.
<br>
<li><b>You use the tiConcurrency isolation level.</b><br>OAT will not advance,
because tiConcurrency tells the server that you want a snapshot view of the
database. If physical transactions were to able to come and go in order to
advance the OAT, the snapshot view would be corrupted. Each time a new
transaction is started with tiConcurrency you get a fresh view of the database
because other user's committed changes become visible.
<br>
<li><b>You have a SELECT statement that will return thousands of rows and you
open it but don't fetch all the records.</b>
<br>This forces a cursor to be held open on the server until all the rows have
been fetched.  Eventually this situation would be overcome by the background
fetching that begins taking place in order to free up the cursor. If you set the
CommitAction to caInvalidateCursor then it will just get refreshed for you and
there won't be a problem.
<p>It is generally inadvisable to leave large datasets open where all
records are not fetched in.</p>
</ol>
<br>
Be sure to get familiar with the TimeOutProps settings because it is possible
there to ensure that the OAT advancement issues can be dealt with.
<br><br>
<br>
<b>Logical Transactions</b>
<br>
Consider a logical transaction to be the unit of work that the user is
performing. IBO will automatically keep track of your logical transaction for
you.
<br><br>
<b>Implicit Logical Transaction</b>
Use the TransactionIsActive property to determine whether you are in an implicit
logical transaction.<ul>
<li>If AutoCommit is true, the implicit logical transaction will be limited to
the duration of time that any dataset is in an edit state. As soon as it is
posted, the transaction is auto-committed and the implicit logical transaction
ends.
<br><br>
<li>If AutoCommit is false then the implicit logical transaction will start from
the moment at which a dataset is put into an edit state and will persist after a
change is posted.  It will not finish until the transaction is ended by
explicitly calling one of the methods to end it. <br>
<li>If a dataset is cancelled and no other changes had been posted then the
implicit logical transaction will be ended automatically.
</ul>
<br><br>
<b>Explicit Transactions</b>
<ul>
<li>An explicit transaction is initiated by calling the StartTransaction method.
It will persist until one of the methods to end the unit of work is called. <br>
<li> LosePoint and SavePoint does not end an explicit transaction.<br>
<li>The InTransaction property is used exclusively to determine whether you are
in an explicit transaction. It does not indicate the status of an implicit
logical transaction or of the underlying physical transaction.<br>
<li>AutoCommit=True behavior is suspended during an explicit transaction and all
changes posted to the server remain in the transaction, i.e. they are not
committed when a dataset's Post method is called.<li>
</ul>
<br><br>
<b>AutoCommit</b><br>
<ul>
<li>Setting AutoCommit to True allows you to avoid taking explicit control of
logical transactions. It will generate an immediate SavePoint when you activate
the transaction by posting a change to a dataset, executing a DML statement,
etc. Everything becomes committed on the server the moment it is executed.
In these conditions, there is no logical transaction to worry about.
<br><br>
<li>If AutoCommit is false, you need to call LosePoint, SavePoint,
CommitRetaining, Commit, RollbackRetaining, Rollback or Refresh() in order to
process a unit of work. These methods all end the logical transaction whether
it is implicit or explicit.
</ul>}
TIB_Transaction = class( TIB_Component )
private
{ Utility Storage }
  flag_junk_value: integer;
{ Property storage fields }
  FtrHandle: isc_tr_handle;
  FConnectionLinkList: TList;
  FStatementList: TList;
  FDatasetList: TList;
  FTransactionLinkList: TList;
  FTransactionState: TIB_TransactionState;
  FReadOnly: boolean;
  FIsolation: TIB_Isolation;
  FRecVersion: boolean;
  FLockWait: boolean;
  FAutoCommit: boolean;
  FServerAutoCommit: boolean;
  FAnnounceFocus: boolean;
  FPostPendingCount: integer;
  FCachedUpdatePendingCount: integer;
  FConfirmCancelPrompt: TStrings;
  FConfirmClosePrompt: TStrings;
  FTPBLength: smallint;
  FDMLCache: TIB_TransactionDMLCache;
  FWantPostPendingChanged: integer;
  FWantCachedUpdatePendingChanged: integer;
  FActivateCount: cardinal;
  FStartedDateTime: TDateTime;
  FLastStarted: TDateTime;
  FLastStopped: TDateTime;
  FPaused: integer;
  FPauseDisabled: integer;
  FResumeFromCommit: boolean;
  FIsPausePending: boolean;
  FOpenCursors: integer;
  FInTransaction: boolean;
  FClosePending: boolean;
  FOATPending: boolean;
  FPessimisticLockCount: integer;
  FTimeoutProps: TIB_TimeoutProps;
  FConnectionWasLost: boolean;
{ Event storage fields }
  FBeforeStart: TIB_TransactionEvent;
  FAfterStart: TIB_TransactionEvent;
  FBeforeCommitRetaining: TIB_TransactionEvent;
  FAfterCommitRetaining: TIB_TransactionEvent;
  FBeforeSavePoint: TIB_TransactionEvent;
  FAfterSavePoint: TIB_TransactionEvent;
  FBeforeLosePoint: TIB_TransactionEvent;
  FAfterLosePoint: TIB_TransactionEvent;
  FBeforeRollbackRetaining: TIB_TransactionEvent;
  FAfterRollbackRetaining: TIB_TransactionEvent;
  FBeforeCommit: TIB_TransactionEvent;
  FAfterCommit: TIB_TransactionEvent;
  FBeforeRollback: TIB_TransactionEvent;
  FAfterRollback: TIB_TransactionEvent;
  FBeforeEnd: TIB_TransactionEvent;
  FAfterEnd: TIB_TransactionEvent;
  FOnGainFocus: TIB_TransactionEvent;
  FOnLoseFocus: TIB_TransactionEvent;
  FOnCustomizeTPB: TIB_CustomizeTPBEvent;
  FOnPauseChanged: TIB_TransactionEvent;
  FOnGetCanPause: TIB_TransactionCanPauseEvent;
  FOnTimeoutPromptUser: TIB_TransactionEvent;
{ Property Access Methods }
  procedure SetStarted( AValue: boolean );
  function GetStarted: boolean;
  function GetPtrHandle: pisc_tr_handle;
  function GetPdbHandles( Index: integer ): pisc_db_handle;
  function GetConnectionCount: integer;
  function GetConnection( Index: integer ): TIB_Connection;
  function GetConnectionIndex( Index: TIB_Connection ): integer;
  function GetStatementCount: integer;
  function GetStatement( Index: integer ): TIB_Statement;
  function GetDatasetCount: integer;
  function GetDataset( Index: integer ): TIB_Dataset;
  function GetTransactionLinkCount: integer;
  function GetTransactionLink( Index: integer ): TIB_TransactionLink;
  function GetTransactionIsActive: boolean;
  procedure SetConfirmCancelPrompt( AValue: TStrings );
  procedure SetConfirmClosePrompt( AValue: TStrings );
  procedure ResetTransactionState( LogicalEnd: boolean );
  function GetIsPaused: boolean;
  function GetIsPauseDisabled: boolean;
  function GetTimeActive: TDateTime;
  function GetIB_Connection1: TIB_Connection;
  procedure SetIB_Connection1( AValue: TIB_Connection );
  function GetIB_Connection2: TIB_Connection;
  procedure SetIB_Connection2( AValue: TIB_Connection );
  procedure SetTimeoutProps( AValue: TIB_TimeoutProps );
protected
{ Connection handling stuff }
  procedure SysAddConnection( NewConnection: TIB_Connection ); //~virtual;
  procedure SysRemoveConnection( OldConnection: TIB_Connection ); //~virtual;
  procedure SysRemoveAllConnections; //~virtual;
  function GetAutoCommit: boolean; virtual;
  function IsAutoCommitStored: boolean; virtual;
{ Property Access Methods }
  procedure SetTransactionState( AValue: TIB_TransactionState ); //~virtual;
  procedure SetAnnounceFocus( AValue: boolean ); //~virtual;
  procedure SetAutoCommit( AValue: boolean ); virtual;
  procedure SetServerAutoCommit( AValue: boolean ); virtual;
  procedure SetReadOnly( AValue: boolean ); //~virtual;
  procedure SetIsolation( AValue: TIB_Isolation ); //~virtual;
  procedure SetRecVersion( AValue: boolean ); //~virtual;
  procedure SetLockWait( AValue: boolean ); //~virtual;
  function GetCanPause: boolean; //~virtual;
  procedure DoPauseChanged; //~virtual;
  procedure IsPausedError;
  function GetIB_Connection: TIB_Connection; virtual;
  procedure SetIB_Connection( AValue: TIB_Connection ); virtual;
{ Inherited Methods }
  procedure SetSession( ASession: TIB_Session ); override;
  function NeedTimerNotifications: boolean; override;
{ System Methods }
  function SysStart: boolean; //~dynamic;
  procedure SysBeforeStart; //dynamic;
  procedure SysFailedStart; //~dynamic;
  procedure SysAfterStart; //~dynamic;
  procedure SysCommit( ARefreshing: boolean ); //~dynamic;
  procedure SysCommitRetaining( ASavePointOnly: boolean ); //virtual;
  procedure SysRollback( ARefreshing: boolean ); //~dynamic;
  function SysCommitBegin( ARefreshing: boolean ): boolean;
  procedure SysCommitEnd( ARefreshing: boolean );
  function SysRollbackBegin( ARefreshing: boolean ): boolean;
  procedure SysRollbackEnd( ARefreshing: boolean );
  procedure SysRollbackRetaining( ALosePointOnly: boolean ); //~virtual;
  procedure SysBeforeEnd; //~dynamic;
  procedure SysAfterEnd( WasCommitted: boolean ); //~dynamic;
  procedure SysPostAll( IncludeCachedUpdates: boolean ); //~dynamic;
  procedure SysCancelAll( IncludeCachedUpdates: boolean ); //~dynamic;
  procedure SysAdjustPendingCount; 
  procedure SysAdjustPostPendingCount( Adj: integer );
  procedure SysAdjustCachedUpdatePendingCount( Adj: integer );
  procedure SysBeforeExecDDL; //~dynamic;
  procedure SysAfterExecDDL; //~dynamic;
  procedure SysProcessCommitAction( const ABeforeEnd,
                                          ACommitChanges,
                                          ARefreshing,
                                          ARetaining: boolean );
  procedure SysTimeOutAttempt( var Done: boolean );
  procedure SysTimeOutPromptUser;
  function CheckLostConnection: boolean;
{ API Level methods }
  procedure API_Start;
  procedure API_Commit;
  procedure API_CommitRetaining;
  procedure API_Rollback;
  procedure API_RollbackRetaining;
  procedure API_ExecuteImmediate( const ACommand: string; AParam: PXSQLDA );
{ Event Dispatch Methods }
  procedure DoBeforeStart; //~dynamic;
  procedure DoAfterStart; //~dynamic;
  procedure DoBeforeCommit; //~dynamic;
  procedure DoAfterCommit; //~dynamic;
  procedure DoBeforeCommitRetaining; //~dynamic;
  procedure DoAfterCommitRetaining; //~dynamic;
  procedure DoBeforeSavePoint; //~dynamic;
  procedure DoAfterSavePoint; //~dynamic;
  procedure DoBeforeLosePoint; //~dynamic;
  procedure DoAfterLosePoint; //~dynamic;
  procedure DoBeforeRollbackRetaining; //~dynamic;
  procedure DoAfterRollbackRetaining; //~dynamic;
  procedure DoBeforeRollback; //~dynamic;
  procedure DoAfterRollback; //~dynamic;
  procedure DoBeforeEnd; //~dynamic;
  procedure DoAfterEnd; //~dynamic;
  procedure DoGainFocus; //~dynamic;
  procedure DoLoseFocus; //~dynamic;
  procedure DoCustomizeTPB(     AConnection: TIB_Connection;
                            var AIndex: longint;
                            var ABuffer: pchar );
{ Linked Event Dispatch Methods }
  procedure ProcessEvent( AEvent: TIB_TransactionEventType ); //~virtual;
  procedure ProcessConnectionEvent( Sender: TIB_ConnectionLink;
                                    AEvent: TIB_ConnectionEventType );//~virtual;
public

{ Inherited Methods }

  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;

{ New methods }

{: This method is used to apply updates for selected datasets in a
specified order.
<br><br>
To have all datasets for the transaction handled in no specific order, pass in
an empty set.}
  procedure ApplyUpdates( const ADatasets: array of TIB_BDataset );
{: This method is used to cancel updates for selected datasets in a specified
order.
<br><br>
To have all datasets for the transaction handled in no specific order, pass in
an empty set.}
  procedure CancelUpdates( const ADatasets: array of TIB_BDataset );
{: This method adds a new connection to use for a multi-database transaction.}
  procedure AddConnection( New: TIB_Connection );
{: This method removes a connection from the list of connections to span.}
  procedure RemoveConnection( Old: TIB_Connection );
{: This method is used to add a DML cache item to the list so that when the
transaction is committed it will announce these changes to other transactions
in the same connection that have an isolation of tiCommitted.
<br><br>
This is done automatically if you set the appropriate announce DML cache flags
in the dataset. If you need some behavior that is different from the default
behavior, call this method from the OnDMLCacheAnnounceItem event handler.}
  procedure AddDMLCacheItem(       AConnection: TIB_Connection;
                                   ADataset: TIB_Dataset;
                             const AKeyFieldNames: string;
                                   AKeyFieldValues: variant;
                                   ADMLItemType: TIB_DMLCacheItemType );
{: This method starts what I call an explicit logical transaction. It causes the
InTransaction property to return True. The explicit logical transaction then
persists  until Commit, CommitRetaining, Rollback, RollbackRetaining, Close or
Refresh() is called.
<br><br>
If AutoCommit is set to true, autocommitting behavior is temporarily suspended
by calling this method, allowing all changes to be held within a logical
transaction. Once the logical transaction is ended, auto commit behavior will
resume.
<br><br>
An exception will be raised if StartTransaction has already been called, so it
is advisable to check the status of the InTransaction property before calling
this method.
<br><br>
If StartTransaction is never called, the InTransaction property will always
return false, even if changes are posted to the server or are pending.
<br><br>
Instead of calling StartTransaction, it is possible to rely on the implicit
logical transaction that IBO maintains for you automatically. In this case, if
AutoCommit is false, use the TransactionIsActive property (<b>not</b> the
InTransaction property) to determine whether something has actually been sent to
the server or (if a dataset is still in an edit state) is pending.}
  procedure StartTransaction;
{: Some statements may process an alteration to data or metadata that IBO is not
smart enough to detect. This could be due to calling the ExecuteImmediate()
method where a DML or DDL is performed. It could also be a case where a stored
procedure is used as a SELECT proc in a TIB_Cursor and it also has DML
statements in it. In such cases it is necessary to activate the transaction
manually, to let IBO know something is happening that necessitates setting the
transaction active.
<ul>
<li>If AutoCommit is true and StartTransaction wasn't previously called this
will cause an immediate SavePoint to be generated which will cause the Status to
go back to tsInactive or tsActivePending depending on the states of the
datasets.
<br><br>
<li>If AutoCommit is false or a logical transaction was explicitly started the
Status will be set to tsActive and TransactionIsActive will now indicate True.
</ul>
IBO calls this method internally whenever a dataset posts a change or a DML
statement is executed successfully. It also calls this method when a stored
procedure is executed and the StoredProcHasDML property is set to true.}
  procedure Activate;
{: This method causes a post of all datasets in an edit state and then makes all
posted changes in the transaction committed on the server. This call retains the
current physical transaction so it doesn't require that open datasets be closed
or have all their records fetched into the buffers.
<br><br>
This method is ideal for when you don't want the datasets to be closed but need
all changes posted up to this point to be committed.
<br><br>
Because calling this method does not end the transaction it is still holding up
garbage collection. It is vital for any application that may be persistently
open for a long period of time to ensure that an occasional hard Commit or
Rollback is performed so that the OAT (oldest active transaction) can be
advanced and enable garbage collection to take place.
<br><br>
This is especially critical for apps that run 24X7.}
  procedure CommitRetaining;
{: This method posts any pending edits, inserts and deletes and then causes a
commit, totally ending both the logical and physical aspects of the current
transaction.
<br><br>
Commit also causes the datasets to perform their CommitAction setting.<ul>
<li>By default, all native IB_Dataset components are closed.
<li>By default, the TDataset-based datasets use caInvalidateCursor, which leaves
them open.
</ul>The CommitAction property on datasets determines what behavior they should
display when the transaction is committed or rolled back. It may be desirable
for some datasets to remain open in spite of the end of the transaction.
CommitAction can be set to allow that to happen.}
  procedure Commit;
{: This method is the same as CommitRetaining except that it does not require
that the datasets of the transaction all be posted first. Its job is simply to
perform a call to the API function that does a commit retaining on the server.
So, if the transaction was active it goes back to the inactive or active pending
state, according to whether or not a dataset needs to be posted.
<br><br>
A call to the SavePoint method does NOT actually end the logical transaction,
even though you could say that it does end a physical unit of work.
<br><br>
AutoCommit does a SavePoint in order to make the new committed changes effective 
immediately, even before the AfterPost event handler is called.  This makes the
new changes visible to others if AfterPost sends notification after a DML
statement is processed.}
  procedure SavePoint;
{: This method is the same as RollbackRetaining except that it does not require
that the datasets of the transaction all be cancelled first. Its job is simply
to perform a call to the API function that does a rollback retaining on the
server.
So, if the transaction was active it goes back to the inactive or active pending
state, according to whether or not a dataset needs to be posted.
<br><br>
A call to the LosePoint method does NOT actually end the logical transaction,
even though you could say that it does end a physical unit of work.
}
  procedure LosePoint;
{: This method causes a rollback, ending both the physical and logical aspects
of the current transaction. It also cancels any pending inserts, edits or
deletes for all datasets that are a part of this transaction.
<br><br>
By default, Rollback also causes the datasets to all be closed. The CommitAction
property on the datasets determines what action they should take when their
physical transaction is ended. It may be desirable for some datasets to remain
open in spite of the end of the transaction. The CommitAction allows for that to
happen, either by fetching all records or by performing a refresh after the
rollback - your choice.
<br><br>
One of the pitfalls of the BDE is that, when Commit or Rollback is called, it
will force a FetchAll of all datasets in order to preserve its cursor and keep
the dataset Active. IBO gives you more careful control over such action.
In some cases it can be a valid approach but in others it is a severe
performance trap.
<br><br>
To work around this problem, you can set the soft-commit flag at a global-driver
level.  Unfortunately, the soft-commit flag prevents the OAT from advancing
properly.  This isn't desirable either, so with the BDE there really is no
winning.}
  procedure Rollback;
{: This method cancels any pending inserts, edits or deletes and performs a
rollback, but the datasets are not all closed.
<br><br>
This method's behavior varies according to InterBase version:
<ul>
<li>For IB 5.x and earlier, it emulates cursor retention by refreshing all the
datasets and maintaining their current cursor positions wherever possible. The
datasets will actually be refreshed, because it has to perform a hard rollback
which does end the physical transaction.
<br><br>
<li>InterBase 6 supports cursor retention at the API level so this method maps
into that functionality. Thus, for InterBase 6 and later, both the cursors and
the physical transaction will be retained.
</ul>
It will still be necessary to refresh the datasets.  IBO does not attempt to
keep track of all the changes made during a transaction (e.g. in a cache
somewhere) in order to provide capability for reverting them out of the client
buffers - a good example of the optimistic nature of the client/server
environment.}
  procedure RollbackRetaining; 
{: This method causes a transaction-wide refresh of all buffered datasets. It is
equivalent to opening and closing all datasets, as occurs when Refresh is called.
<br><br>
It ends the current transaction and starts a new one so that all of the
previously opened datasets can be refreshed according to their RefreshAction
property setting. Thus, the normal state changes that would attend a Rollback
or Commit can be avoided.
<br><br>
CommitChanges parameter indicates whether to Commit changes (if True) or
Rollback (if False).}
  procedure Refresh( CommitChanges: boolean );
{: This method permits the transaction Isolation property to be changed even
while the transaction is active. It operates essentially the same as the
Refresh() method but changes the Isolation level before refreshing the datasets.
<br><br>
The transaction does have to be closed in order for this to take place so if
there are changes pending they will either need to be committed or rolled back.
<br><br>
CommitChanges parameter indicates whether to Commit changes (if True) or
Rollback (if False).}
  procedure ChangeIsolation( NewIsolation: TIB_Isolation;
                             CommitChanges: boolean );
{: This method causes a transaction to end.  If AutoCommit is True, it ends
with a Commit;  otherwise it ends with a Rollback.  If an exception is raised
during the Commit, a rollback is performed automatically.
<br><br>
This is typically only used when the object is being destroyed or its
connection is being closed and an exception is undesirable.}
  procedure Close;
{: This method is great for a UI when a physical transaction needs to be
resolved and user input is desired.
<br><br>
This call will always result in the physical end of the transaction unless
cancel is pressed. <br>
NOTE :: To end just the logical transaction and not the physical
use the EndWithConfirm method instead of this one.}
  function CloseWithConfirm: integer;
//function CloseWithConfirm: TModalResult;
{: This method is great for a UI when a transaction needs to be resolved
and the user input is desired.
<br><br>
Keep this in mind:  if the changes are not to be saved then a RollbackRetaining
is performed which may end the physical transaction. Please see the information
on the RollbackRetaining method to see how its behavior varies according to the
version of InterBase you are using.}
  function EndWithConfirm: integer;
//function EndWithConfirm: TModalResult;
{: This is a quick way to execute a statement without having to create a
statement object. It will execute the statement for each connection the
transaction is involved in. If you have more than one connection and it fails
you should actually perform a rollback because there isn't a way to know which
database the error occurred in.}
  procedure ExecuteImmediate( const ACommand: string );   
{: All datasets that are associated with this transaction will be posted.}
  procedure PostAll;
{: All datasets that are associated with this transaction will be cancelled.}
  procedure CancelAll;
{: This method causes the transaction to be the focused transaction for the
session.}
  procedure SetFocus;
{: This method announces the event that prompts all transaction links to update
their status.
<br><br>
It is possible for a script to execute a COMMIT WORK statement which
will cause a transaction to end. As a result, the transaction handle is cleared
to nil, but the IB_Transaction component is not aware that this happened. So,
after running a script, use this method to have a TIB_Transaction instance check
whether it is still up to date.}
  procedure UpdateStatus;
{: This method will increment the flag that determines whether event
notifications should be sent to the OnPostPendingChanged events of the
transaction links.}
  procedure BeginPostPendingNotify;
{: This method will decrement that flag that determines whether event
notifications should be sent to the OnPostPendingChanged events of the
transaction links.}
  procedure EndPostPendingNotify;
{: This method will increment that flag that determines whether event
notifications should be sent to the OnCachedUpdatePendingChanged events of the
transaction links.}
  procedure BeginCachedUpdatePendingNotify;
{: This method will decrement that flag that determines whether event
notifications should be sent to the OnCachedUpdatePendingChanged events of the
transaction links.}
  procedure EndCachedUpdatePendingNotify;
{: Call Pause( true ) to commit a transaction without notifying any datasets
that the transaction has stopped.  <br>
Call Pause( false ) to rollback a transaction without notifying any datasets
that the transaction has stopped.
<br>While the transaction is paused, the Started property will return false and
IsPaused will return true.  The TransactionState property will report the status
of the transaction as it was before being paused.
<br><br>
Call Resume to have the transaction restored to its earlier state,
including having all datasets that were opened refreshed.
<br><br>
This is essentially like the Refresh method split into two parts.  The Pause
that closes the transaction and the Resume that reopens and refreshes the
transaction.
<br><br>
Any attempt to start/activate the transaction while it is paused will result in
a "Transaction Paused" exception.
<br><br>
Returns true if the transaction became paused, otherwise false.}
  function Pause( CommitChanges: boolean ): boolean;
{: See description for Pause function.
<br><br>
Returns true if the transaction is no longer paused, false if the transaction
is still paused (possibly due to multiple depth of pause calls).}
  function Resume( WithRestart: boolean ): boolean;
{: Call this to disable pause processing - usually to prevent a long running
report or procedure being interrupted by a call to Pause.  Call EnablePause
to re-enable Pause capability for the transaction.
<br><br>
DisablePause/EnablePause would usually be code in a try/finally block to
ensure that the transaction can be paused after the long running process
has completed.
<br><br>
If DisablePause is called when the Transaction is already paused then
the standard "Transaction Paused" exception is raised.}
  procedure DisablePause;
{: See DisablePause for details. }
  procedure EnablePause;
{: This method is used in the processing to control the mechanisms to keep the
OAT advancing. It is mainly used internally and should never really need to be
called from an application.
<br><br>
The method checks to see if the current physical transaction can be ended
internally. If there are no changes pending a commit nor any open cursors that
need to be preserved, IBO can automatically end the transaction and allow the
OAT to advance. The next time any dataset needs a transaction, it is
automatically started again. This makes the OAT management transparent to the
application code.}
  procedure CheckOAT;
{: This DisableOAT method is used in the processing to control the mechanisms to
keep the OAT advancing. It is mainly used internally and should never really
need to be called from an application.
<br><br>
It can be very beneficial to performance if the OAT is temporarily disabled
whilst a large number of queries are processed. It will allow all the queries to
operate within a single physical transaction instead of having IBO start and end
a physical transaction each time a single dataset is processed.}
//  procedure DisableOAT;
{: The EnableOAT method is used in the processing to control the mechanisms to
keep the OAT advancing. It is mainly used internally and should never really
need to be called from an application.
<br><br>
Please see DisableOAT for useful comments.}
//  procedure EnableOAT;
  
{: Override of the base class method in TIB_Component.
<br><br>
This procedure is called to make sure the Transaction OAT is helped along.}
  procedure ProcessPassiveTasks( var IsDone,
                                     IsWaiting,
                                     Terminate: boolean ); override;
  
{ Properties }

{: This is the number of times during the transaction that Activate has been
called.  When it hits the peak it will simply remain at the peak rather than
roll over.
<br><br>
I use this for basic logging purposes.}
  property ActivateCount: cardinal read FActivateCount;
{: Native API transaction handle.}
  property trHandle: isc_tr_handle read FtrHandle;
{: Pointer to the native API transaction handle.}
  property PtrHandle: pisc_tr_handle read GetPtrHandle;
{: When a transaction is being physically started, this determines the size of
the buffer used to store transaction settings. In the case that a lot of
settings are going to be used, it would be important to increase this size to
accommodate it. The default size is only 1024 bytes.}
  property TPBLength: smallint read FTPBLength write FTPBLength default 1024;
{: Current state of the transaction.}
  property TransactionState: TIB_TransactionState read FTransactionState;
{: This property indicates whether there is a change posted or pending to post
for the transaction. This property works for both the implicit and explicit
logical transaction modes.
<br><br>
It is different to InTransaction in that it will still return false even if
StartTransaction has been called. InTransaction returns true once
StartTransaction has been called, even if no changes have been posted or are
even pending to post.}
  property TransactionIsActive: boolean read GetTransactionIsActive;
{: This property indicates whether an explicit logical transaction is in
progress.  It returns true only after StartTransaction has been called.
If StartTransaction has not been called it will return false, even if an
implicit logical transaction is in effect.
<br><br>
For more general control use the TransactionIsActive property, which works for
both the implicit and explicit modes of the logical transaction.}
  property InTransaction: boolean read FInTransaction;
{: This property tells whether the Close method has been called. Close will end
the transaction in such a way that there will not be any more datasets open or
in search mode after it processes.}
  property ClosePending: boolean read FClosePending;  
{: Number of connections associated with this transaction.}
  property ConnectionCount: integer read GetConnectionCount;
{: Indexed reference to the connections associated with this transaction.}
  property Connections[Index: integer]: TIB_Connection read GetConnection;
{: Index reference of a connection that may be associated to the transaction.
<br><br>
An index of -1 is returned if the connection is found to be absent from the
list.}
  property ConnectionIndex[Index: TIB_Connection]: integer
      read GetConnectionIndex;
{: Number of statements associated with this transaction.}
  property StatementCount: integer read GetStatementCount;
{: Indexed reference to the statements associated with this transaction.}
  property Statements[Index: integer]: TIB_Statement read GetStatement;
{: Number of datasets associated with this transaction.}
  property DatasetCount: integer read GetDatasetCount;
{: Indexed reference to the datasets associated with this transaction.}
  property Datasets[Index: integer]: TIB_Dataset read GetDataset;
{: Indexed reference to the pointers to the connections associated with
this transaction.}
  property PdbHandles[Index: integer]: pisc_db_handle read GetPdbHandles;
{: Number of datasets associated with this transaction that NeedToPost.}
  property PostPendingCount: integer read FPostPendingCount;
{: Number of datasets associated with this transaction that need to apply
CachedUpdates.}
  property CachedUpdatePendingCount: integer read FCachedUpdatePendingCount;
{: Indicates the number of open cursors associated with the transaction.}
  property OpenCursors: integer read FOpenCursors;  
{: Returns True if a transaction has been physically started.
<br><br>
Whether it returns True or False, an active logical transaction could
nevertheless be in progress.}
  property Started: boolean read GetStarted write SetStarted;
{: This is a datetime indicating the system time at the point when this
transaction was started.}
  property StartedDateTime: TDateTime read FStartedDateTime;
{: The datetime when the transaction was last started. (If 0 then transaction
has never been started.)  Unlike StartedDateTime, this property is set with the
actual API call which means that it always reflects the actual transaction
start time, regardless of internal processing of this class.}
  property LastStarted: TDateTime read FLastStarted;
{: The datetime when the transaction was last stopped (committed or rolledback).
(If 0 then the transaction has never been stopped.)  Unlike StartedDateTime, 
this property is set with the actual API call which means that it always
reflects the actual transaction stop time, regardless of internal processing
of this class.}
  property LastStoppped: TDateTime read FLastStopped;
{: Number of transactionLink components that reference this transaction.}
  property TransactionLinkCount: integer read GetTransactionLinkCount;
{: Array of TransactionLink objects.}
  property TransactionLinks[ Index: integer ]: TIB_TransactionLink
      read GetTransactionLink;
{: Is the transaction currently paused? }
  property IsPaused: boolean read GetIsPaused;
{: Is the transaction currently in the process of being paused? }
  property IsPausePending: boolean read FIsPausePending;
{: Has Pause been disabled? }
  property IsPauseDisabled: boolean read GetIsPauseDisabled;
{: Find out if the transaction can be paused. }
  property CanPause: boolean read GetCanPause;
{: Reports the amount of time the transaction has been active, that is,
the difference between now and the LastStarted time IF the transaction
is currently active.  If not active returns a value of 0. }
  property TimeActive: TDateTime read GetTimeActive;
{: This flag tells whether the commit being done was initiated.}
  property OATPending: boolean read FOATPending;

published
{ Properties }

{: Determine the session object this connection uses.}
  property IB_Session;

{: This property determines the primary connection on which this transaction
will be started.}
  property IB_Connection: TIB_Connection read GetIB_Connection
                                         write SetIB_Connection;
{: This property determines the first additional connection on which this
transaction will be started.}
  property IB_Connection1: TIB_Connection read GetIB_Connection1
                                          write SetIB_Connection1;
{: This property determines the second additional connection on which this
transaction will be started.}
  property IB_Connection2: TIB_Connection read GetIB_Connection2
                                          write SetIB_Connection2;
{: This property is an IBO behavioral property which does not map to any
specific feature of the InterBase API.
<br><br>
It is used to provide a simpler interface when all changes should be made
permanent as they occur.
<br><br>
This setting takes effect when a transaction is activated or closed.
<br><br>
When a transaction is physically activated, a SavePoint is generated that will
cause the transaction to go back to an inactive or active pending state.
Thus, if AutoCommit is true then the status of the transaction will never get
beyond tsActivePending unless Autocommit behaviour is suspended temporarily by a
call to StartTransaction.
<br><br>
If Close is called then AutoCommit will cause a Commit to be used to end the
transaction instead of a Rollback. If an exception is raised when committing
a rollback is performed.
<br><br>
It is not always safe to use AutoCommit true with datasets that use
PessimisticLocking.
IBO does not perform a commit retaining when the DML to place the record lock
is executed. However, once another dataset in the same transaction posts, a
SavePoint will be generated which will clear the record lock.  The effect of
this is a point of risk.  With the lock cleared, another transaction with
tiCommitted isolation, or new transactions of any isolation, will be able to
alter the row.
<br><br>
Calling the StartTransaction method temporarily suspends the auto commit
behavior until the logical transaction is ended. At that point the auto commit
behavior will resume.
<br><br>
It is a mistake to think of AutoCommit as a mechanism for ensuring that there
will be no long running transactions holding up garbage collection. The OAT
advancement is not affected by setting AutoCommit to True.  AutoCommit does not
cause the transaction to be physically ended.  It uses a SavePoint, which maps
to a call to isc_commit_retaining(). This call enables changes to be committed
to the server while preserving existing cursors.  It does not itself cause OAT
to advance. Future versions of InterBase or Firebird may have this ability.
<br><br>
IBO automatically advances the OAT as best it can when tiCommitted isolation
is used. So AutoCommit in that respect is automatic.}
  property AutoCommit: boolean read GetAutoCommit
                               write SetAutoCommit
                               stored IsAutoCommitStored;
{: Use the native AutoCommit mode of InterBase. If you are watching the SQL
trace monitor and looking for any COMMIT RETAINING calls resulting from
AutoCommit being true, you will not see any. You will only see a COMMIT or
ROLLBACK call to end the transaction. I don't bother calling anything when I
know the commit has been performed on the server automatically.
<br><br>
In some cases it is necessary to set ServerAutoCommit to true when executing
scripts that have DDL in them.  Try it, if you have a script that is giving
errors.
<br><br>
Another condition which makes a good reason to set this true is when you need to
initiate a change on the server that must be committed.  It will guarantee that,
if the statement executes properly on the server, it will be committed at the
same time.  When it is false, the client still has to issue a subsequent call to
commit the changes made by executing a statement which opens up a point of
failure.
<br><br>
Note: You cannot use PessimisticLocking when ServerAutocommit is true.}
  property ServerAutoCommit: boolean read FServerAutoCommit
                                     write SetServerAutoCommit
                                     default false;
{: This message is here to make it convenient to have generic base class forms
provide customizable feedback to the user.  So far I have one base class form
that uses it. }
  property ConfirmCancelPrompt: TStrings read FConfirmCancelPrompt
                                         write SetConfirmCancelPrompt;
{: This message is used in the dialog that accompanies the CloseWithConfirm
and EndWithConfirm methods.}
  property ConfirmClosePrompt: TStrings read FConfirmClosePrompt
                                        write SetConfirmClosePrompt;
{: Determines whether this transaction should announce that it has received
focus.}
  property AnnounceFocus: boolean read FAnnounceFocus
                                  write SetAnnounceFocus
                                  default false;
{: Makes this transaction ReadOnly at the API level.
<br><br>
It is propagated to all the datasets which are attached to this transaction.}
  property ReadOnly: boolean read FReadOnly
                             write SetReadOnly
                             default false;
{: Determines the isolation level of the transaction.
<br><br>
These are the supported modes of isolation:
<br><br>
<br>tiConcurrency - Read-Repeatable or Snapshot
<br>tiConsistency - Forced-Repeatable or Forced Snapshot (Exclusive access)
<br>tiCommitted - Read-Committed
<br><br><b>Summarizing behavior at each isolation level:</b>
<br>
<br>tiConcurrency - Read-Repeatable or Snapshot
<br>
<br>
This transaction isolation is ideal for reports or exports that need to be
able to work with a consistent view of the data in a way that will not block
others from continuing to update the data. As long as the transaction is
open you are guaranteed to get the same exact view of the data over and over
again. Essentially, you are given a true SNAPSHOT of the data.
<br><br>
You can update data from this transaction isolation and the updates made
within your own transaction context are visible. But, if another user or
transaction context has any posted OR COMMITTED changes to individual rows that
exist in your snapshot set, this isolation level will always encounter a
deadlock situation when attempting to perform an update or delete on those
changed rows.  The deadlock occurs because later transactions have added new
record versions after that of the snapshot-isolated baseline row.  Because these
changes by other transactions are not visible to the snapshot-isolated
transaction, the posting of any changes based on that older view are disallowed.
<br><br>
To restate this, changes attempted on any rows within a transaction isolated at
the tiConcurrency level will be deadlocked as a result of any committed or "in
limbo" changes on the same rows from another transaction context.  <br><br>
There is only one way to avoid such deadlocks:  close and reopen the transaction
in order to establish a more recent baseline to work from.  This, of course,
causes all of the buffered data for the datasets to be closed and requires the
user to start afresh.

<br><br>
This isolation level is not very useful for user interfaces that involve
browsing of data and "pot-shot" editing of records. As users work on the data
they will slowly build up locks, with nobody able to see others' work once it
has committed.  A user has no way to know when (or whether) to do a rollback or
commit in order to resolve deadlocks.  It is risky to impose on users this
degree of responsibility for database-level control.
<br><br>
Be aware that tiConcurrency is the default isolation level for explicitly
defined transactions when you use an IB_Transaction component yourself from the
component palette.  IBO generates an individual, internal transaction for each
IB_DSQL, IB_Cursor or IB_Query component that does not have an explicitly
defined transaction object assigned to its IB_Transaction property.
<br>
<br>tiConsistency - Forced-Repeatable or Forced Snapshot or Snapshot Table
Stability
<br><br>
This isolation is the same as tiConcurrency except that it also imposes the same
snapshot view on other transactions.  While holding its consistent view of the
set of data, it blocks any other transaction from altering the rows it holds in
that set.  I refer to it as "forced" because it forces its view of the data set
to be the same for all users and to stay that way.
<br><br>
Use this isolation ONLY when you are fully aware of its effect on all other
users of the table(s) involved in the transaction.  It will cause deadlocks
galore since it is a blocking transaction.  This could lock up a database and
keep others from making changes - consider, for example, the effect of
SELECT * FROM ATABLE.  Of course, if this is what you want to accomplish then go
for it.
<br><br>
Used well, snapshot isolation can be a powerful tool.  If you are clear about
the effects, don't be afraid of using these isolation levels for transactions
controlling individual tables for a specialized task.  tiConsistency can be an
effective way to force exclusive access to specified tables during a transaction.
<br><br>
<b>tiCommitted - Read-Committed</b>
<br><br>
This is the ideal isolation for a browsing user interface because it keeps all
committed changes in the database visible to the transaction as they occur.
The interface can be refreshed to reveal changes in the database without the
need to start a new transaction.
<br><br>
With IB_Query components, it is possible to refresh individual rows of the
dataset.  Further, by refreshing the keys of the IB_Query, it is even possible
to refresh the ordering or inclusion of rows, when the ORDER BY criteria change
or when other users have deleted and inserted rows, without having to refetch
the individual rows of the dataset.  This type of refresh causes all of the row
buffers to be stored aside, for reassociation with the record buffer in memory
when their keys are fetched from the server.
<br><br>
Read-committed isolation should not be used to run a report or an export because
changes in the data might cause the report to be skewed or inconsistent.
When designing an application, it is a good idea to have the users browsing an
IB_Query-based dataset and then, if they want to run a report, to take the SQL
of the IB_Query and assign it to an IB_Cursor under a freshly-started
transaction with tiConcurrency isolation for running the report.
<br><br>
The method AssignSQLWithSearch() provides an excellent means to transfer the SQL
from an IB_Query to an IB_Cursor, even retaining all of the search criteria of
the IB_Query.
<br><br>
Read-committed is the default isolation for the transaction that is internally
created and assigned when the IB_Transaction property of an IB_DSQL, IB_Cursor
or IB_Query is not assigned to an explicit named IB_Transaction component.  It
is done this way because the default setting for AutoCommit is changed to True
for implicit transactions.  Although AutoCommit does not demand that the
isolation level be set to tiCommitted, it is the most sensible option for this
"point-and-shoot" style of updating.
<br><br>
<b>"Dirty Read"</b>
<br>
InterBase does not support a non-isolated transaction state - a "dirty read" -
in which any transaction can read both committed and pending changes from other
transaction contexts.
<br><br>
Each transaction defines how it will be able to read data from the database.}
  property Isolation: TIB_Isolation read FIsolation write SetIsolation;
{: This property affects only transactions whose Isolation is set to tiCommitted.
It determines whether the most recent record version must be seen, or whether an
earlier record version can be seen in order to avoid a deadlock when reading
records.
<br><br>
Setting RecVersion to True will avoid deadlocks by allowing uncommitted changes
in the database to be ignored. Only the most recent committed changes will be
visible. I consider this the "friendly" mode of committed isolation.
<br><br>
If this transaction must see the most recent record version and another
transaction has an update or delete pending, a deadlock condition is generated
when this transaction encounters the row that the other transaction intends to
update or delete.  Deadlock will occur even when just selecting the data while
using tiCommitted isolation with RecVersion set to False.  Unless LockWait is
true, an immediate Deadlock is inevitable and a user will be prevented from
finishing the processing of the query.
<br><br>
Thus, when RecVersion is set to False, LockWait should usually set to True so
that queries, or multi-record updates, deletes, etc., will be able to process to
completion without being stopped by a deadlock exception.
<br><br>
It is VERY important that all transactions performing updates, deletes, etc.
have a very short duration in this type of configuration so that a client
application will not appear to be locked up indefinitely.
<br><br>
This is the only set of conditions where a deadlock can be encountered when
selecting data, other than the conflict that occurs where another transaction
was started using tiConsistency against the same table.}
  property RecVersion: boolean read FRecVersion
                               write SetRecVersion
                               default true;
{: When there is a conflict or deadlock condition this property determines
whether an error should be returned immediately or should wait until the
uncommitted changes are resolved.
<br><br>
There are cases where it knows not to wait and returns a deadlock error
immediately, regardless of this setting.
<br><br>
This property should only be set to True if you are absolutely certain that
transactions involving updates will be quickly resolved.}
  property LockWait: boolean read FLockWait write SetLockWait default false;
{ Transaction timeout properties.}
  property TimeoutProps: TIB_TimeoutProps read FTimeoutProps
                                          write SetTimeoutProps
                                          stored true;

{: General transaction event notification.}
  property BeforeStart: TIB_TransactionEvent read FBeforeStart
                                             write FBeforeStart;
{: General transaction event notification.}
  property AfterStart: TIB_TransactionEvent read FAfterStart
                                            write FAfterStart;
{: General transaction event notification.}
  property BeforeCommit: TIB_TransactionEvent read FBeforeCommit
                                              write FBeforeCommit;
{: General transaction event notification.}
  property AfterCommit: TIB_TransactionEvent read FAfterCommit
                                             write FAfterCommit;
{: General transaction event notification.}
  property BeforeCommitRetaining: TIB_TransactionEvent
    read FBeforeCommitRetaining
   write FBeforeCommitRetaining;
{: General transaction event notification.}
  property AfterCommitRetaining:  TIB_TransactionEvent
    read FAfterCommitRetaining
   write FAfterCommitRetaining;
{: General transaction event notification.}
  property BeforeSavePoint: TIB_TransactionEvent
    read FBeforeSavePoint
   write FBeforeSavePoint;
{: General transaction event notification.}
  property AfterSavePoint:  TIB_TransactionEvent
    read FAfterSavePoint
   write FAfterSavePoint;
{: General transaction event notification.}
  property BeforeLosePoint: TIB_TransactionEvent
    read FBeforeLosePoint
   write FBeforeLosePoint;
{: General transaction event notification.}
  property AfterLosePoint:  TIB_TransactionEvent
    read FAfterLosePoint
   write FAfterLosePoint;
{: General transaction event notification.}
  property BeforeRollbackRetaining: TIB_TransactionEvent
    read FBeforeRollbackRetaining
   write FBeforeRollbackRetaining;
{: General transaction event notification.}
  property AfterRollbackRetaining:  TIB_TransactionEvent
    read FAfterRollbackRetaining
   write FAfterRollbackRetaining;
{: General transaction event notification.}
  property BeforeRollback:  TIB_TransactionEvent read FBeforeRollback
                                                 write FBeforeRollback;
{: General transaction event notification.}
  property AfterRollback:   TIB_TransactionEvent read FAfterRollback
                                                 write FAfterRollback;
{: General transaction event notification.}
  property BeforeEnd:       TIB_TransactionEvent read FBeforeEnd
                                                 write FBeforeEnd;
{: General transaction event notification.}
  property AfterEnd:        TIB_TransactionEvent read FAfterEnd
                                                 write FAfterEnd;
{: General transaction event notification.}
  property OnGainFocus:     TIB_TransactionEvent read FOnGainFocus
                                                 write FOnGainFocus;
{: General transaction event notification.}
  property OnLoseFocus:     TIB_TransactionEvent read FOnLoseFocus
                                                 write FOnLoseFocus;
{: This event allows direct alteration of the TPB (Transaction Parameter
Buffer) that is passed to the API call in order to start a transaction.
<br><br>
In the case of a transaction that spans more than one database, it is possible
to customize the TPB that is associated with each database as well as the base
TPB that will be, by default, a part of the TPB for all of the databases.
<br><br>
The AConnection parameter provides the reference to the IB_Connection to which
the TPB will apply. If it is nil, customizations will be applied to all of the
connections.
<br><br>
Here is a thread I made that may shed some additional light on the subject:
<br><br>
First of all, SET TRANSACTION   RESERVING .. is an embedded SQL
statement that is processed by GPRE into the appropriate API call with the
transaction parameter block configured with the specified settings.
<br><br>
So, what we will need to do is configure the TPB (transaction parameter
block) to use the functionality that the RESERVING clause gives by adding on
the TPB items and information that will deliver this behavior.
<br><br>
Using the TIB_Transaction.OnCustomizeTPB event it is possible to override and
clarify what is being sent to the API in the TPB. In your case you will need
to add <ul>
<li>a byte code to indicate that you want to reserve some resources
(isc_tpb_lock_write), followed by
<li>a list of those resources ( a length indicator and table name ), followed by 
<li>an item indicating the protection level to assign (isc_tpb_protected). 
</ul>
The API guide describes how to set up the TPB and explains all of the different
options available. You may need to repeat this for each table that you want to
reserve.
<br><br>
If your transaction is across multiple connections, two variations of
OnCustomizeTPB will be triggered. <ul>
<li>In the first one triggered you will get a nil for the Connection, which
causes any alterations (additions) to the TPB to be applied to all of the TPBs
of the TEB array. (A TPB is generated for each connection involved in the
transaction.)
<li>Then, for each connection, another invocation of the OnCustomizeTPB is
triggered so that database-specific TPB alterations can be put in place.
</ul>
When the TPB is being interpreted the last setting wins. Thus, it is possible
to override previous TPB items and force the transaction to take on a
certain behavior specific to a given connection, just by adding what you want
at the end of the TPB specific to a particular database.
<br><br>
I have provided a helper method to assist in cramming information into a TPB.
Please look in the source at the API_Start method and use the same approach
when constructing the TPB in the event code that you write. It makes things
much cleaner and easier.}
  property OnCustomizeTPB: TIB_CustomizeTPBEvent read FOnCustomizeTPB
                                                 write FOnCustomizeTPB;
{: Notification of when the transaction is paused or resumed.  Check
the IsPaused state, it has been set before this event is called.}
  property OnPauseChanged: TIB_TransactionEvent read FOnPauseChanged
                                                write FOnPauseChanged;
{: Called to allow developer to override the conditions determining whether a
transaction can be paused.  This is only called if the current transaction state
will permit the pause to occur - so the AllowPause parameter will be true when
the event handler is called.}
  property OnGetCanPause: TIB_TransactionCanPauseEvent read FOnGetCanPause
                                                       write FOnGetCanPause;
{: Use this event to hook in your customized behavior to resolve a transaction
that needs the user's cooperation in order to end it.}
  property OnTimeoutPromptUser: TIB_TransactionEvent read FOnTimeoutPromptUser
                                                     write FOnTimeoutPromptUser;
{$IFNDEF HELPSCAN}
  property _TransactionLinkCount: integer read GetTransactionLinkCount
                                          write flag_junk_value
                                          stored false;
  property _ConnectionCount: integer read GetConnectionCount
                                     write flag_junk_value
                                     stored false;
  property _StatementCount: integer read GetStatementCount
                                    write flag_junk_value
                                    stored false;
  property _DatasetCount: integer read GetDatasetCount
                                  write flag_junk_value
                                  stored false;
{$ENDIF}
{$I IB_Component.PBL}
end;

{$IFNDEF HELPSCAN}
TIB_TransactionDefault = class( TIB_Transaction )
public
  constructor Create( AOwner: TComponent ); override;
published
  property AutoCommit default true;
  property Isolation;
end;

TIB_TransactionInternal = class( TIB_TransactionDefault )
public
  constructor Create( AOwner: TComponent ); override;
published
  property ReadOnly default true;
end;
{$ENDIF}

// IBA_Transaction.IMP

{                                                                              }
{ TIB_TransactionLink                                                          }
{                                                                              }

{: This event serves as the general event type for transaction link
components. }
TIB_TransactionLinkEvent = procedure( Sender: TIB_TransactionLink;
                                      ATransaction: TIB_Transaction) of object;

{: Event type for the TIB_TransactionLink component.}
TIB_TransactionLinkNotifyEvent = procedure( Sender: TIB_TransactionLink;
                                            AEvent: TIB_TransactionEventType ) of object;

{: This component serves as a base class or contained object for other
components needing to become transaction-aware.
<br><br>
Its role is analogous to the role of a TDataSource linking other components to a
TDataset, except that TIB_TransactionLink links other components to an InterBase
transaction.}
TIB_TransactionLink = class(TIB_Component)
private
{ Property Storage Fields }
  FIB_Transaction: TIB_Transaction;
  FNewTransaction: TIB_Transaction;
  FReceiveFocus: boolean;
{ Event Storage }
  FOnProcessEvent: TIB_TransactionLinkNotifyEvent;
  FOnReceiveFocus: TIB_TransactionLinkEvent;
protected
{ Property Access Methods }
  function GetStarted: boolean; //~virtual;
  function GetTransactionState: TIB_TransactionState; //~virtual;
  procedure SetTransaction( AValue: TIB_Transaction ); //~virtual;
  procedure SetReceiveFocus( AValue: boolean ); //~virtual;
{ Inherited Methods }
  procedure Loaded; override;
  procedure SetSession( ASession: TIB_Session ); override;
{ Event Dispatch Methods }
  procedure DoReceiveFocus( T: TIB_Transaction ); //~virtual;
  procedure ProcessEvent( AEvent: TIB_TransactionEventType ); virtual;
  
public
{ Inherited Methods }
  destructor Destroy; override;
{ Properties }
{: Returns the transaction state of the referenced transaction.}
  property TransactionState: TIB_TransactionState read GetTransactionState;
{: Returns whether or not a referenced transaction is started.}
  property Started: boolean read GetStarted;
{: This provides a hook so that events can be captured and processed from this
component. }
  property OnProcessEvent: TIB_TransactionLinkNotifyEvent read FOnProcessEvent
                                                          write FOnProcessEvent;
published
{ Properties }
{: Reference to the IB_Transaction for which this component is to receive event
notifications. }
  property IB_Transaction: TIB_Transaction read FIB_Transaction
                                           write SetTransaction;
{: Determines whether this component should receive a notification of a newly
focusing transaction component.
<br><br>
By default, a newly focusing transaction becomes referenced by this component.
<br><br>
To define a custom action to take, use the OnReceiveFocus event.}
  property ReceiveFocus: boolean read FReceiveFocus write SetReceiveFocus;
{ Events }
{: If ReceiveFocus is true then, this event will be triggered each time a new
IB_Transaction component is becoming focused.
<br><br>
If this event is assigned, the default behavior of assigning this component to
any newly focusing IB_Transaction will not be performed.}
  property OnReceiveFocus: TIB_TransactionLinkEvent read FOnReceiveFocus
                                                    write FOnReceiveFocus;
end;

// IBA_TransactionLink.IMP

{                                                                              }
{ TIB_Database                                                                 }
{                                                                              }

{: This component is intended for BDE/VCL compatibility only. It is a
TIB_Connection descendant that is equipped with its own internally-contained
TIB_TransactionDefault component. The two are welded together, emulating the
single transaction per connection construction of the VCL's TDatabase.
<br><br>
Any statement or dataset that attaches to a TIB_Database object by way of its
own IB_Connection property will have its IB_Transaction propety assigned to the
internally contained IB_Transaction instance automatically.
<br><br>
All transaction control is thereby bound at the connection level, instead of
taking advantage of InterBase's capability to support multiple transactions
per connection.
<br><br>
This could lend some simplicity to your application, if you are not concerned
with assigning tasks to discrete transactions and would prefer not to have to
worry about making sure each dataset has its IB_Transaction property properly
assigned.
<br><br>
In the case where a statement's or dataset's IB_Connection property is not a
TIB_Database class, and its IB_Transaction property is left blank, it will
create its own internal transaction.  If you made the choice to avoid working
with simultaneous transactions, this behavior could give rise to the undesirable
effect of having different statements and datasets being kept within different
transaction contexts.
<br><br>
It takes extra work and a little extra know-how to use the IB_Connection and
IB_Transaction components separately.  However, the flexibility and power it
adds to your application makes it worthwhile to avoid using the
TIB_Database class.}
TIB_Database = class( TIB_Connection )
protected
  FIB_Transaction: TIB_Transaction;
  procedure SetName( const NewName: TComponentName ); override;
  procedure SetAnnounceFocus( AValue: boolean ); override;
  procedure SetDefaultTransaction( AValue: TIB_Transaction ); override;
  function GetPtrHandle: pisc_tr_handle;
  function GetTransactionState: TIB_TransactionState;
  function GetCachedUpdatePendingCount: integer;
  function GetPostPendingCount: integer;
  function GetStarted: boolean;
  procedure SetStarted( AValue: boolean );
  function GetAutoCommit: boolean;
  procedure SetAutoCommit( AValue: boolean );
  function GetReadOnly: boolean;
  procedure SetReadOnly( AValue: boolean );
  function GetIsolation: TIB_Isolation;
  procedure SetIsolation( AValue: TIB_Isolation );
  function GetRecVersion: boolean;
  procedure SetRecVersion( AValue: boolean );
  function GetLockWait: boolean;
  procedure SetLockWait( AValue: boolean );
  function GetBeforeStart: TIB_TransactionEvent;
  procedure SetBeforeStart( AValue: TIB_TransactionEvent );
  function GetAfterStart: TIB_TransactionEvent;
  procedure SetAfterStart( AValue: TIB_TransactionEvent );
  function GetBeforeCommit: TIB_TransactionEvent;
  procedure SetBeforeCommit( AValue: TIB_TransactionEvent );
  function GetAfterCommit: TIB_TransactionEvent;
  procedure SetAfterCommit( AValue: TIB_TransactionEvent );
  function GetBeforeCommitRetaining: TIB_TransactionEvent;
  procedure SetBeforeCommitRetaining( AValue: TIB_TransactionEvent );
  function GetAfterCommitRetaining: TIB_TransactionEvent;
  procedure SetAfterCommitRetaining( AValue: TIB_TransactionEvent );
  function GetBeforeRollbackRetaining: TIB_TransactionEvent;
  procedure SetBeforeRollbackRetaining( AValue: TIB_TransactionEvent );
  function GetAfterRollbackRetaining: TIB_TransactionEvent;
  procedure SetAfterRollbackRetaining( AValue: TIB_TransactionEvent );
  function GetBeforeRollback: TIB_TransactionEvent;
  procedure SetBeforeRollback( AValue: TIB_TransactionEvent );
  function GetAfterRollback: TIB_TransactionEvent;
  procedure SetAfterRollback( AValue: TIB_TransactionEvent );
  function GetBeforeEnd: TIB_TransactionEvent;
  procedure SetBeforeEnd( AValue: TIB_TransactionEvent );
  function GetAfterEnd: TIB_TransactionEvent;
  procedure SetAfterEnd( AValue: TIB_TransactionEvent );
  function GetTransactionLinkCount: integer;
  function GetInTransaction: boolean;
  function GetTransactionIsActive: boolean;
  function GetOnSessionTimer: TIB_SessionTimerEvent;
  procedure SetOnSessionTimer( AValue: TIB_SessionTimerEvent );
  function GetTimeOutProps: TIB_TimeoutProps;
  procedure SetTimeOutProps( AValue: TIB_TimeoutProps );
  procedure GetTransIsolation( AReader: TReader );
  function GetDriverName: string;
  function CreateTransaction( AOwner: TComponent ): TIB_Transaction; virtual;
protected
{ Inherited Methods }
  procedure DefineProperties( AFiler: TFiler ); override;
public
  constructor Create( AOwner: TComponent ); override;
  procedure SetFocus; override;

{ Methods from IB_Transaction }

{: This allows a direct low-level execution of a statement.  If the transaction
is not closed by executing a COMMIT or ROLLBACK statement then the transaction
will assume either there was a DDL or DML statement executed and activate the
transaction.  This assumption wouldn't be necessary if the database engine
returned whether or not an actual update of some kind was posted on the server
but for now we just have to assume there was.  If AutoCommit is true and there
hasn't been a call to StartTransaction then a COMMIT will happen after this
call.}
  procedure ExecSQL( const ASQL: string );
{: See the help for TIB_Transaction.}
  procedure ApplyUpdates( const ADatasets: array of TIB_BDataset );
{: See the help for TIB_Transaction.}
  procedure CancelUpdates( const ADatasets: array of TIB_BDataset );
{: See the help for TIB_Transaction.}
  procedure StartTransaction;
{: See the help for TIB_Transaction.}
  procedure Activate;
{: See the help for TIB_Transaction.}
  procedure CommitRetaining;
{: See the help for TIB_Transaction.}
  procedure SavePoint;
{: See the help for TIB_Transaction.}
  procedure Commit;
{: See the help for TIB_Transaction.}
  procedure Rollback;
{: See the help for TIB_Transaction.}
  procedure RollbackRetaining;
{: See the help for TIB_Transaction.}
  procedure Refresh( CommitChanges: boolean );
{: See the help for TIB_Transaction.}
  function CloseWithConfirm: integer{TModalResult};
{: See the help for TIB_Transaction.}
  function EndWithConfirm: integer{TModalResult};
{: See the help for TIB_Transaction.}
  procedure PostAll;
{: See the help for TIB_Transaction.}
  procedure CancelAll;
{: See the help for TIB_Transaction.}
  procedure UpdateStatus; 

{ Properties }

{: BDE Compatibility property.}
  property DriverName: string read GetDriverName;
{: This property gives a reference to the internal transaction of this
component.}
  property IB_Transaction: TIB_Transaction read FIB_Transaction;
{: See the help for TIB_Transaction.}
  property PtrHandle: pisc_tr_handle read GetPtrHandle;
{: See the help for TIB_Transaction.}
  property TransactionState: TIB_TransactionState read GetTransactionState;
{: See the help for TIB_Transaction.}
  property CachedUpdatePendingCount: integer read GetCachedUpdatePendingCount;
{: See the help for TIB_Transaction.}
  property PostPendingCount: integer read GetPostPendingCount;
{: See the help for TIB_Transaction.}
  property InTransaction: boolean read GetInTransaction;
{: See the help for TIB_Transaction.}
  property TransactionIsActive: boolean read GetTransactionIsActive;

published

{ Properties }

{: Avoids errors when the form is loading.}
  property DefaultTransaction stored false;

{: See the help for TIB_Transaction.}
  property AutoCommit: boolean read GetAutoCommit
                               write SetAutoCommit
                               default true;
{: See the help for TIB_Transaction.}
  property ReadOnly: boolean read GetReadOnly
                             write SetReadOnly
                             default false;
{: See the help for TIB_Transaction.}
  property Isolation: TIB_Isolation read GetIsolation write SetIsolation;
{: See the help for TIB_Transaction.}
  property RecVersion: boolean read GetRecVersion
                               write SetRecVersion
                               default true;
{: See the help for TIB_Transaction.}
  property LockWait: boolean read GetLockWait write SetLockWait default false;

{: See the help for TIB_Transaction.}
  property TimeoutProps: TIB_TimeoutProps read GetTimeoutProps
                                          write SetTimeoutProps;
                                          
{ Events }

{: See the help for TIB_Transaction.}
  property BeforeStart: TIB_TransactionEvent read GetBeforeStart
                                             write SetBeforeStart;
{: See the help for TIB_Transaction.}
  property AfterStart: TIB_TransactionEvent read GetAfterStart
                                            write SetAfterStart;
{: See the help for TIB_Transaction.}
  property BeforeCommit: TIB_TransactionEvent read GetBeforeCommit
                                              write SetBeforeCommit;
{: See the help for TIB_Transaction.}
  property AfterCommit: TIB_TransactionEvent read GetAfterCommit
                                             write SetAfterCommit;
{: See the help for TIB_Transaction.}
  property BeforeCommitRetaining: TIB_TransactionEvent
      read GetBeforeCommitRetaining
     write SetBeforeCommitRetaining;
{: See the help for TIB_Transaction.}
  property AfterCommitRetaining: TIB_TransactionEvent
      read GetAfterCommitRetaining
     write SetAfterCommitRetaining;
{: See the help for TIB_Transaction.}
  property BeforeRollbackRetaining: TIB_TransactionEvent
      read GetBeforeRollbackRetaining
     write SetBeforeRollbackRetaining;
{: See the help for TIB_Transaction.}
  property AfterRollbackRetaining: TIB_TransactionEvent
      read GetAfterRollbackRetaining
     write SetAfterRollbackRetaining;
{: See the help for TIB_Transaction.}
  property BeforeRollback: TIB_TransactionEvent read GetBeforeRollback
                                                write SetBeforeRollback;
{: See the help for TIB_Transaction.}
  property AfterRollback: TIB_TransactionEvent read GetAfterRollback
                                               write SetAfterRollback;
{: See the help for TIB_Transaction.}
  property BeforeEnd: TIB_TransactionEvent read GetBeforeEnd
                                           write SetBeforeEnd;
{: See the help for TIB_Transaction.}
  property AfterEnd: TIB_TransactionEvent read GetAfterEnd
                                          write SetAfterEnd;
{: See the help for TIB_Transaction.}
  property OnSessionTimer: TIB_SessionTimerEvent read GetOnSessionTimer
                                                 write SetOnSessionTimer;
{$IFNDEF HELPSCAN}
  property _TransactionLinkCount: integer read GetTransactionLinkCount
                                          write flag_junk_value
                                          stored false;
{$ENDIF}                                          
end;

// IBA_Database.IMP

{                                                                              }
{ TIB_Statement                                                                }
{                                                                              }

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Wassim Haddad                                                               }
{  10-Aug-2001                                                                 }
{                                                                              }
{  Added CalculateAllFields property which dictates the behaviour of           }
{  OnCalculateField firing mode.                                               }
{                                                                              }
{******************************************************************************}

{: Exception class for the TIB_Statement based components.}
EIB_StatementError = class( EIB_Error );

{: InterBase statement types.}
TIB_StatementType = ( stUnknown,
                      stSelect,
                      stInsert,
                      stUpdate,
                      stDelete,
                      stDDL,
                      stGetSegment,
                      stPutSegment,
                      stExecProcedure,
                      stStartTrans,
                      stCommit,
                      stRollback,
                      stSelectForUpdate,
                      stSetGenerator,
                      stSavePoint );
{: Statement event types.
<br><br>
Many of these are used exclusively by the dataset class.}
TIB_StatementEventType = ( setBeforeAssignment,
                           setAfterAssignment,
                           setBeforePrepare,
                           setAfterPrepare,
                           setBeforeUnprepare,
                           setAfterUnprepare,
                           setBeforeExecute,
                           setAfterExecute,
                           setBeforeExecDML,
                           setAfterExecDML,
                           setBeforeExecDDL,
                           setAfterExecDDL,
                           setPreparedChanged,
                           setActiveChange,
                           setCheckBrowseMode,
                           setLayoutChanged,
                           setRowHeightChanged,
                           setParamsStateChanged,
                           setParamsDataChange,
                           setParamsUpdateData,
                           setParamsRefsChanged,
                           setFieldsStateChanged,
                           setFieldsDataChange,
                           setFieldsUpdateData,
                           setFieldsRefsChanged,
                           setInvalidateRows,
                           setShowNearest,
                           setFocusControl,
                           setOrderingChanged,
                           setOrderingLinkChanged,
                           setSearchingLinkChanged,
                           setUpdateSearchCriteria,
                           setHintsChanged,
                           setSelectedChanged,
                           setBufferStatusChanged,
                           setUserDefined );
{: Standard event type for the TIB_Statement class.}
TIB_StatementEvent = procedure( Sender: TIB_Statement ) of object;
{: Event type for a change in the state of the fields or params row.}
TIB_RowStateEvent = procedure( Sender: TIB_Statement;
                               Row: TIB_Row ) of object;
{: Event to create a custom field type.}
TIB_CreateColumnEvent = procedure (       Sender: TIB_Statement;
                                          ARow: TIB_Row;
                                    const ANewIndex: smallint;      
                                    const PSQLVAR: PXSQLVAR;
                                      var AColumn: TIB_Column ) of object;
{: Event type to get the value of a Macro substitution.}
TIB_MacroSubstituteEvent = procedure (       Sender: TComponent;
                                       const ATextBlock: string;
                                         var ATextResult: string ) of object;
{: Event to handle a calculated field operation.}
TIB_RowFieldEvent = procedure ( Sender: TIB_Statement;
                                ARow: TIB_Row;
                                AField: TIB_Column ) of object;
{: Types of rows.}
TIB_RowType  = ( rtParam, rtField, rtKey );
{: States that a row's buffer can be in.}
TIB_RowState = ( rsNone, rsUnmodified, rsModified );
{: Trimming options.  An exception will be raised if the length is over the
space for storage when using the xxxxCheckLen values.
<br>
<br>
See help for the FieldsTrimming property for more details.}

TIB_ColumnTrimming = ( ctNone,
                       ctNoneCheckLen,
                       ctAll,
                       ctAllCheckLen,
                       ctBoth,
                       ctBothCheckLen,
                       ctLeft,
                       ctLeftCheckLen,
                       ctRight,
                       ctRightCheckLen,
                       ctSentence,
                       ctSentenceCheckLen );
                       
{: Special strings class for handling the SQL property.}
TIB_SQLStrings = class(TIB_StringList)
private
  FStatement: TIB_Statement;
  FBegPos: array [ssSQL..ssForUpdate] of integer;
  FEndPos: array [ssSQL..ssForUpdate] of integer;
  function GetBegPos( AVal: integer ): integer;
  function GetEndPos( AVal: integer ): integer;
  function GetSQLSection( Index: integer ): string;
  function GetUnionSection( Index, UnionLevel: integer ): string;
protected
  procedure Changing; override;
  procedure AssignTo( APersistent: TPersistent ); override;
  property BegPos[ index: integer ]: integer read GetBegPos;
  property EndPos[ index: integer ]: integer read GetEndPos;
public
  procedure Assign( APersistent: TPersistent ); override;
  procedure AssignWithSearch( ASQLStrings: TIB_SQLStrings );
  property Section[ Index: integer ]: string read GetSQLSection;
  property UnionSection[ Index, UnionLevel: integer ]: string
                                                           read GetUnionSection;
end;

{: This class serves as a foundational base class for the TIB_DSQL,
TIB_Cursor and TIB_Query components.
<br><br>
It handles all of the necessary API calls to allocate, prepare and execute an
InterBase dynamic SQL (DSQL) statement. It also interprets and defines memory
buffers that are bound to column objects for both input and output handling.
<br><br>
An important thing to know about this component and its descendants is that all
output is accessed in the Fields[] array and not the Params[] array. The BDE
returns stored procedure results in the Params[] array and this is not how it
works in InterBase. I may emulate how the BDE does it at a future time but for
now when executing stored procedures get the results from the Fields[] array.
<br><br>
Before Params[] can be accessed the statement or dataset must be Prepared.
This is the step that creates objects for all of the input and output columns.}
TIB_Statement = class(TIB_Component)
private
{ Property storage fields }
  FstHandle: isc_stmt_handle;
  FIB_ConnectionLink: TIB_ConnectionLink;
  FIB_TransactionLink: TIB_TransactionLink;
  FIB_StatementLinkList: TList;
  FSQL: TIB_SQLStrings;
  FKeyRelation: string;
  FRefinedSQL: string;
  FServerSQL: string;
  FPreparedSQL: string;
  FParamValueLinks: TIB_StringList;
  FSysParamNames: TIB_StringList;
  FSysFieldNames: TIB_StringList;
  FSysTableNames: TIB_StringList;
  FSysFieldNamesNeedRefine: boolean;
  FOldParamValueLinks: TIB_StringList;
  FSelectParamsCount: integer;
  FParamCheck: boolean;
  FSQLHistory: TIB_StringList;
  FKeepSQLHistory: boolean;
  FSQLHistoryRef: integer;
  FStatementType: TIB_StatementType;
  FRowsAffected: TIB_RowsAffected;
  FStatementPlan: string;
  FPrepared: boolean;
  FSQLIsValid: boolean;
  FParams: TIB_Row;
  FParamCount: integer;
  FCursorFields: TIB_Row;
  FCursorFieldCount: smallint;
  FCursorKeyFields: TIB_Row;
  FCursorKeyFieldCount: smallint;
  FParamChar: char;
  FMacroBegin: string;
  FMacroEnd: string;
  FAssigningSQL: boolean;
  FPreparingSQL: boolean;
  FRefiningSQL: boolean;
  FRepreparingSQL: boolean;
  FUnpreparing: boolean;
  FStoredProcHasDML: boolean;
  FDatabaseName: string;
  FRetrieveDomainNames: boolean;
  FWasSingleton: boolean;
  FUnExPrmCnt: integer;
  FSQLDialect: integer;
  FSetParamAttribs: boolean;
{ Hints that appear in the TIB_NavigatorBar and other controls }
  FHints: TStrings;
{ Field attributes }
  FCalculatedFields: TIB_StringList;
  FColumnAttributes: TIB_StringList;
  FFieldsAlignment: TIB_StringList;
  FFieldsCharCase: TIB_StringList;
  FFieldsDisplayFormat: TIB_StringList;
  FFieldsDisplayLabel: TIB_StringList;
  FFieldsGridLabel: TIB_StringList;
  FFieldsGridTitleHint: TIB_StringList;
  FFieldsDisplayWidth: TIB_StringList;
  FFieldsEditMask: TIB_StringList;
  FFieldsIndex: TIB_StringList;
  FFieldsReadOnly: TIB_StringList;
  FFieldsTrimming: TIB_StringList;
  FFieldsVisible: TIB_StringList;
{ Optimization/System Flags }
  flag_statement_type_invalid: boolean;
  flag_rows_affected_invalid: boolean;
  flag_statement_plan_invalid: boolean;
  flag_prepare_after_load: boolean;
  flag_junk_value: integer;
  flag_rel_alias_invalid: boolean;
{ Event storage fields }
  FBeforePrepare: TIB_StatementEvent;
  FAfterPrepare: TIB_StatementEvent;
  FBeforeUnprepare: TIB_StatementEvent;
  FAfterUnprepare: TIB_StatementEvent;
  FBeforeExecute: TIB_StatementEvent;
  FAfterExecute: TIB_StatementEvent;
  FBeforeExecDDL: TIB_StatementEvent;
  FAfterExecDDL: TIB_StatementEvent;
  FBeforeExecDML: TIB_StatementEvent;
  FAfterExecDML: TIB_StatementEvent;
  FOnPrepareSQL: TIB_StatementEvent;
  FOnPreparedChanged: TIB_StatementEvent;
  FOnActiveChange: TIB_StatementEvent;
  FOnLayoutChanged: TIB_StatementEvent;
  FOnInvalidateSQL: TIB_StatementEvent;
  FOnParamsStateChanged: TIB_RowStateEvent;
  FOnFieldsStateChanged: TIB_RowStateEvent;
  FOnCreateColumn: TIB_CreateColumnEvent;
  FOnCalculateField: TIB_RowFieldEvent;
  FOnValidateField: TIB_RowFieldEvent;
  FOnBlobCallback: TIB_BlobCallbackEvent;
  FOnMacroSubstitute: TIB_MacroSubstituteEvent;
  FCalculateAllFields: boolean;
  FTreatSingletonEmptySetAsEof: boolean;
{ Property Access Methods }
  function GetPdbHandle: pisc_db_handle;
  function GetPtrHandle: pisc_tr_handle;
  function GetPstHandle: pisc_stmt_handle;
  function GetStatementPlan: string;
  function GetStatementLinkCount: integer;
  function GetClientSQL: string;
  procedure SetKeyRelation( const AValue: string );
  procedure SetSQLHistoryRef( AValue: integer );
  function GetSQLDialect: integer;
  function GetSQLIsRefined: boolean;
  function GetCalculatingFields: boolean; virtual;
  function GetMacroBegin: string;
  function GetMacroEnd: string;
  procedure SetMacroBegin( AValue: string );
  procedure SetMacroEnd( AValue: string);
  function IsMacroBeginStored: boolean;
  function IsMacroEndStored: boolean;
  procedure UpdateAlignment;
  procedure UpdateCharCase;
  procedure UpdateDisplayFormat;
  procedure UpdateDisplayLabel;
  procedure UpdateGridLabel;
  procedure UpdateGridTitleHint;
  procedure UpdateDisplayWidth;
  procedure UpdateEditMask;
  procedure UpdateIndex;
  procedure UpdateReadOnly;
  procedure UpdateTrimming;
  procedure UpdateVisible;
  procedure SysUpdateAlignment( AField: TIB_Column );
  procedure SysUpdateCharCase( AField: TIB_Column );
  procedure SysUpdateDisplayFormat( AField: TIB_Column );
  procedure SysUpdateDisplayLabel( AField: TIB_Column );
  procedure SysUpdateGridLabel( AField: TIB_Column );
  procedure SySUpdateGridTitleHint( AField: TIB_Column );
  procedure SysUpdateDisplayWidth( AField: TIB_Column );
  procedure SysUpdateEditMask( AField: TIB_Column );
  procedure SysUpdateReadOnly( AField: TIB_Column );
  procedure SysUpdateTrimming( AField: TIB_Column );
  procedure SysUpdateVisible( AField: TIB_Column );
  procedure RetrieveRelAliasList;
  procedure RetrieveRelAliasListFB2;
{------------------------------------------------------------------------------}
protected
{------------------------------------------------------------------------------}
  FJoinLinks: TIB_StringList;
  FReadOnly: boolean;
  FActive: boolean;
  FIgnoreLayoutChange: integer;
  // Allow a derivation to cause DoMacroSubtitue to be called whether
  // an OnMacroSubstitute event has been defined or not (for built in macros).
  FAlwaysCallMacroSubstitute: boolean;
  FBindingCursor: TIB_Dataset;
  FDefineCursorKeyFields: boolean;
  FIB_Transaction: TIB_TransactionDefault;
  FCombineDuplicateParams: boolean;
public
  FRelAliasList: TIB_StringList;
  property RelAliasList: TIB_StringList read FRelAliasList;
  property TreatSingletonEmptySetAsEof: boolean
    read FTreatSingletonEmptySetAsEof
    write FTreatSingletonEmptySetAsEof
    default false;
protected
  function IsConnectionStored: boolean;
  function IsTransactionStored: boolean;
  function IsPreparedStored: boolean;
  function IsActiveStored: boolean;
  procedure GetColumnIsReadOnly(     AColumn: TIB_Column;
                                 var AReadOnly: boolean ); virtual;
  procedure GetControlIsReadOnly(     AColumn: TIB_Column;
                                  var AReadOnly: boolean ); virtual;
  procedure DoHandleError(       Sender: TObject;
                           const errcode: isc_long;
                                 ErrorMessage,
                                 ErrorCodes: TStringList;
                           const SQLCODE: isc_long;
                                 SQLMessage,
                                 SQL: TStringList;
                             var RaiseException: boolean); override;
  procedure DoCalculateField( ARow: TIB_Row; AField: TIB_Column ); virtual;
  function DoMacroSubstitute( const ASQL: string ): string; virtual;
{ Methods }
  procedure UpdateDesigner;
  function GetSysKeyRelation: string; virtual;
  function GetDomainName( const ARelName, ASQLName: string ): string;
  function GetSQLIsAggregate: boolean; //virtual;
  function GetSQLIsSelectProc: boolean; //virtual;
  function GetSQLIsExecuteBlock: boolean; //virtual;
  procedure OnSQLChange( Sender: TObject );
  procedure LayoutChange( Sender: TObject );
  procedure HintsChange( Sender: TObject );
  procedure Notification( AComponent: TComponent;
                          Operation: TOperation); override;
  procedure SysUpdateLayout( FirstTime: boolean ); //~virtual;
  procedure SetColumnAttributes( Value: TIB_StringList ); //virtual;
  procedure SetCalculatedFields( Value: TIB_StringList ); //virtual;
  function GetDatabaseName: string; //~virtual;
  procedure SetDatabaseName( const AValue: string ); virtual;
  procedure SetFieldsAlignment( Value: TIB_StringList ); //virtual;
  procedure SetFieldsCharCase( Value: TIB_StringList ); //virtual;
  procedure SetFieldsDisplayFormat( Value: TIB_StringList ); //virtual;
  procedure SetFieldsDisplayLabel( Value: TIB_StringList ); //virtual;
  procedure SetFieldsGridLabel( Value: TIB_StringList ); //virtual;
  procedure SetFieldsGridTitleHint( Value: TIB_StringList ); //virtual;
  procedure SetFieldsDisplayWidth( Value: TIB_StringList ); //virtual;
  procedure SetFieldsEditMask( Value: TIB_StringList ); //virtual;
  procedure SetFieldsIndex( Value: TIB_StringList ); //virtual;
  procedure SetFieldsReadOnly( Value: TIB_StringList ); //virtual;
  procedure SetFieldsTrimming( Value: TIB_StringList ); //virtual;
  procedure SetFieldsVisible( Value: TIB_StringList ); //virtual;
{ Attribute Manipulation Methods }
  function GetColAttributeParams( const ACol, AParam: string ): string;
  procedure SetColAttributeParams( const ACol, AParam: string; AValue: string );
  function GetColIsAttributeSet( const ACol, AParam: string ): boolean;
  procedure SetColIsAttributeSet( const ACol, AParam: string; AValue: boolean );
{ Hints for NavBar and other Bars }
  procedure SetHints( Value: TStrings ); //virtual;
{ Property Access Methods }
  procedure SetPrepared( Value: boolean ); //virtual;
  procedure SetActive( Value: boolean ); virtual;
  function GetActive: boolean; virtual;
  procedure SetConnection( AValue: TIB_Connection ); virtual;
  function GetConnection: TIB_Connection; virtual;
  procedure SetTransaction( AValue: TIB_Transaction ); virtual;
  function GetTransaction: TIB_Transaction; virtual;
  function GetDefaultConnection: TIB_Connection; virtual;
  function GetDefaultTransaction: TIB_Transaction; virtual;
  function GetSQL: TStrings; virtual;
  procedure SetSQL( AValue: TStrings ); virtual;
  procedure SysSQLChange( Sender: TObject; var Unprepare: boolean ); virtual;
  procedure SysLayoutChange( Sender: TObject ); virtual;
  function GetStatementType: TIB_StatementType; virtual;
  function GetRowsAffected: integer; //virtual;
  function GetRowsSelected: integer; //virtual;
  function GetFields: TIB_Row; virtual;
  function GetParams: TIB_Row; //~virtual;
  function GetFieldCount: integer;
  function GetParamCount: integer;
  function GetFieldValue( const FieldName: string ): Variant;
  procedure SetFieldValue( const FieldName: string; const Value: Variant );
  function GetParamValue( const ParamName: string ): Variant;
  procedure SetParamValue( const ParamName: string; const Value: Variant );
  function GetIsSelectSQL: boolean; //virtual;
  function GetSysParamNames: TIB_StringList; //virtual;
  function GetSysFieldNames: TIB_StringList; virtual;
  function GetSysTableNames: TIB_StringList; //virtual;
{ System Methods }
  procedure SysUpdateStatementType; //virtual;
  procedure SysPrepareAfterLoad; //virtual;
  function SysAllocate: boolean; virtual;
  procedure SysDeallocate( AllowCachedHandle: boolean ); //virtual;
  function SysPrepare: boolean; virtual;
  procedure SysExecPrepare; virtual;
  procedure SysClose; virtual;
  procedure SysUnprepare; virtual;
  procedure SysBeforePrepare; virtual;
  procedure SysAfterPrepare; virtual;
  procedure SysBeforeUnprepare; virtual;
  procedure SysAfterUnprepare; virtual;
  procedure SysExecute; virtual;
  procedure SysExecuteFailed; //~virtual;
  procedure SysBeforeExecute; virtual;
  procedure SysAfterExecute; //~virtual;
  procedure SysBeforeExecuteForOutput; virtual;
  procedure SysAfterExecuteForOutput; virtual;
  procedure SysAfterExecuteWithEof; virtual;
  procedure SysBeforeExecuteWithInput; //~virtual;
  procedure SysAfterExecuteWithInput; //~virtual;
  procedure SysExecuteImmediate( const Statement: string;
                                       AParam: PXSQLDA ); //~virtual;
  procedure SysExecImmed2( Statement: string; AParam,
                                              AField: PXSQLDA ); //~virtual;
  procedure SysPreparedChanged; virtual;
  procedure SysActiveChange; virtual;
  procedure SysLayoutChanged; //~virtual;
  procedure SysPrepareSQL; virtual;
  function SysNeedToRefineSQL: boolean; virtual;
  procedure SysRefineSQL; virtual;
  procedure SysInitRawSQL; virtual;
  procedure SysGetRawSQL; //~virtual;
  function SysSubstituteMacros( const ATextBlock: string ): string; virtual;
  procedure SysFinishRawSQL; virtual;
  procedure SysFieldNamesRefine;
  procedure SysUpdateDescriptors; virtual;
  procedure SysStoreParamValueLinks; virtual;
  procedure SysRestoreParamValueLinks; virtual;
  procedure SysDescribeVARList( ARow: TIB_Row ); virtual;
  procedure SysExecStatement; virtual;
  procedure SysExecSelect; virtual;
  procedure SysPrepareFailed; virtual;
{ Row alteration event handlers }
  procedure SysParamsStateChanged( Sender: TIB_Row ); //~virtual;
  procedure SysBeforeParamsDataChange( Sender: TIB_Row;
                                       AField: TIB_Column ); virtual;
  procedure SysAfterParamsDataChange( Sender: TIB_Row;
                                      AField: TIB_Column ); virtual;
  procedure SysCursorFieldsStateChanged( Sender: TIB_Row ); //~virtual;
  procedure SysBeforeCursorFieldDataChange( Sender: TIB_Row;
                                            AField: TIB_Column ); //~virtual;
  procedure SysAfterCursorFieldDataChange( Sender: TIB_Row;
                                           AField: TIB_Column ); //~virtual;
  procedure SysFieldsStateChanged( Sender: TIB_Row ); //~virtual;
  procedure SysBeforeFieldDataChange( Sender: TIB_Row;
                                      AField: TIB_Column ); virtual;
  procedure SysAfterFieldDataChange( Sender: TIB_Row;
                                     AField: TIB_Column ); virtual;
{ Connection Link event handlers }
  procedure ProcessConnectionEvent( AConnectionLink: TIB_ConnectionLink;
                                    AEvent: TIB_ConnectionEventType ); virtual;
{ Transaction Link event handlers }
  procedure ProcessTransactionEvent( ATransactionLink: TIB_TransactionLink;
                                     AEvent: TIB_TransactionEventType ); virtual;
{ Dispatch a local event for this component.}
  procedure ProcessEvent( AEvent: TIB_StatementEvent ); //~virtual;
{ Dispatch an event for the datasource and datalink components.}
  procedure ProcessLinkEvent( AEvent: TIB_StatementEventType;
                              Info: integer ); virtual;
{ System Methods }
  function API_Prepare(     Text: PChar;
                        var InVar,
                            OutVar: smallint ): integer; virtual;
  procedure API_Execute;
  function API_QuickFetch( Exec2: boolean ): integer;
  procedure API_Execute2;
  procedure API_ExecuteImmediate( const Statement:string; AParam: PXSQLDA );
  function ISC_ExecImmed2( const AStatement: string; AParam,
                                                     AField: PXSQLDA ): integer;
  procedure API_ExecImmed2( const AStatement: string; AParam,
                                                      AField: PXSQLDA );
  procedure API_DSQL_SQL_INFO( var Items: array of Char;
                               var Buffer: array of Char);
  procedure API_CheckStatement( const AStatement: string;
                                  var errcode: longint );
{ Inherited methods }
  procedure Loaded; override;
  procedure SetSession( ASession: TIB_Session ); override;
{ Stored Internal Property Methods }
  procedure DefineProperties( Filer: TFiler ); override;
  procedure ReadOldParamData( Reader: TReader );
  procedure WriteOldParamData( Writer: TWriter );
{ Properties }
  property CursorFields: TIB_Row read FCursorFields;
  property CursorFieldCount: smallint read FCursorFieldCount;
  property CursorKeyFields: TIB_Row read FCursorKeyFields;
  property CursorKeyFieldCount: smallint read FCursorKeyFieldCount;
  property SysKeyRelation: string read GetSysKeyRelation;
  property WasSingleton: boolean read FWasSingleton;

public

{ Inherited Methods }
  constructor Create( AOwner: TComponent ); override;
  constructor CreateWithBinding( AOwner: TComponent;
                                 ADataset: TIB_Dataset ); virtual;
  destructor Destroy; override;
{: Clears out the history of statements added to the statement history.}
  procedure ClearSQLHistory;
{: Adds the current statement to the statement history depending on where the
SQLHistoryRef property is set.}
  function AddSQLHistory( ASQL: TStrings; Position: integer ): boolean;
{: Drops a string from out of the SQL History.}
  procedure RemoveSQLHistory( Position: integer );
{: Method to cause the current fields to be recalculated.}
  procedure CalculateFields; virtual;
{: Call this method to invoke the API call that will cancel the running
query on the server.
<br>
<br>
NOTE: This is for InterBase version 7 and up only.}
  procedure CancelQuery; dynamic;
{: Method to check the status of the IB_Connection reference and optionally
request that a connection be attempted.}
  function CheckConnection( RequestConnect: boolean ): boolean; //virtual;
{: Method to check the status of the IB_Transaction reference and optionally
request that a transaction be started.}
  function CheckTransaction( RequestStart: boolean ): boolean; virtual;
{: Method which allows direct access to the contents of a BLOB parameter or
field.
<br><br>
Here is some sample code showing how to use this method:
<br>
<br>procedure TIB_ColumnBlob.LoadFromStream( const AStream: TStream );
<br>var
<br>  tmpStream: TStream;
<br>begin
<br>  tmpStream := Statement.CreateBlobStream( Self, bsmWrite );
<br>  try
<br>    tmpStream.CopyFrom( AStream, 0 );
<br>  finally
<br>    tmpStream.Free;
<br>  end;
<br>end;
<br>
<br>procedure TIB_ColumnBlob.SaveToStream( const AStream: TStream );
<br>var
<br>  tmpStream: TStream;
<br>begin
<br>  tmpStream := Statement.CreateBlobStream( Self, bsmRead );
<br>  try
<br>    AStream.CopyFrom( tmpStream, 0 );
<br>  finally
<br>    tmpStream.Free;
<br>  end;
<br>end;}
  function CreateBlobStream( AColumn: TIB_Column;
                             AMode: TIB_BlobStreamMode ): TIB_BlobStream;
{: BDE/VCL compatibility method. It does the same as Execute.
<br><br>
If you are using input parameters, it is very important to be aware that IBO
will preserve parameter values.  You need to clear the values out explicitly,
even when unpreparing and preparing again.
<br><br>
The easiest ways to clear them out are by:<ul>
<li>calling the Clear method of the input parameters;  or
<li>Calling the Params.ClearBuffers( rsUnmodified ) method of the Params object.
</ul>}
  procedure ExecSQL;
{: This method will cause the server resources involved with the statement to
be freed up so that the client will not unnecessarily hold resources on the
server open.  A case for using this method is when you have a lookup dataset
that fetches all the data that it needs and will stay open for a long while.
The statement has no need to hold the server resources for a read-only dataset.
<br><br>
From IBO version 3.4 forward, this method becomes effectively obsolete since
capability was added for IBO to do as much of this as it can, automatically,
utilizing the TIB_Transaction timeout properties.}
  procedure FreeServerResources; virtual;
{: Prepare or reprepare an invalidated statement or dataset.
<br><br>
This causes the creation of all input and output columns.}
  procedure Prepare;
{: This method will close, unprepare and free up the InterBase statement
handle obtained upon allocation.
<br><br>
I now have the connection caching the statement handles so it is possible that
allocating and deallocating a statement will reuse a handle already acquired.
<br><br>
This also frees all of the input and output column buffers.
<br><br>
For datasets it also deallocates all of its auxillary statements and cursors.
<br><br>
One caution is that you should not assume that an Unprepare will set all the
input parameter values to NULL.  If you happen to prepare a new statement that
has an input parameter of the same name as one from the previous statement, the
old value will survive unless you clear it explicitly. }
  procedure Unprepare;
{: This method will execute the SQL statement.}
  procedure Execute; virtual;
{: This method allows you to execute DDL statements without having to go through
all the steps to prepare, describe, etc.
<br><br>
ExecuteDDL also notifies the connection and transaction objects that the
metadata have been altered.}
  procedure ExecuteDDL( const Statement: string );
{: This method allow you to execute DML statements without having to go through
all the steps to prepare, describe, etc.
<br><br>
ExecuteDML also notifies the statement link objects that data have been altered.}
  procedure ExecuteDML( const Statement: string; AParam: PXSQLDA );
{: This method allows immediate execution of a statement with input
parameters.}
  procedure ExecuteImmediate( const Statement: string; AParam: PXSQLDA );
{: This method will allow immediate execution of a statement with both
input and output parameters.}
  procedure ExecImmed2( const Statement: string; AParam, AField: PXSQLDA );
{: This method flags the statement or dataset as needing to be reprepared
before it is executed again.}
  procedure InvalidateSQL; dynamic;
{: This method will attempt to return a TIB_Column reference for the
field name passed in. If it cannot be found then an exception is raised.}
  function FieldByName( const AFieldName: string ): TIB_Column;
{: This method will attempt to return a TIB_Column reference for the
parameter name passed in. If it cannot be found then an exception is raised.}
  function ParamByName( const AFieldName: string ): TIB_Column; virtual;
{: This method will attempt to return a TIB_Column reference for the field
name passed in. If it cannot be found then nil is returned.}
  function FindField( const FieldName: string ): TIB_Column;
{: This method will attempt to return a TIB_Column reference for the field
name passed in in the KeyFields array.
<br><br>
If it cannot be found then nil is returned.}
  function FindKeyField( const FieldName: string ): TIB_Column; virtual;
{: This method will attempt to return a TIB_Column reference for the
parameter name passed in. If it cannot be found then nil is returned.}
  function FindParam( const FieldName: string ): TIB_Column;
{: This procedure will take a BlobNode and get the data for its BLOBID and
store it into the buffer with the size indicated.}
  procedure GetBlobNodeData( AVar: PXSQLVAR;
                             ArrayDesc: PISC_Array_Desc;
                             ABlobNode: PIB_BlobNode );
{: This procedure will take a BlobNode and "put" the data for it in a new
temporary BLOBID and store it in the BlobNode.BLOBID.}
  procedure PutBlobNodeData( AVar: PXSQLVAR;
                             ArrayDesc: PISC_Array_Desc;
                             ABlobNode: PIB_BlobNode );
{: This method populates a string list with the names of the fields.}
  procedure GetFieldNamesList( AStrings: TStrings );
{: This method populates a string list with the names of the parameters.}
  procedure GetParamNamesList( AStrings: TStrings );
{: This method populates a TList with the TIB_Column references for the
fields.}
  procedure GetFieldList( AList: TList; const FieldNames: string );
{: This method populates a TList with the TIB_Column references for the
parameters.}
  procedure GetParamList( AList: TList; const ParamNames: string );
{: This method will return a Generator value with a specified increment.}
  function GeneratorValue( const AGenerator: string;
                                 Increment: integer ): ISC_INT64;
{: This method will return a Generator value with a specified increment.}
  function Gen_ID( const AGenerator: string; Increment: integer ): ISC_INT64;
{: Begin a bracketed operation on the FieldsXXXX properties for efficiency.}
  procedure BeginLayout; dynamic;
{: End a bracketed operation on the FieldsXXXX properties for efficiency.}
  procedure EndLayout; dynamic;
{: Force the IB_Column objects in the Fields and Params properties to take
on the settings in the FieldsXXXX property settings.
<br><br>
Within a BeginLayout ... EndLayout block it is possible to make multiple
alterations to the TIB_StringProperty properties (FieldsDisplayFormat and so on)
and then have them all coordinated in a single "flush" instead of each and every
time the FieldsXXX properties are changed.
<br><br>After calling EndLayout to end the block, call this method to apply the
updated values in all of the properties named in the block operation. }
  procedure UpdateLayout;
{: Cause any controls holding changes to data to save them to the row buffer.}
  procedure UpdateRecord;
{: This function indicate whether or this statement or dataset is using the
default connection of the session.}
  function UsingDefaultConnection: boolean;
{: This function indicates whether this statement or dataset is using the
default transaction of the connection.}
  function UsingDefaultTransaction: boolean;
{: This function is for convenient access to the RelAliasList information.}
  function GetRelNameByRelAlias( const AValue: string ): string;
{: This function is for convenient access to the RelAliasList information.}
  function GetRelAliasByRelName( const AValue: string ): string;
{------------------------------------------------------------------------------}
{$IFNDEF HELPSCAN}
public
{$ELSE}
published
{$ENDIF}
{------------------------------------------------------------------------------}

{ Properties to be public for IB_DSQL }

{: Makes the contents of the SQL history list available.
<br><br>
Use the CommaText property of another stringlist to parse the statement out.}
  property SQLHistory: TIB_StringList read FSQLHistory;
{: Positions the reference into the history list. Changes made to the SQL
property will overwrite the existing setting when this property is set to an
existing entry in the list.}
  property SQLHistoryRef: integer read FSQLHistoryRef
                                  write SetSQLHistoryRef;
{: This property determines whether a SQL history is preserved for the
compoenent or not. Each time a statement is successfully prepared an entry is
made in the history list.
<br><br>
If the SQLHistoryRef has been scrolled to a previous statement in the list
then new entries are not made but the current one is overwriten.
<br><br>
This is utilized in the IB_DSQL tab of the IB_SQL utility.}
  property KeepSQLHistory: boolean read FKeepSQLHistory
                                   write FKeepSQLHistory
                                   default false;
{: Setting this to false will cause IBO to ignore all requests to retrieve the
name of the Domain for its columns. DomainName will be blank in this case.}
  property RetrieveDomainNames: boolean read FRetrieveDomainNames
                                        write FRetrieveDomainNames;
{: This gives a count of the number of input parameters which were included in
the select portion of a SELECT statement. For some reason the parser for
InterBase DSQL handles parameters in this part of the statement in a different
order so it can sometimes be necessary to give them special treatment.}
  property SelectParamsCount: integer read FSelectParamsCount;

{ Properties to be published for IB_StoredProc }

{: If a stored procedure does not perform any DML operations then it is
posssible to make it so that executing it will not activate the transaction.}
  property StoredProcHasDML: boolean read FStoredProcHasDML
                                     write FStoredProcHasDML
                                     default true;

{ Properties to be published with the IB_Cursor and IB_Query components }

{: This property can make a dataset that involves a multi-table join become
insert or delete capable. It specifies which relation the insert or delete
action should be performed on.}
  property KeyRelation: string read FKeyRelation write SetKeyRelation;

{: The various bar controls will look here for generic hint definitions.
<br><br>
Please see the source within each IB_XXXXBar.PAS file to see which constants
are used to index the custom hints.}
  property Hints: TStrings read FHints write SetHints;
{: If input parameters are used in the SQL statement, the values that need to
be assigned to these parameters can be added as string entries into this string
list. Before the dataset is opened these values are bound to the corresponding
parameters and converted as necessary.
<br><br>
After a statement has been Prepared, it is best to change parameter values by
using the Params[] property or the ParamByName() method to change the TIB_Column
instances directly.}
  property ParamValueLinks: TIB_StringList read FParamValueLinks;
{: Set to true to use the FieldsXXXX properties to initialise parameter attributes.
Param column instances are not initialised in this manner otherwise.}
  property SetParamAttribs: boolean read FSetParamAttribs write FSetParamAttribs
    default false;

{ Events to be published }

{: General statement notification event.}
  property BeforePrepare:    TIB_StatementEvent read FBeforePrepare
                                                write FBeforePrepare;
{: General statement notification event.}
  property AfterPrepare:     TIB_StatementEvent read FAfterPrepare
                                                write FAfterPrepare;
{: General statement notification event.}
  property BeforeUnprepare:  TIB_StatementEvent read FBeforeUnprepare
                                                write FBeforeUnprepare;
{: General statement notification event.}
  property AfterUnprepare:   TIB_StatementEvent read FAfterUnprepare
                                                write FAfterUnprepare;
{: General statement notification event.}
  property BeforeExecute:    TIB_StatementEvent read FBeforeExecute
                                                write FBeforeExecute;
{: General statement notification event.}
  property AfterExecute:     TIB_StatementEvent read FAfterExecute
                                                write FAfterExecute;
{: General statement notification event.}
  property BeforeExecDDL:    TIB_StatementEvent read FBeforeExecDDL
                                                write FBeforeExecDDL;
{: General statement notification event.}
  property AfterExecDDL:     TIB_StatementEvent read FAfterExecDDL
                                                write FAfterExecDDL;
{: General statement notification event.}
  property BeforeExecDML:    TIB_StatementEvent read FBeforeExecDML
                                                write FBeforeExecDML;
{: General statement notification event.}
  property AfterExecDML:     TIB_StatementEvent read FAfterExecDML
                                                write FAfterExecDML;
{: This event allows specific hooks into the process that gets and puts BLOB
segments to and from the server. It is especially handy for a progress bar.}
  property OnBlobCallback: TIB_BlobCallbackEvent read FOnBlobCallback
                                                 write FOnBlobCallback;
{: The event is used in order to provide macro substitutions when in the phase
of preparing the SQL statement for execution.
<br><br>
This will produce an event for each one of these where you can supply the
substitute values for them.
<br><br>
If you set the macro characters to a percent then this is what your SQL
statement would look like:
<br><br>
  SELECT * FROM %%MYTABLE%%
<br><br>
Warning, there is a lot of behind the scenes parsing and generation of
statements that should also be taken into consideration. It is likely a macro
value will be requested more than once for a single statement prepare.}
  property OnMacroSubstitute: TIB_MacroSubstituteEvent read FOnMacroSubstitute
                                                       write FOnMacroSubstitute;
{: General statement notification event.
<br><br>
This indicates a special phase that is performed just before sending a SQL
statement to the server to be prepared. It is possible to manipulate the SQL
property directly or to use the other properties like SQLWhere, SQLOrder and
even the special SQLWhereItems property, which allow you to adjust the statement
sent to the server for your desired effect.
<br><br>
Use this in combination with InvalidateSQL and Refresh methods if you want to
bind a control on your form to the SQL that is sent to the server. In this
event, base the alteration of the SQL on the setting in the control. Then, when
the control is changed, call InvalidateSQL and Refresh.}
  property OnPrepareSQL: TIB_StatementEvent read FOnPrepareSQL
                                            write FOnPrepareSQL;
{: General statement notification event.}
  property OnPreparedChanged: TIB_StatementEvent read FOnPreparedChanged
                                                 write FOnPreparedChanged;
{: General statement notification event.}
  property OnActiveChange: TIB_StatementEvent read FOnActiveChange
                                               write FOnActiveChange;
{: General statement notification event.}
  property OnLayoutChanged: TIB_StatementEvent read FOnLayoutChanged
                                               write FOnLayoutChanged;
{: General statement notification event.}
  property OnInvalidateSQL: TIB_StatementEvent read FOnInvalidateSQL
                                               write FOnInvalidateSQL;
{: General statement notification event.}
  property OnParamsStateChanged: TIB_RowStateEvent
      read FOnParamsStateChanged
     write FOnParamsStateChanged;
{: General statement notification event.}
  property OnFieldsStateChanged: TIB_RowStateEvent
      read FOnFieldsStateChanged
     write FOnFieldsStateChanged;
{: Method to allow customized TIB_Column classes to be used.}
  property OnCreateColumn: TIB_CreateColumnEvent read FOnCreateColumn
                                                 write FOnCreateColumn;
{: Event where code can be placed to provide values for the fields created as
a result of entering specifications in the CalculatedFields property.
<br><br>
It is very important that the calculations be done in the context of the
IB_Row parameter which is passed into the event. Otherwise, you could be
basing calculations for one row using data from another row.
<br><br>
This is possible because IBO handles lots of different row types, especially
when working with the buffered datasets.}
  property OnCalculateField: TIB_RowFieldEvent read FOnCalculateField
                                               write FOnCalculateField;
{: Event to validate the field's value. Raise an exception if it is invalid.}
  property OnValidateField: TIB_RowFieldEvent read FOnValidateField
                                              write FOnValidateField;

public

{ New Properties }

{: The prepared status of the statement or dataset can be determined
with this property.
<br><br>
This property should be true before calling ParamByName() or accessing the
Params[] array.  ParamByName() will Prepare the statement itself if necessary,
but you must test Prepared yourself for Params[] and call Prepare if it is
false. }
  property Prepared: boolean read FPrepared write SetPrepared
                                            stored IsPreparedStored;
{: When output is being requested, Active will be true if a prepared statement
was successfully executed and output exists.}
  property Active: boolean read GetActive write SetActive
                                          stored IsActiveStored;
{: Once the statement is prepared, this property returns which statement type
(stSelect, stInsert, stDelete, etc.) has been prepared.  Check out the full
range in the help for TIB_StatementType. }
  property StatementType: TIB_StatementType read GetStatementType;
{: Indicates whether the statement is a SELECT SQL statement.}
  property IsSelectSQL: boolean read GetIsSelectSQL;
{: Property which indicates whether the SQL statement is an aggregate style
SELECT statement. This could be from the inclusion of DISTINCT, COUNT(), MIN(),
MAX(), AVG() or a GROUP BY clause.}
  property SQLIsAggregate: boolean read GetSQLIsAggregate;
{: This property returns True if the SELECT statement is selecting data from a
stored procedure.}
  property SQLIsSelectProc: boolean read GetSQLIsSelectProc;
{: This property returns True if the statement is EXECUTE BLOCK }
  property SQLIsExecuteBlock: boolean read GetSQLIsExecuteBlock;
{: If a DML statement is executed, this property returns the number of rows
that were affected by it.}
  property RowsAffected: integer read GetRowsAffected;
{: This result accompanies RowsAffected and returns the number of rows that were
selected on the server in order to complete the operation performed.}
  property RowsSelected: integer read GetRowsSelected;
{: If a SELECT or query based DML is prepared, an optimization PLAN will be
resolved for it. This property returns the optimization plan assigned to the
statement by the server.}
  property StatementPlan: string read GetStatementPlan;
{: This is the InterBase statement handle obtained as a result of allocating
it.}
  property stHandle: isc_stmt_handle read FstHandle;
{: Pointer to the InterBase statement handle.}
  property PstHandle: pisc_stmt_handle read GetPstHandle;
{: Pointer to the InterBase connection handle referenced by this statement.}
  property PdbHandle: pisc_db_handle read GetPdbHandle;
{: Pointer to the InterBase transaction handle referenced by this statement.}
  property PtrHandle: pisc_tr_handle read GetPtrHandle;
{: Reference to the output columns of the statement or dataset.}
  property Fields: TIB_Row read GetFields;// write SetFields;
{: This property gives a variant result of the given output column passed in.}
  property FieldValues[const FieldName: string]: Variant
      read GetFieldValue
     write SetFieldValue; default;
{: This property accepts or returns gives a variant representing the value of
the input column passed in.}
  property ParamValues[const ParamName: string]: Variant
      read GetParamValue
     write SetParamValue;
{: Number of output columns.}
  property FieldCount: integer read GetFieldCount;
{: Number of input columns.}
  property ParamCount: integer read GetParamCount;
{: Character to be used as an alternative prefix character to denote an input
parameter in the SQL property.
<br><br>
By default this is a colon ":" but caution should be exercised, since some
triggers and stored procedures use a colon prefix to denote internal parameters.
It will cause problems if they are parsed as input parameters.}
  property ParamChar: char read FParamChar write FParamChar default ':';
{: This property, if set True, makes the parser check for input parameters.
<br><br>
It can be useful to set False where you are processing many DDL and DML
statements that you know will never need to be checked for input parameters. }
  property ParamCheck: boolean read FParamCheck write FParamCheck default true;
{: Text to signify the beginning of a macro token string.
<br><br>
In the SQL, place the token string, to mark the beginning of your macro
token. For example, if you use the percent symbol for both the MacroBegin and
MacroEnd markers, you would write your SQL like this:<br>
<br>
SELECT %%MYCOLS%% FROM %%MYTABLE%%
<br>
<br>
Then, in the handler you would check for the macros named "MYCOLS" and
"MYTABLE" and substitute in the appropriate values. }
  property MacroBegin: string read GetMacroBegin
                              write SetMacroBegin
                              stored IsMacroBeginStored;
{: Text to signify the ending of a macro.
<br>
See MacroBegin for more information.}
  property MacroEnd: string read GetMacroEnd
                            write SetMacroEnd
                            stored IsMacroEndStored;
{: Parameter values are stored aside so that, between transactions, their
values can be preserved. This stringlist contains a standard list of column
entries with their corresponding values.}
  property OldParamValueLinks: TIB_StringList read FOldParamValueLinks;
{: Flag which returns True if the statement is in the process of being
assigned.}
  property AssigningSQL: boolean read FAssigningSQL;
{: Flag which returns True if the statement is in the process of being
prepared.}
  property PreparingSQL: boolean read FPreparingSQL;
{: Flag indicating whether the PrepareSQL phase is in refinement.}
  property RefiningSQL: boolean read FRefiningSQL;
{: Property which returns the refined SQL that was sent to the server.}
  property RefinedSQL: string read FRefinedSQL;
{: Returns a flag indicating whether the dataset is a result of a refined SQL
statement.
<br><br>
This tells you that the dataset is working with a reduced subset of rows, which
it will do as a means to avoid fetching all of the rows from the server in order
to be able to position the current record pointer onto a given row.
<br><br>
When performing incremental searching it is possible to set up different
parameters that allow a refined dataset to be defined before the actual
incremental searching begins. Thus, if a user types "Wha" the dataset
can be refined to only include records that are STARTING WITH "Wh" and then
incrementally search upon thos records only. In this way the records starting
with "A" to "V" will simply stay on the server and not be brought to the client.
<br><br>
This is employed in the handling of the SearchingLinks. SQLIsRefined will always
be true if SearchingLinks are defined.}
  property SQLIsRefined: boolean read GetSQLIsRefined;
{: Property which returns the SQL that was sent to the server.
<br><br>
This does not include refinements made.}
  property ServerSQL: string read FServerSQL;
{: Property which returns the client-provided SQL statement without any
alterations that might be applied for sending to the server.}
  property ClientSQL: string read GetClientSQL;
{: Flag which returns True if the statement is going through a re-prepare cycle.
<br><br>
It may be important to check this property in the AfterPrepare event.}
  property RepreparingSQL: boolean read FRepreparingSQL;
{: Number of statement link objects referencing this statement.
<br><br>
This applies to datasources for datasets, too, since these are base classes for
them.}
  property StatementLinkCount: integer read GetStatementLinkCount;
{: Tells whether the statement has a need to reprepare itself.
<br><br>
This becomes true when a call to InvalidateSQL is made and is false after a
statement or dataset is prepared or reprepared.}
  property SQLIsValid: boolean read FSQLIsValid;
{: This is the property which contains the SQL statement to be prepared
and executed.
<br><br>
For datasets it contains the SELECT statement.}
  property SQL: TStrings read GetSQL write SetSQL;
{: Indicates the SQLDialect of the connection.}
  property SQLDialect: integer read GetSQLDialect;
{: Existence of specific user-defined column attributes can be accessed through
this property.}
  property IsColAttributeSet[ const ACol, Index: string ]: boolean
      read GetColIsAttributeSet
     write SetColIsAttributeSet;
{: User defined column attributes can be stored via this property.  Index can
be a string or one of the pre-defined ColumnAttribute string constants.  See
the help for the ColumnAttributes property for a list of these constants. }
  property ColAttributeParams[ const ACol, Index: string ]: string
      read GetColAttributeParams
     write SetColAttributeParams;

{: System use property.}
  property SysBindingCursor: TIB_Dataset read FBindingCursor;
{: System use property.}
  property SysParamNames: TIB_StringList read GetSysParamNames;
{: System use property.}
  property SysFieldNames: TIB_StringList read GetSysFieldNames;
{: System use property.}
  property SysTableNames: TIB_StringList read GetSysTableNames;
{: System use property.}
  property StatementLinkList: TList read FIB_StatementLinkList;
public

{: This is a special property that allows custom attributes for the columns
of this statement to be stored.
<br><br>
The following string constant definitions utilized by other components and
controls:
<br>
<br>
// These are various settings that can be made in the ColumnAttributes property.
<br>
  IBO_BINARY      = 'BINARY';     // Flag a CHAR or VARCHAR column as binary.
<br>
  IBO_BLANKISNULL = 'BLANKISNULL';// Used by TIB_Column class when assigning a
<br>
                                 // blank string to a Fields member.
<br>
  IBO_BOOLEAN     = 'BOOLEAN';    // Used to tell what values are used to make
<br>
                                 // this a boolean column.
<br>
  IBO_COMPUTED    = 'COMPUTED';   // It tells that the column is actually a
<br>
                                 // COMPUTED BY derived column.
<br>
  IBO_CURRENCY    = 'CURRENCY';   // Flag to indicate a column contains currency.
<br>
  IBO_NOCASE      = 'NOCASE';     // Flag to set a case insensitive search field
<br>
  IBO_NODATE      = 'NODATE';
<br>
  IBO_NOSOUNDEX   = 'NOSOUNDEX';  // Disable soundex processing in a dataset
<br>
  IBO_NOTIME      = 'NOTIME';
<br>
  IBO_NOTRAILING  = 'NOTRAILING'; // Flag to use STARTING on literal strings
<br>
  IBO_NOTREQUIRED = 'NOTREQUIRED';
<br>
  IBO_REQUIRED    = 'REQUIRED';
<br>
  IBO_SOUNDEX     = 'SOUNDEX';    // Flag to set a SoundEx search field
<br>
  IBO_BCD         = 'BCD';        // Flag to force a colum to be TIBOBCDField
<br>
  IBO_NOBCD       = 'NOBCD';      // Flag to force a colum to be TIBOFloatField
<br>
  IBO_YESCASE     = 'YESCASE';    // Flag to override CN.DefaultNoCase
<br>
  IBO_YESLIKE     = 'YESLIKE';    // Flag to use LIKE on literal strings
<br>
  IBO_YESTRAILING = 'YESTRAILING';// Flag to override CN.DefaultNoTrailing
<br>
  IBO_MODIFIABLE  = 'MODIFIABLE'; // Used for making Calculated Fields Modifiable
<br>
  IBO_MINVAL      = 'MINVAL';  // Used for numeric columns to set a minimum value
<br>
  IBO_MAXVAL      = 'MAXVAL';  // Used for numeric columns to set a maximum value
<br>
  IBO_DOMAIN      = 'DOMAIN';  // Used to assign a field or param to a DOMAIN
<br>
<br>



<br><br>
<br>// Used in column object creation. It tells that the column is actually a
<br>// COMPUTED BY derived column.
<br>  IBO_COMPUTED   = 'COMPUTED';
<br>
<br>// 53bit integer based numeric objects, [Now removed]
<br>  IB_NOROUNDERR = 'NOROUNDERR';
<br>  IB_CURR       = 'CURR';
<br>  IB_COMP       = 'COMP';
<br>
<br>// Used for searching
<br>  IBO_NOCASE     = 'NOCASE';     // Flag to set a case insensitive field.
<br>                                // < colname >=NOCASE=< nocasecolname >;
<br>  IBO_NOTRAILING = 'NOTRAILING'; // Flag to use STARTING on literal strings.
<br>
<br>// Used by the SeachPanel
<br>  IBO_NOINSERT = 'NOINSERT';
<br>  IBO_NOEDIT   = 'NOEDIT';
<br>  IBO_NOSEARCH = 'NOSEARCH';
<br><br>
Attribute <b>IBO_BOOLEAN</b><br>
If a column is defined as a BOOLEAN, the TIB_Grid will display checkbox glyphs
and the Boolean column will work just like a checkbox control. The BOOLEAN
values for the column default to 'T' and 'F' for string type columns and 0 and 1
for numeric type columns. It is possible to define custom values for BooleanTrue
and BooleanFalse by appending a parameter value to the ColumnAttributes BOOLEAN
entry.  For example:
<br><br>
MYCOLUMN=BOOLEAN=Y,N;< another parameter if necessary semicolon separated >
<br><br>
This will use 'Y' as the BooleanTrue value and 'N' as the BooleanFalse value.
<br><br>
It is possible to set multiple ColumnAttributes for a single column in one
entry. Use semi-colons to separate the attributes.
<br><br>
If a Boolean column is nullable, three states are possible when clicking on the
cell - True, False and Grayed (null or unknown). A column defined as NOT NULL
will only toggle between True and False.
<br><br>
If you want to disable this feature in the grid, the IB_Grid control has a
property called IndicateBooleans which allows you to do so.
<br><br>
Refer to the help on the TIB_StringList class to see how to use and work with
link entries and their parameters. The only one so far that uses a parameter
is the NOCASE setting - refer to the link entry example above, which uses it. }
  property ColumnAttributes: TIB_StringList read FColumnAttributes
                                            write SetColumnAttributes;
{: This property is used to enumerate and define the types of any calculated
fields to be used with the statement or dataset.
<br><br>
The format of this property emulates a column definition in a CREATE TABLE
statement where you declare the COLUMN NAME, COLUMN TYPE and its nullable
status, if applicable. For example:
<br><br>
<br>MYINTCOLUMN INTEGER NOT NULL
<br>MYSTRCOLUMN VARCHAR ( 50 )
<br>MYDATECOLUMN DATE
<br><br>
When the statement is prepared, these columns will behave just as though they
were part of the original statement, except that it is necessary to supply
calculation algorithms for them, in the row context, to the OnCalculateFields
event.
<br><br>
When you use the buffered datasets, calculated fields need to be calculated only
once, upon being fetched.  The calculation results are stored in the buffer,
along with the rest of the dataset.  Also, under any circumstances which could
make them become invalid, they will be refreshed automatically.
<br><br>
It should be entirely feasible to use a Lookup() call to another dataset in
order to maintain a lookup field.
<br><br>
The Contact sample application shows a very simple example of how to use these
properties.}
  property CalculatedFields: TIB_StringList read FCalculatedFields
                                            write SetCalculatedFields;
{: Property which tells whether the values of CalculatedFields are being set.}
  property CalculatingFields: boolean read GetCalculatingFields;
{: Property to connect to a connection component by providing a global string
instead of a direct component reference. This is intended to provide BDE
compatibility.}
  property DatabaseName: string read GetDatabaseName write SetDatabaseName;

{ Properties to be published for the IB_DSQL, IB_Cursor and IB_Query }

{: Gives contol over how the data should be aligned if something other than
the default is desired.
<br><br>
<br>Make column entries using the standard format as:
<br>[< tablename >.]< columnname >=R[IGHT]
<br>[< tablename >.]< columnname >=C[ENTER]
<br>[< tablename >.]< columnname >=L[EFT]  }
  property FieldsAlignment: TIB_StringList read FFieldsAlignment
                                           write SetFieldsAlignment;
{: Gives contol over how string data should be handled by data aware
controls.
<br><br>
<br>Make column entries using the standard format as:
<br>[< tablename >.]< columnname >=U[PPER]
<br>[< tablename >.]< columnname >=L[OWER]
<br>[< tablename >.]< columnname >=P[ROPER]
<br>[< tablename >.]< columnname >=N[ORMAL]}
  property FieldsCharCase: TIB_StringList read FFieldsCharCase
                                          write SetFieldsCharCase;
{: Allows a display string to be provided for a column's DisplayText property.
<br><br>
Use the standard format for making column entries.
<br>
<br>Date based columns use the DateTimeToString() syntax.
<br>Numeric based columns use the FormatFloat() syntax.
<br>All other columns use the FormatMaskText() syntax.
<br>
<br>Make column entries using the standard format as:
<br>
<br>[< tablename >.]< columnname >=mm/dd/yyyy
<br>[< tablename >.]< columnname >=##0.00
<br>[< tablename >.]< columnname >=(000)_000-0000;0;*}
  property FieldsDisplayFormat: TIB_StringList read FFieldsDisplayFormat
                                               write SetFieldsDisplayFormat;
{: Allows the DisplayLabel to contain a more user friendly column heading.
<br>
<br>This is used in the IB_Grid and the IB_SearchPanel.
<br>
<br>Use the standard format for making column entries.}
  property FieldsDisplayLabel: TIB_StringList read FFieldsDisplayLabel
                                              write SetFieldsDisplayLabel;
{: Allows you to override DisplayLabels for a column with a label specifically
for display on a grid column title.  Normally such a label may be an
abbreviation designed to fit as a title on a narrow column.
<br>
<br>This is used in the IB_Grid only.
<br>
<br>Use the standard format for making column entries.}
  property FieldsGridLabel: TIB_StringList read FFieldsGridLabel
                                              write SetFieldsGridLabel;
{: Allows to set a user friendly column description.
If the mouse move over a grid column title, the description will be visible as a hint.
<br>
<br>This is used in the IB_Grid.
<br>
<br>Use the standard format for making column entries.}
  property FieldsGridTitleHint: TIB_StringList read FFieldsGridTitleHint
                                               write SetFieldsGridTitleHint;
{: Allows the width in pixels of a IB_Grid column to be adjusted.
<br>
<br><b>The standard format for making column entries is:</b>
<br>[< tablename >.]< columnname >=35
<br>[< tablename >.]< columnname >=95}
  property FieldsDisplayWidth: TIB_StringList read FFieldsDisplayWidth
                                              write SetFieldsDisplayWidth;
{: Allows an EditMask string to be provided for a column's data aware
control to use when editing the contents of the column..
<br><br>
Make column entries using this standard format:
<br>
<br>[< tablename >.]< columnname >=(000)_000-0000;0;*
<br>
<br>For more information about edit masks, please refer to the help topic on
TCustomMaskEdit.EditMask. }
  property FieldsEditMask: TIB_StringList read FFieldsEditMask
                                          write SetFieldsEditMask;
{: Allows the order of the IB_Column objects to be altered.
<br><br>
Simply make a list of standard column references in the desired order. Any
columns in the dataset that are not included in this list are just moved to the
end of the list.
<br>
<br>Use this standard format:
<br>
<br>[< tablename >.]< columnname >
<br>[< tablename >.]< columnname >
<br>[< tablename >.]< columnname >}
  property FieldsIndex: TIB_StringList read FFieldsIndex write SetFieldsIndex;
{: Allows controls bound to specific  fields to be locked as "read only".
It is also possible to restrict the property to make a column become read-only
just during certain states, in order to block edits, inserts and/or searching
on that column.
<br><br>
The standard format for column entries is:
<br>
<br>[< tablename >.]< columnname >=T[RUE];NOEDIT;NOINSERT;NOSEARCH
<br>[< tablename >.]< columnname >=F[ALSE];NOEDIT;NOINSERT;NOSEARCH}
  property FieldsReadOnly: TIB_StringList read FFieldsReadOnly
                                          write SetFieldsReadOnly;
{: Set the trimming of fields according to the valid entries below:
<br><br>
The standard format for column entries is:
<br>
<br>[< tablename >.]< columnname >=N[one]
<br>[< tablename >.]< columnname >=A[ll]
<br>[< tablename >.]< columnname >=B[oth]
<br>[< tablename >.]< columnname >=L[eft]
<br>[< tablename >.]< columnname >=R[ight]
<br>[< tablename >.]< columnname >=S[entence]
<br>[< tablename >.]< columnname >=CN[one]
<br>[< tablename >.]< columnname >=CA[ll]
<br>[< tablename >.]< columnname >=CB[oth]
<br>[< tablename >.]< columnname >=CL[eft]
<br>[< tablename >.]< columnname >=CR[ight]
<br>[< tablename >.]< columnname >=CS[entence]
<br>
<br>
The entries with a C in the first character specify that the length of the
string should be checked and an exception will be raised if the string is
longer than there is storage.}
  property FieldsTrimming: TIB_StringList read FFieldsTrimming
                                          write SetFieldsTrimming;
{: Setting Visible to False allows columns to be hidden in an IB_Grid if it is
not using GridLinks.

<br>
<br>The standard format for column entries is:
<br>[< tablename >.]< columnname >=T[RUE]
<br>[< tablename >.]< columnname >=F[ALSE]}
  property FieldsVisible: TIB_StringList read FFieldsVisible
                                         write SetFieldsVisible;
{: Connection to be used for this statement.}
  property IB_Connection: TIB_Connection read GetConnection
                                         write SetConnection
                                         stored IsConnectionStored;
{: Transaction to be used for this statement.
<br><br>
If the IB_Connection property is referencing a TIB_Database component then this
property is automatically set to reference a transaction that is internally
contained in the TIB_Database instance. This is for BDE emulation only.
<br><br>
If this property is left as nil and the component is not referenced to an
IB_Database, it will create its own internal transaction instance when the
statement is prepared.
<br><br>
Thus, if you never assign this property your, statements and datasets could all
be maintaining their own individual transactions.  This could be undesirable.
This internal transaction instance defaults to setting AutoCommit true and
Isolation to tiCommitted, meaning that each statement and dataset is able to see
what all the others have done.}
  property IB_Transaction: TIB_Transaction read GetTransaction
                                           write SetTransaction
                                           stored IsTransactionStored;
{: This is a reference to the input parameters for the statement.
<br>
<br>With InterBase Objects, all inputs are handled by parameters.
<br><br>
Be sure to Prepare the statement before accessing Param or Field column
references directly.}
  property Params: TIB_Row read GetParams;

{$IFNDEF HELPSCAN}
{ Design-time reference count property. }
  property _StatementLinkCount: integer read GetStatementLinkCount
                                        write flag_junk_value
                                        stored false;
{$ENDIF}

published
{: This property if set will call OnCalculateFields once for any field update
with AField set to nil so that all caculated fields can be changed at once
using Arow. }
  property CalculateAllFields: boolean read FCalculateAllFields
                                       write FCalculateAllFields
                                       default false;

end; { TIB_Statement }

{                                                                              }
{ TIB_DSQL                                                                     }
{                                                                              }

{: This component is used for all InterBase DSQL statements that do not
involve the handling of multiple rows of output data.
<br><br>
It is ideal for executing a stored procedure, doing a large batch of record
inserts, executing DDL or DML statements, etc.
<br><br>
All of the help topics for properties, methods and events are listed under its
parent class TIB_Statement.}
TIB_DSQL = class( TIB_Statement )
{$I IBA_Statement.PBL}
{$I IBA_DSQL.PBL     }
end;

// IBA_Statement.IMP

{                                                                              }
{ TIB_StatementLink                                                            }
{                                                                              }

{: General event type used for the TIB_StatementLink class.}
  TIB_StmtLinkEvent = procedure ( Sender: TIB_StatementLink;
                                  Statement: TIB_Statement ) of object;
{: Event type used for statements and columns.}
  TIB_ColLinkEvent = procedure ( Sender: TIB_StatementLink;
                                 Statement: TIB_Statement;
                                 Field: TIB_Column ) of object;
{: This component is used to propogate the events generated by the
TIB_Statement class.

This component serves as a foundation to the TIB_DataSource class.}
TIB_StatementLink = class(TIB_Component)
private
{ Property Storage Fields }
  FStatement: TIB_Statement;
{ Event storage }
  FOnPrepareSQL: TIB_StmtLinkEvent;
  FOnPreparedChanged: TIB_StmtLinkEvent;
{ Property access methods }
  function GetPrepared: boolean;
  function GetActive: boolean;
protected
{ Property Access Methods }
  function GetStatementType: TIB_StatementType;
  procedure SetStatement( AValue: TIB_Statement ); virtual;
{ System methods }
  procedure SysPrepareSQL; virtual;
  procedure SysBeforeUpdateDescriptors; virtual;
  procedure SysAfterUpdateDescriptors; virtual;
{ Methods }
  procedure ProcessEvent( AEvent: TIB_StatementEventType;
                          Info: longint ); virtual;
public
{ Inherited Methods }
  destructor Destroy; override;
{ Properties }
{: Returns True if the associated statement or dataset is Prepared.}
  property Prepared: boolean read GetPrepared;
{: Returns True if the associated statement or dataset is Active.}
  property Active: boolean read GetActive;
{: Statement type of the SQL in the associated statement or dataset
  component.}
  property StatementType: TIB_StatementType read GetStatementType;
{: Reference to the associated statement or dataset for this component.}
  property Statement: TIB_Statement read FStatement write SetStatement;
published
{: This is a very useful event to provide refinement at the time the SQL is
being prepared.
<br><br>
Any alterations made to the SQL property during this event are integrated into
the SQL that is sent to the server but do not affect the text stored in the SQL
property of the associated statement or dataset component.
<br><br>
Some hints to get you going:
<br><br>
The OnPrepareSQL events are triggered every time a statement is prepared or
re-prepared, initiating a process that begins with the base text from the SQL
property of the dataset.  From there, as each phase of the Prepare takes place,
the base SQL is modified and adjusted to conform with factors arising from
various other settings in the dataset object.
<br><br>
For example: <ul>
<li>If MasterLinks and MasterSource are defined, parameters are added
to the WHERE clause to bind the dataset to the master dataset.
<li>If JoinLinks are defined, they are incorporation into WHERE clause.
<li>Any datalinks that contain SearchBuffer criteria are appended to the WHERE
clause.
<li>If there is an OrderingItems table and OrderingItemNo is non-zero, the
ORDER BY clause is substituted with the ORDER BY criteria from the OrderingItems
property.
</ul>
All modifications to the SQL and SQLXXXX properties during this PreparingSQL
phase are integrated into the SQL that goes to the server and do NOT become a
part of the base SQL property. However, if the SQL property is modified outside
the PreparingSQL phase, that change does get applied to the base SQL.
<br><br>
<b>A Sample Use of OnPrepareSQL</b><br>
Let's take a simple example that shows how to bind SQL criteria to a custom
control in the interface.  Suppose you would like to include a button that the
user can click to toggle to include one or the other of two sets of rows in the
dataset.  You would set it up as though this were an item of search criteria:
<br><font face="Courier New">
<br>// This is a TCheckBox OnClick event handler.
<br>procedure TfrmMain.cbActiveUsersClick( Sender: TObject );
<br>begin
<br>  qrUsers.InvalidateSQL; // Flag it so that it knows to re-prepare.
<br>  qrUsers.Refresh;
<br>end;
<br>
<br>// This is a TIB_StatementLink OnPrepareSQL event handler,
<br>// also used by the TIB_DataSource component.
<br>procedure TfrmMain.dsUsers.PrepareSQL(Sender: TIB_StatementLink;
<br>  Statement: TIB_Statement);
<br>begin
<br>  if cbActiveUsers.Checked then begin
<br>    qrUsers.SQLWhereItems.Add( 'STATUS=''A''' );
<br>  end else begin
<br>    qrUsers.SQLWhereItems.Add( 'STATUS=''I''' );
<br>  end;
<br>end;
</font>
<br><br><br>
<br>Now, the <br>SQL that goes to the server will always have a WHERE clause
item of STATUS='A' or STATUS='I', according to the state of the checkbox
cbActiveUsers.}
  property OnPrepareSQL: TIB_StmtLinkEvent read FOnPrepareSQL
                                           write FOnPrepareSQL;
{: StatementLink event.}
  property OnPreparedChanged: TIB_StmtLinkEvent read FOnPreparedChanged
                                                write FOnPreparedChanged;

end; { TIB_StatementLink }

// IBA_StatementLink.IMP

{                                                                              }
{ IBA_Dataset                                                                  }
{                                                                              }

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Wassim Haddad <lobolo2000@yahoo.com>                                        }
{  22-Aug-2001                                                                 }
{     Added support to import column defaults from the server upon request.    }
{     ImportServerDefaults is a public method that imports column defaults     }                                                                        
{     from the server.                                                         }
{                                                                              }
{  29-Aug-2001                                                                 }
{    Added OnKeySourceStateChanged event which fires whenever the keysource    }
{    state changes.                                                            }
{                                                                              }
{******************************************************************************}

{: Exception class for the TIB_Dataset component.}
EIB_DatasetError = class( EIB_Error );
{: Event type for most all events of the TIB_Dataset component.}
TIB_DatasetEvent = procedure( IB_Dataset: TIB_Dataset ) of object;
{: Event type for most all events of the TIB_Dataset component that also
need a row reference to accompany them.}
TIB_DatasetRowEvent = procedure( IB_Dataset: TIB_Dataset;
                                 ARow: TIB_Row ) of object;
{: When fetching records to the end of a dataset or an undefined point a
fetch callback mechanism is used. Each callback has a status of one of these
three types.}
TIB_CallbackStatus = ( csInit, csRefresh, csFinal );
{: Event type to process a fetch callback when fetching many records.}
TIB_CallbackEvent = procedure(     IB_Dataset: TIB_Dataset;
                                   Status: TIB_CallbackStatus;
                                   CursorRowNum: longint;
                               var Abort: boolean ) of object;
{: This event is used to provide a hook that allows confirmation to be acquired.
Its only current use is in the OnConfirmDelete event but others could be
implemented at any time.}
TIB_ConfirmationEvent = procedure(     Sender: TComponent;
                                   var Confirmed: boolean ) of object;
{: This is used to inquire whether the dataset should be scrolled. It is used
for the OnGetCanScroll event.}
TIB_GetCanScrollEvent = procedure (     Sender: TIB_Dataset;
                                    var CanScroll: boolean ) of object;
{: In some cases it is necessary to provide a custom means to figure out
how many records are in the current dataset. This event type provides a
means to do this.}
TIB_CustomRecordCount = procedure(     Sender: TIB_Dataset;
                                   var RecordCount: longint ) of object;
{: These event types are used to process SearchBuffer values.}
TIB_SearchingEventType = ( setClearCriteria,
                           setSaveCriteria,
                           setRecallCriteria,
                           setSaveLastCriteria,
                           setRecallLastCriteria,
                           setWriteCriteria,
                           setReadCriteria );
{: These status types are used to determine the state of the dataset.
<br><br>
I added an extra "s" in order to avoid a naming collision with the VCL so
that IB Objects can coexist with the VCL's data aware components and controls.}
TIB_DatasetState = ( dssInactive,
                     dssPrepared,
                     dssSearch,
                     dssBrowse,
                     dssEdit,
                     dssInsert,
                     dssDelete );
{: Set of TIB_DatasetState values. }
TIB_DatasetStateSet = set of TIB_DatasetState;
{: States for which a check is performed to allow the developer to affix a
custom algorithm to the CanEdit, CanDelete, and CanInsert properties.}
TIB_DatasetCanModify = ( cmDelete, cmEdit, cmInsert );
{: Event type to enable or block a dataset's ability to be modified.}
TIB_GetCanModifyEvent = procedure (     Sender: TIB_Dataset;
                                        Action: TIB_DatasetCanModify;
                                    var CanModify: boolean ) of object;
{: These settings provide more specific control over the behavior of datasets
in a master-detail relationship when MasterSearch is being used.}
TIB_MasterSearchFlagsSet = ( msfOpenMasterOnOpen,
                             msfSearchMasterOnSearch,
                             msfSearchAppliesToMasterOnly );

{: Options to better control behavior of datasets when using the MasterSearch
option.}
TIB_MasterSearchFlags = set of TIB_MasterSearchFlagsSet;

{: Actions taken by a child dataset in a master-detail relationship.
<br><br>
This is intended for internal use only.}
TIB_MasterChildAction = ( mcaSaveSearch,
                          mcaClearSearch,
                          mcaRecallSearch,
                          mcaRecallLastSearch,
                          mcaWriteSearch,
                          mcaReadSearch,
                          mcaMasterSearchChanged,
                          mcaPost,
                          mcaCancelInsert );

{: Actions taken by a child dataset in a key-detail relationship.
<br><br>
This is intended for internal use only.}
TIB_KeyChildAction = ( kcaUpdateKeyDescCalcFields );

{: Utility flags for use with the TIB_Dataset class.}
  TIB_DatasetFlagsSet = ( dsfInsertWasPosted,
                          dsfDeleteWasPosted,
                          dsfEditWasPosted,
                          dsfWasInvalidated,
                          dsfWasRefreshed );
{: Set of Dataset Utility Flags.}
  TIB_DatasetFlags = set of TIB_DatasetFlagsSet;

{: When Refresh is called one of these actions will be taken.
<br><br>
<b>Hints about refreshing data</b>
<br><br>
With IBO you can refresh data in a range of different ways.  The crudest and
least efficient is call Close and then Open (or set Active to False and then to
True).  It forces all rows previously fetched to be tossed and then completely
re-fetched from the server.  This may be desirable in some circumstances, though.
<br><br>
There are these methods and properties to consider. Most of these only apply
to the IB_Query (TIB_BDataset classes).
<br><br>
The Refresh method keeps the whole row buffers in memory and refreshes the list
of keys that define the dataset.  If a new row is added, it becomes a member
of the dataset through its key being fetched on refresh;  or, if a row is
deleted, its key is excluded when the keys are refreshed.  The deleted row's
whole record buffer would eventually be garbage-collected as a result of its key
being absent from the dataset.
<br><br>
This method is used internally when the OrderingItemNo property is changed.  It
works especially well there, because the whole record buffers stay in memory and
only the newly-ordered keys need to be fetched back in.   The individual row
buffers are simply re-associated with the keys, without having to be re-fetched
for re-ordering purposes.
<br><br>
The RefreshAction property determines how the refresh is to end up. It can
behave as though it were being opened the first time (raOpen) or, after it is
opened again it can go to the row number that it was on before the refresh
(raKeepRowNum). The third option is for it to go to the same row it was
positioned on before, by matching the Bookmark (raKeepDataPos).
<br><br>
The InvalidateRows method will keep the current list of keys and cause all of
the individual row buffers to be fetched from the server again.
<br><br>
If you combine Refresh and InvalidateRows, the dataset will be entirely
refreshed, as if you set active to false and then true. Calling the RefreshAll
method accomplishes the same thing.
<br><br>
You can also invalidate individual rows by calling the InvalidateRowNum() and
InvalidateBookmark() methods. This is great for reflecting changes that occur
in separate datasets or by calling a stored procedure.
<br><br>
When a record goes into dssEdit state, that row is automatically invalidated
because, in order to establish a cursor context, it needs to fetch the record
again.
<br><br>
Updates and deletes are executed using positioned
WHERE CURRENT OF < cursor name > statements.}
TIB_RefreshAction = ( raOpen,
                      raKeepRowNum,
                      raKeepDataPos,
                      raKeepDataPosOrRowNum );

{: These are actions the dataset should take when its transaction Commits.}
TIB_CommitAction = ( caClose, caInvalidateCursor, caFetchAll,
                     caRefreshKeys, caRefresh );

{: These are to signify what kinds of adjustments need to be made to the cached
updates buffers to keep everything together so that there are no orphaned
child records due to allowing cached updates to be used in conjunction with
master-detail relationships.}
TIB_SynchronizeCachedUpdates = ( scuMasterDeleted,
                                 scuMasterDataChanged );
                                 
{: This event type is used to process the DMLCacheItems in order to maintain
cross-dataset buffer synchronization.}
TIB_AnnounceDMLCacheItemEvent = procedure (
        ADataset: TIB_Dataset;
  const ADMLCacheItemType: TIB_DMLCacheItemType ) of object;

{: This event type is used to process the DMLCacheItems in order to maintain
cross-dataset buffer synchronization.}
TIB_ReceiveDMLCacheItemEvent = procedure (
        ADataset: TIB_Dataset;
  const ADMLCacheItem: TIB_DMLCacheItem ) of object;

{: VCL Compatibility.}
TIB_DataSetErrorEvent = procedure(     DataSet: TIB_DataSet;
                                       E: EIB_Error;
                                   var Action: TIB_DataAction ) of object;
{: VCL Compatibility}
TIB_UpdateErrorEvent = procedure(    DataSet: TComponent;
                                     E: Exception;
                                     UpdateKind: TIB_UpdateKind;
                                 var UpdateAction: TIB_UpdateAction ) of object;

TIB_UpdateRecordEvent = procedure(   DataSet: TComponent;
                                     UpdateKind: TIB_UpdateKind;
                                 var UpdateAction: TIB_UpdateAction ) of object;
TIB_KeyDatasourceEvent = procedure ( Sender: TIB_Dataset;
                                     ADataSource: TIB_DataSource ) of object;
{: This class serves as the base for the TIB_Cursor and TIB_Query components.
<br><br>
It is intended primarily for SQL statements that select multiple rows.
It can, however, handle any statement.
<br><br>
The IB_Cursor component is a single-buffer unidirectional dataset ideal for
scanning through records one at a time in sequential order. It will return
accurate data for any valid SELECT statement using very little memory.  It is
the fastest way to do a full table scan.
<br><br>
The IB_Query component is a buffered scrollable dataset that is ideal for
browsing by users and providing a randomly scrollable dataset.
<br><br>
The RecordCount property may not work for some select statements. It uses a
simple parsing substitution in order to put together a separate SELECT COUNT( * )
statement to get the record counts.  You can provide a custom event handler to
calculate the RecordCount for a dataset.  In some cases with the buffered dataset
it will do a FetchAll and then report the number of rows in the buffer.}
TIB_Dataset = class( TIB_Statement )
private
{ Property Storage Fields }
  FIB_KeyDataLink: TIB_KeyDataLink;
  FIB_MasterDataLink: TIB_MasterDataLink;
  FDataSourceList: TList;
  FAutoFetchFirst: boolean;
  FAutoFetchAll: boolean;
  FCheckRequired: boolean;
  FState: TIB_DatasetState;
  FLastRowState: TIB_RowState;
  FPostedState: TIB_DatasetState;
  FCancelledState: TIB_DatasetState;
  FOpenPending: boolean;
  FClosePending: boolean;
  FInsertPending: boolean;
  FWasSearching: boolean;
  FPreventEditing: boolean;
  FPreventInserting: boolean;
  FPreventDeleting: boolean;
  FPreventSearching: boolean;
  FPreventKeySeeking: integer;
  FCursorRecordCountValid: boolean;
  FParamWasChanged: boolean;
  FPessimisticLocking: boolean;
  FIsRowLocked: boolean;
  FIsCancelling: boolean;
  FCursorIsOpen: boolean;
  FCursorEof: boolean;
  FCursorName: string;
  FCursorRowNum: longint;
  FCursorRecordCount: longint;
  FIsPostingLevel: integer;
  FSQLSelect: TIB_StringList;
  FSQLFrom: TIB_StringList;
  FSQLWhere: TIB_StringList;
  FSQLGroup: TIB_StringList;
  FSQLHaving: TIB_StringList;
  FSQLUnion: TIB_StringList;
  FSQLPlan: TIB_StringList;
  FSQLOrder: TIB_StringList;
  FSQLForUpdate: TIB_StringList;
  FSQLOrderLinks: TIB_StringList;
  FOrderingItems: TIB_StringList;
  FOrderingLinks: TIB_StringList;
  FOrderingItemNo: integer;
  FOrderingItemNoChanging: integer;
  FOrderingLink: string;
  FOrderingField: TIB_Column;
  FOrderingParam: TIB_Column;
  FOrderingLinkChanging: integer;
  FCommitAction: TIB_CommitAction;
  FRefreshAction: TIB_RefreshAction;
  FRefiningIncSearch: integer;
  FSearchingLinks: TIB_StringList;
  FSearchingLink: string;
  FSearchingParamName: string;
  FSearchingParam: TIB_Column;
  FSearchingLinkChanging: integer;
  FSearchCriteria: TStrings;
  FKeyLinks: TIB_StringList;
  FKeyDescLinks: TIB_StringList;
  FKeyLinksAutoDefine: boolean;
  FKeySeeking: boolean;
  FMasterLinks: TIB_StringList;
  FMasterParamLinks: TIB_StringList;
  FMasterSearch: boolean;
  FMasterSearchFlags: TIB_MasterSearchFlags;
  FMasterWhere: string;
  FMasterSearchWasActive: boolean;
  FKeyDataFreezeLevel: integer;
  FKeyChildUpdatingLevel: integer;
  FMasterDataChangeLevel: integer;
  FMasterDataChangeMaxLevel: integer;
  FStateChangeLevel: integer;
  FSQLWhereLow: TStrings;
  FSQLWhereMed: TStrings;
  FSQLWhereHigh: TStrings;
  FSQLWhereChanged: boolean;
  FSQLOrderChanged: boolean;
  FColorScheme: boolean;
  FGetServerDefaults: boolean;
  FDefaultValues: TIB_StringList;
  FGeneratorLinks: TIB_StringList;
  FAutoPostDelete: boolean;
  FConfirmDeletePrompt: TStrings;
  FRefreshOnParamChange: boolean;
  FRequestLive: boolean;
  FSQLSectionChanging: integer;
  FMaxRows: longint;
  FMaxTicks: DWORD;
  FMinTicksRows: longint;
  FPageRows: longint;
  FControlsDisabledLevel: integer;
  FWasStateChange: boolean;
  FWasDataChange: boolean;
  FWasScrolled: boolean;
  FDMLCacheFlags: TIB_DMLCacheFlagsSet;
  FAssignedSQLWhere: string;
  FDatasetFlags: TIB_DatasetFlags;
  FRefreshDML: Boolean;
{ Fetch callback dialog }
  dlgCancelQuery: TForm;
{ Optimization/System Flags }
  flag_open_after_load: boolean;
  flag_junk_value: integer;
  flag_keylinks: integer;
{ Event Storage Fields }
  FBeforeOpen: TIB_DatasetEvent;
  FAfterOpen: TIB_DatasetEvent;
  FBeforeClose: TIB_DatasetEvent;
  FAfterClose: TIB_DatasetEvent;
  FBeforeSearch: TIB_DatasetEvent;
  FAfterSearch: TIB_DatasetEvent;
  FBeforeEdit: TIB_DatasetEvent;
  FAfterEdit: TIB_DatasetEvent;
  FBeforeInsert: TIB_DatasetEvent;
  FAfterInsert: TIB_DatasetEvent;
  FBeforeDelete: TIB_DatasetEvent;
  FAfterDelete: TIB_DatasetEvent;
  FBeforeCancel: TIB_DatasetEvent;
  FAfterCancel: TIB_DatasetEvent;
  FAfterScroll: TIB_DatasetEvent;
  FBeforeScroll: TIB_DatasetEvent;
  FBeforePost: TIB_DatasetEvent;
  FAfterPost: TIB_DatasetEvent;
  FOnNewRecord: TIB_DatasetEvent;
  FOnCallback: TIB_CallbackEvent;
  FAfterFetchRow: TIB_DatasetEvent;
  FAfterFetchEof: TIB_DatasetEvent;
  FOnConfirmDelete: TIB_ConfirmationEvent;
  FOnOrderingChanged: TIB_DatasetEvent;
  FOnGetRecordCount: TIB_CustomRecordCount;
  FOnGetCanScroll: TIB_GetCanScrollEvent;
  FOnGetCanModify: TIB_GetCanModifyEvent;
  FOnCustomEdit: TIB_DatasetEvent;
  FOnCustomDelete: TIB_DatasetEvent;
  FOnCustomInsert: TIB_DatasetEvent;
  FOnCustomLockRow: TIB_DatasetEvent;
  FOnDMLCacheAnnounceItem: TIB_AnnounceDMLCacheItemEvent;
  FOnDMLCacheReceivedItem: TIB_ReceiveDMLCacheItemEvent;
private
  FKeyLinksMapsValid: boolean;
  FMasterLinksMapsValid: boolean;
  FKeyLinksFieldsMap: TList;  
  FKeyDescLinksFieldsMap: TList;
  FMasterLinksFieldsMap: TList;
  FMasterParamLinksFieldsMap: TList;
  FMasterLinksParamsMap: TList;
  FMasterParamLinksParamsMap: TList;
protected
  procedure InvalidateKeyLinksMaps;
  procedure InvalidateMasterLinksMaps;
  procedure CheckKeyLinksMaps;
  procedure CheckMasterLinksMaps;
protected
  FOnEditError: TIB_DataSetErrorEvent;
  FOnPostError: TIB_DataSetErrorEvent;
  FOnDeleteError: TIB_DataSetErrorEvent;
  FOnUpdateError: TIB_UpdateErrorEvent;
  FOnUpdateRecord: TIB_UpdateRecordEvent;
  procedure CheckOperation( Operation: TIB_DataOperation;
                            ErrorEvent: TIB_DataSetErrorEvent );
  function GetMasterLinkParamName( AIndex: integer ): string;
private
  FIsPostRetainingLevel: integer;
  FHasPostRetained: boolean;
  function GetIsPosting: boolean;
  function GetIsPostRetaining: boolean;
  function GetCalculatingFields: boolean; override;
protected
  FCachedUpdates: boolean;
  FKeyLookupCurLevel: integer;
  FKeyLookupMaxLevel: integer;
  FRefreshEof: boolean;
  FRefreshRowNum: longint;
  FRefreshBookmark: string;
  FRefreshCurLevel: integer;
  FRefreshMaxLevel: integer;
  FRefreshWasScrolled: boolean;
  FRefreshResult: boolean;
  function GetUpdatesPending: boolean; virtual;
  function GetUpdateStatus: TIB_UpdateStatus; virtual;
  function IsUsingManualDML( UpdateKind: TIB_UpdateKind ): boolean; virtual;
private
  procedure CheckCursorName;
  procedure SetDefaultValues( AValue: TIB_StringList );
  function GetGeneratorLinks: TIB_StringList;
  procedure SetGeneratorLinks( Value: TIB_StringList );
  procedure SetJoinLinks( AJoinLinks: TIB_StringList );
  procedure SetKeySeeking( AValue: boolean );
  function GetKeyDataset: TIB_Dataset;
  function GetMasterDataset: TIB_Dataset;
  function GetMasterRelation( LinkNo: integer ): string;
  function GetMasterFieldName( LinkNo: integer ): string;
  procedure SetMasterLinks( Value: TIB_StringList );
  procedure SetMasterSource( AValue: TIB_DataSource ); //~virtual;
  function GetMasterSource: TIB_DataSource; //~virtual;
  procedure SetOrderingItems( Value: TIB_StringList );
  procedure SetOrderingLinks( Value: TIB_StringList );
  function GetOrderingItemNoChanging: boolean;
  function GetOrderingLinkChanging: boolean;
  function GetSearchingLinkChanging: boolean;
  procedure SetMasterParamLinks( Value: TIB_StringList );
  procedure SetSearchingLinks( Value: TIB_StringList );
  function GetSQLSection( Index: integer ): TStrings;
  procedure SetSQLSection( Index: integer; Value: TStrings );
  procedure SetPreventEditing( AValue: boolean );
  procedure SetPreventInserting( AValue: boolean );
  procedure SetPreventDeleting( AValue: boolean );
  procedure SetPreventSearching( AValue: boolean );
  function GetDataSourceCount: integer;
  function GetDataSources( Index: integer ): TIB_DataSource;
  function GetModified: boolean;
  function GetSQLWhereChanged: boolean;
  procedure SetAutoPostDelete( AValue: boolean );
  procedure SetConfirmDeletePrompt( AValue: TStrings );
  function GetPreparedEdits: boolean;
  procedure SetPreparedEdits( AValue: boolean );
  function GetPreparedInserts: boolean;
  procedure SetPreparedInserts( AValue: boolean );
  procedure IB_MasterDataChange( ADataLink: TIB_DataLink;
                                 ADataSource: TIB_DataSource;
                                 AField: TIB_Column );
  procedure IB_KeyDataChange( ADataLink: TIB_DataLink;
                              ADataSource: TIB_DataSource;
                              AField: TIB_Column );
  function GetRecNo: longint;
  procedure SetRecNo( AValue: longint );
  function GetDeleteSQL: TIB_StringList;
  function GetEditSQL: TIB_StringList;
  function GetLockSQL: TIB_StringList;
  function GetInsertSQL: TIB_StringList;
  procedure SetDeleteSQL( AValue: TIB_StringList );
  procedure SetEditSQL( AValue: TIB_StringList );
  procedure SetLockSQL( AValue: TIB_StringList );
  procedure SetInsertSQL( AValue: TIB_StringList );
  procedure ProcessSQLWhereStrings( const AStrings: TStrings;
                                    var   NewWhereClause: string;
                                          High: boolean );
  function GetRefreshing: boolean;
  function GetCallbackFreezeLevel: integer;
protected
  FCallbackFetchingLevel: integer;
  FCallbackInc: integer;
  FCallbackInitTick: DWORD;
  FCallbackRefreshTick: DWORD;
  FCallbackInitInt: DWORD;
  FCallbackRefreshInt: DWORD;
  FCallbackCaption: string;
  FFetching: boolean;
  FScanningLevel: integer;
  FMaxScanLevel: integer;
  FCursorGen: DWORD;
  FFetchingAborted: boolean;
  FFetchingAbortedGen: integer;
  FKeyLinksAutoDefined: boolean;
  FKeyLinksAreDBKEY: boolean;
  FClosedBookmark: string;
  FClosedRowNum: longint;
  FClosedEof: boolean;
  FUpdateSQL: TIB_UpdateSQL;
  FPostToServerSucceeded: boolean;
  procedure DefineProperties( Filer: TFiler ); override;
  procedure GetColumnIsReadOnly(     AColumn: TIB_Column;
                                 var AReadOnly: boolean ); override;
  procedure GetControlIsReadOnly(     AColumn: TIB_Column;
                                  var AReadOnly: boolean ); override;
  function GetKeyLinksExist: boolean;
  procedure SetKeyLinks( AKeyLinks: TIB_StringList ); //~virtual;
  procedure SetKeyDescLinks( AKeyDescLinks: TIB_StringList ); //~virtual;
  function GetKeyLinks: TIB_StringList; //~virtual;
  function IsKeyLinksStored: boolean; //virtual;
  procedure SetKeySource( AValue: TIB_DataSource ); //~virtual;
  function GetKeySource: TIB_DataSource; //~virtual;
  procedure SetCallbackCaption( const AValue: string ); //~virtual;
  procedure SetRequestLive( AValue: boolean ); //~virtual;
{ Inherited Property Access Methods }
  function GetSysKeyRelation: string; override;
  procedure SetConnection( AValue: TIB_Connection ); override;
  procedure SetTransaction( AValue: TIB_Transaction ); override;
  procedure SetActive( Value: boolean ); override;
  procedure SysBeforeParamsDataChange( Sender: TIB_Row;
                                       AField: TIB_Column ); override;
  procedure SysAfterParamsDataChange( Sender: TIB_Row;
                                      AField: TIB_Column ); override;
  procedure SysLayoutChange( Sender: TObject ); override;
{ Buffering Property Access Methods }
  function GetBufferRowCount: longint; virtual;
  function GetBufferHasBof: boolean; virtual;
  function GetBufferHasEof: boolean; virtual;
  function GetBufferRowNum: longint; virtual;
  procedure SetBufferRowNum( AValue: longint ); virtual;
  function GetBufferBof: boolean; virtual;
  function GetBufferEof: boolean; virtual;
  function GetBufferFields: TIB_Row; virtual;
{ Property Access Methods }
  function GetBookmark: string; virtual;
  procedure SetBookmark( const ABookMark: string ); virtual;
  function GetBufferBookmark: string; virtual;
  procedure SetBufferBookmark( const ABookMark: string ); virtual;
  function GetCursorBof: boolean;
  function GetEof: boolean; virtual;
  function GetBof: boolean; virtual;
  function GetCanModify: boolean; //~virtual;
  function GetCanEdit: boolean; virtual;   // Reserved by Geoff Worboys.
  function GetCanInsert: boolean; virtual; // Reserved by Geoff Worboys.
  function GetCanDelete: boolean; virtual; // Reserved by Geoff Worboys.
  function GetCanSearch: boolean; //~virtual;
  function GetCanScroll: boolean; 
  function SysGetCanScroll: boolean; virtual; // Corey Wangler 2/28/2000
  function GetNeedToPost: boolean;
  function GetState: TIB_DatasetState;
  procedure SetState( AValue: TIB_DatasetState ); //~virtual;
  function GetRowNum: longint; virtual;
  procedure SetRowNum( AValue: longint ); virtual;
  function GetBofRowNum: longint; virtual;
  function GetEofRowNum: longint; virtual;
  function GetUnidirectional: boolean; virtual;
  procedure SetReadOnly( AValue: boolean ); //~virtual;
  function GetReadOnly: boolean; virtual;
  procedure SetOrderingItemNo( AValue: integer ); //~virtual;
  procedure SetOrderingLink( const AValue: string ); //~virtual;
  procedure SetSearchingLink( const AValue: string ); //~virtual;
  function GetSearchingLinksActive: boolean; //virtual;
  function GetSearchedDeletes: boolean;
  function GetSearchedEdits: boolean;
  procedure SetSearchedDeletes( AValue: boolean );
  procedure SetSearchedEdits( AValue: boolean );
  function GetKeyFields: TIB_Row; virtual;
  property KeyLinksExist: boolean read GetKeyLinksExist;
  property RefiningIncSearch: integer read FRefiningIncSearch;
{ General methods }
  procedure ProcessTransactionEvent( ATransactionLink: TIB_TransactionLink;
                                     AEvent: TIB_TransactionEventType ); override;
  procedure ProcessLinkEvent( AEvent: TIB_StatementEventType;
                              Info: integer ); override;
{ Inherited Methods }
  procedure Loaded; override;
  procedure SetSession( ASession: TIB_Session ); override;
  procedure SysActiveChange; override;
  procedure SysPreparedChanged; override;
  procedure SysAfterPrepare; override;
  procedure SysAfterUnprepare; override;
  procedure SysExecSelect; override;
  procedure SysExecute; override;
  procedure SysClose; override;
  procedure SysBeforeFieldDataChange( Sender: TIB_Row;
                                      AField: TIB_Column ); override;
  procedure SysBeforeExecuteForOutput; override;
  procedure SysAfterExecuteForOutput; override;
  function SysNeedToRefineSQL: boolean; override;
  procedure SysRefineSQL; override;
  procedure SysPrepareFailed; override;
  function SysPrepare: boolean; override;
  procedure SysPrepareSQL; override;
  procedure SysInitRawSQL; override;
  procedure SysFinishRawSQL; override;
  procedure SysExecPrepare; override;
  procedure SysUnprepare; override;
  procedure SysSQLChange( Sender: TObject; var Unprepare: boolean ); override;
  procedure SysUpdateDescriptors; override;
  procedure SysDescribeVARList( ARow: TIB_Row ); override;
  function DoMacroSubstitute( const ASQL: string ): string; override;
{ New System Methods }
  function GetCanDoSearchedSQL: boolean; //~virtual;
  function SysGetCursorRecordCount: longint; virtual;
  function SysFetchSingle: boolean; //~virtual;
  function SysOpen: boolean; //~virtual;
  function SysRecordCount: longint; virtual;
  function SysRefresh( Rows, Keys: boolean ): boolean; virtual;
  procedure SysOpenAfterLoad; //virtual;
  procedure SysUpdateKeyData( PreserveOldKeyData: boolean ); virtual;
  procedure SysEdit; //~virtual;
  procedure SysLockRow; virtual;
  procedure SysInsert; //~virtual;
  procedure SysInsertRow; virtual;
  procedure SysGetGeneratorValues; //virtual;
  procedure SysGetServerDefaults( PreSetDefaults: boolean ); //virtual;
  procedure SysGetDefaultValues; //virtual;
  procedure SysDelete; //~virtual;
  procedure SysPost( CancelUnmodified, IsRetaining: boolean ); virtual;
  procedure SysExecPost( CancelUnmodified: boolean ); virtual;
  procedure SysPostSearch( EndSearchMode: boolean ); //~virtual;
  procedure SysPostEditedRow; virtual;
  procedure SysPostInsertedRow; virtual;
  procedure SysPostDeletedRow; //~virtual;
  procedure SysCancel; virtual;
  procedure SysCancelSearch; //~virtual;
  procedure SysCancelEditedRow; //~virtual;
  procedure SysCancelInsertedRow; virtual;
  procedure SysCancelDeletedRow; //~virtual;
  procedure SysClearLock; //~virtual;
  procedure SysLast; virtual;
  procedure SysFirst; virtual;
  function SysMoveBy( JumpRecs: longint ): longint; virtual;
  function SysCursorMoveByRow( JumpRecs: longint ): longint;
  procedure SysMasterSearchChanged;
  procedure SysBeforeScroll; virtual;
  procedure SysAfterScroll; virtual;
  procedure SysStateChanged; //~virtual;
  procedure SysKeyStateChanged; //~virtual;
  procedure SysUpdateKeyLinksData; //~virtual;
  procedure SysMasterStateChanged; //~virtual;
  procedure SysSearch; //~virtual;
  procedure SysKeyDataChange( AField: TIB_Column ); //~virtual;
  procedure SysMasterDataChange( AField: TIB_Column ); //~virtual;
  procedure SysMasterDataUpdate( AField: TIB_Column ); //~virtual;
  procedure SysProcessCallback( Status: TIB_CallbackStatus ); virtual;
  procedure SysUpdateState; //virtual;
  procedure SysCheckOrderingLink;
  procedure SysCheckSearchingLink;
{ Internal method handlers }
  procedure SysBeforeOpen; virtual;
  procedure SysAfterOpen; virtual;
  procedure SysJustBeforeOpen; virtual;
  procedure SysJustAfterOpen; virtual;
  procedure SysBeforeClose; virtual;
  procedure SysAfterClose; virtual;
  procedure SysBeforeSearch; virtual;
  procedure SysAfterSearch; virtual;
  procedure SysBeforeEdit; virtual;
  procedure SysAfterEdit; virtual;
  procedure SysBeforeInsert; virtual;
  procedure SysAfterInsert; virtual;
  procedure SysBeforeDelete; virtual;
  procedure SysAfterDelete; virtual;
  procedure SysBeforePost; virtual;
  procedure SysAfterPost; virtual;
  procedure SysBeforeCancel; virtual;
  procedure SysAfterCancel; virtual;
{ System Cursor handling methods }
  procedure SysFetchAll( ATicks: DWORD );
  procedure SysFetchNext;
  procedure SysEditCursorRow; virtual;
  procedure SysInsertCursorRow; virtual;
  procedure SysDeleteCursorRow; virtual;
  procedure SysAfterFetchCursorEof; virtual;
  function SysAfterFetchCursorRow: boolean; virtual;
{ DML Methods }
  procedure SQL_DeleteRow; virtual;
  procedure SQL_EditRow; virtual;
  function SQL_LockRow: boolean; virtual;
  procedure SQL_InsertRow; virtual;
{ API Function Calls }
  function API_FetchRow: isc_long;
  procedure API_OpenCursor( const ACursorName: string );
  procedure API_CloseCursor;
{ Event Dispatch Methods }
  procedure DoBeforeOpen; dynamic;
  procedure DoAfterOpen; dynamic;
  procedure DoBeforeClose; dynamic;
  procedure DoAfterClose; dynamic;
  procedure DoBeforeSearch; dynamic;
  procedure DoAfterSearch; dynamic;
  procedure DoBeforeEdit; dynamic;
  procedure DoBeforeScroll; virtual;
  procedure DoAfterEdit; dynamic;
  procedure DoBeforeInsert; dynamic;
  procedure DoAfterInsert; dynamic;
  procedure DoAfterDelete; dynamic;
  procedure DoBeforeDelete; dynamic;
  procedure DoBeforePost; dynamic;
  procedure DoAfterPost; dynamic;
  procedure DoBeforeCancel; dynamic;
  procedure DoAfterCancel; dynamic;
  procedure DoAfterScroll; virtual;
  procedure DoKeyDataChange( AField: TIB_Column ); dynamic;
  procedure DoNewRecord; dynamic;
  procedure DoOrderingChanged; dynamic;
  procedure DoAppCallback; virtual;
  procedure DoCallback; virtual;
  procedure DoDMLCacheAnnounceItem(
                         ADMLCacheItemType: TIB_DMLCacheItemType ); //~virtual;
  procedure DoDMLCacheReceiveItem(
                           const ADMLCacheItem: TIB_DMLCacheItem ); //~virtual;
  // These two defined for use in TIB_Cursor (and similar derivatives)
  // - the derivatives will simply create a public version of this function
  // that call inherited to access these functions.  Buffered dataset
  // derivatives should not use these functions.
  procedure APIFirst;
  procedure APINext;
{ Link Event Dispatch Methods }
  procedure DoLinkStateChanged;
{ General Methods }
  procedure SQLSectionChange( Sender: TObject ); //~virtual;

public
{: This property indicates whether there are actually records in the buffer, as
opposed to indicating whether the dataset was opened and records were fetched
into the buffer.  It is necessary for cases where it is possible that records
might be inserted into the buffer without the dataset being opened.  This
property will tell you whether just the buffer is active. }
  function BufferActive: boolean;
{: This is a reference to the datalink used to maintain a relationship with the
KeyLinks master dataset.
<br><br>Lookup controls may wish to read properties from the KeyDataLink.}
  property IB_KeyDataLink: TIB_KeyDataLink read FIB_KeyDataLink;
{: This flag can be used to find out whether the dataset is going to be retained
when it is being posted.  This may affect the validation logic on certain columns.
You may only want to raise an exception for a certain condition when the post
is not going to be retained.}
  property IsPostRetaining: boolean read GetIsPostRetaining;
{: This flag can be used to indicate that an insert is being initialised. This
can be particularly useful in DataChange events when you want to check that the
datachange occurred due to default value or other initialisation.}
  property IsInsertPending: boolean read FInsertPending;
{: After the post/retaining has been performed, this property can verify it in
case you want a certain action to follow it.  For example, if you are cancelling
a master dataset row in a master-detail relationship, that had been inserted
with PostRetaining, you will need to delete the child records too. (Of course,
you can have cascading deletes via ref integrity or a trigger to do that too.)}
  property HasPostRetained: boolean read FHasPostRetained;

{: RowNum of the underlying unidirectional cursor that brings records into the
buffer. This is the same as RowNum on a unidirectional dataset like TIB_Cursor.}
  property CursorRowNum: longint read FCursorRowNum;
{: Bof status of the underlying cursor.}
  property CursorBof: boolean read GetCursorBof;
{: Eof status of the underlying cursor.}
  property CursorEof: boolean read FCursorEof;
{: Name of the cursor defined on the server for fetching records from.}
  property CursorName: string read FCursorName;

{ General properties }

{: This property indicates whether the state and data change events are being
cached and not propagated to the datasource and datalinks.}
  property ControlsDisabledLevel: integer read FControlsDisabledLevel;
{: This indicates whether a cursor is open on the server.}
  property CursorIsOpen: boolean read FCursorIsOpen;
{: This property applies where a relationship has been formed between a column in
this dataset and the unique key of another dataset, by way of the other dataset's
KeyLinks property, in order to pick up and display a single, corresponding value
from that other dataset.  The other dataset's KeySource property points to this
dataset's DataSource. A KeyDataFreezeLevel lower than 1 indicates that this
dataset's pointer column is allowed to be modified by scrolling in the linked
dataset and selecting a different value. <br><br>
The property is maintained internally and it not intended to be utilized by the
application programmer.}
  property KeyDataFreezeLevel: integer read FKeyDataFreezeLevel;
{: In a relationship which has been formed by pointing a column in this dataset
to the unique key of another dataset, by way of the other dataset's KeyLinks
and KeySource properties, this property is used to keep track of whether
the other dataset is currently being scrolled to seek a match for the pointing
column's value.  If so, this property will return a value higher than zero and
prevent the current row from being put into dssEdit state.
<br><br>
The property is maintained internally and it not intended to be utilized by the
application programmer. }
  property KeyChildUpdatingLevel: integer read FKeyChildUpdatingLevel;
{: This property determines whether the KeyLinks should be defined automatically
if none were provided by the developer.}
  property KeyLinksAutoDefine: boolean read FKeyLinksAutoDefine
                                       write FKeyLinksAutoDefine
                                       default false;
{: This flag indicates whether the KeyLinks property was defined by the system,
from querying the metadata for a primary or unique key ( True ) or defined by the
developer supplying values for it ( False ). }
  property KeyLinksAutoDefined: boolean read FKeyLinksAutoDefined;
{: Returns True if the KeyLinks are defined using the DB_KEY column.}
  property KeyLinksAreDBKEY: boolean read FKeyLinksAreDBKEY;
{: If the dataset is a dependent dataset having its MasterSearch property set
True, this flag stores True if the dataset is active when it follows its master
into dssSearch mode.
<br><br>
Then, when the master leaves dssSearch mode the dependent dataset will be
reopened if the flag indicates it was previously active. }
  property MasterSearchWasActive: boolean read FMasterSearchWasActive;
{: Flag which is set internally to indicate that the dataset is being refreshed
in response to its master dataset in a MasterLinks relationship having had its
data changed. }
  property MasterDataChangeLevel: integer read FMasterDataChangeLevel;
{: Property indicating the state of the dataset immediately before it was
cancelled.  It will be either dssEdit, dssInsert or dssDelete.
<br><br>
It is commonly useful to read this property in the AfterCancel event. }
  property CancelledState: TIB_DatasetState read FCancelledState;
{: Property indicating the state of the dataset immediately before it was last
posted.  It will be either dssEdit, dssInsert or dssDelete.
<br><br>
This property can be read in the AfterPost event to determine which type of DML
was just posted to the server.}
  property PostedState: TIB_DatasetState read FPostedState;
{: Flag to indicate which portion of the SQL property is being changed.
<br><br>
It applies only to the SQLSelect, SQLFrom, etc. pre-parsed sections of the
SQL property.}
  property SQLSectionChanging: integer read FSQLSectionChanging;
{: Temporary reference used when a call to WriteSearch() or ReadSearch()
is performed.}
  property SearchCriteria: TStrings read FSearchCriteria;
{: Preparsed access to the SELECT portion of the SQL in a SELECT statement, from
the beginning of the statement up to the beginning of the FROM  portion.
<br><br>
This property is resolved to the SQL property immediately.}
  property SQLSelect: TStrings index ssSelect read GetSQLSection
                                              write SetSQLSection
                                              stored false;
{: Preparsed access to the FROM portion of the SQL SELECT statement, from the
FROM keyword up to the beginning of the next major portion.
<br><br>
This property is resolved to the SQL property immediately.}
  property SQLFrom: TStrings Index ssFrom read GetSQLSection
                                          write SetSQLSection
                                          stored false;
{: Preparsed access to the WHERE clause of the SQL statement, from the keyword
WHERE up to the next major portion.
<br><br>
This property is resolved to the SQL property immediately.}
  property SQLWhere: TStrings Index ssWhere read GetSQLSection
                                            write SetSQLSection
                                            stored false;
{: Preparsed access to the GROUP BY portion of SQL SELECT statement, from the
keyword GROUP BY up to the next major portion.
<br><br>
This property is resolved to the SQL property immediately.}
  property SQLGroup: TStrings Index ssGroup read GetSQLSection
                                            write SetSQLSection
                                            stored false;
{: Preparsed access to the HAVING portion of the SQL statement, from the keyword
HAVING up to the next major portion.
<br><br>
This property is resolved to the SQL property immediately.}
  property SQLHaving: TStrings Index ssHaving read GetSQLSection
                                              write SetSQLSection
                                              stored false;
{: Preparsed access to the UNION  portion of the SQL statement, from the keyword
UNION up to the next major portion.
<br><br>
This property is resolved to the SQL property immediately.}
  property SQLUnion: TStrings Index ssUnion read GetSQLSection
                                            write SetSQLSection
                                            stored false;
{: Preparsed access to the PLAN portion of the SQL statement, from the keyword
PLAN up to the next major portion.
<br><br>
This property is resolved to the SQL property immediately.}
  property SQLPlan: TStrings Index ssPlan read GetSQLSection
                                          write SetSQLSection
                                          stored false;
{: Preparsed access to the  the ORDER BY portion of the SQL statement, from the
keyword ORDER BY up to the next major portion.
<br><br>
This property is resolved to the SQL property immediately.}
  property SQLOrder: TStrings Index ssOrder read GetSQLSection
                                            write SetSQLSection
                                            stored false;
{: Preparsed access to the FOR UPDATE portion of the SQL statement, from the
keyword FOR UPDATE up to the next major portion.
<br><br>
This property is resolved to the SQL property immediately.}
  property SQLForUpdate: TStrings Index ssForUpdate read GetSQLSection
                                                    write SetSQLSection
                                                    stored false;
{: Message prompt used to confirm if a record should be deleted or not.}
  property ConfirmDeletePrompt: TStrings read FConfirmDeletePrompt
                                         write SetConfirmDeletePrompt;
{: This property controls how a dataset interacts with the DML cache of its
transaction in order to synchronize changes between datasets automatically.
<br><br>
It is possible to override the default behavior by using the
OnDMLCacheAnnounceItem and OnDMLReceiveItem events. Otherwise, the KeyLinks
columns and values are used by default.}
  property DMLCacheFlags: TIB_DMLCacheFlagsSet read FDMLCacheFlags
                                               write FDMLCacheFlags
                                               default [];
{: This property controls whether the dataset should be refreshed after an input
parameter has been changed.}
  property RefreshOnParamChange: boolean read FRefreshOnParamChange
                                         write FRefreshOnParamChange
                                         default false;
{: This property is used to supply a custom SQL statement that will take care
of deleting the current row of the dataset. Its syntax is the same as that used
for the VCL's TUpdateSQL.DeleteSQL property.
<br><br>
For example:
<br><br>
DELETE FROM MYTABLE WHERE MYKEY = :OLD_MYKEY
<br><br>
Prefixing the column name with OLD_ on the parameter will ensure that the proper
association is made with the "OLD." state variable on the server.
<br><br>
It is NOT necessary to use this component if you have properly assigned KeyLinks
to the dataset. IBO automatically generates the standard statements to perform
deletes.
<br>
I have found this property useful to perform non-standard actions when deleting
a record from a dataset. One case could be where the dataset is actually
derived from a select stored procedure. You can use the DeleteSQL to call
another stored procedure that you have created to perform the delete.
<br><br>
For example:
<br><br>
EXECUTE PROCEDURE MYDELETEPROC ( :OLD_MYKEY )
<br><br>
This will pass in the necessary information to the stored procedure and then as
long as you did your job right it will take care of whatever database actions
are required to effectively delete that record.
<br><br>
Keep in mind that when using a stored procedure you do not have the automatic
protection of checking the RowsAffected property performed for you. It will be
a very good idea to test such practices thoroughly.}
  property DeleteSQL: TIB_StringList read GetDeleteSQL write SetDeleteSQL;
{: This property is used to supply a custom SQL statement that will take care
of editing the current row of the dataset. Its syntax is the same as that used
for the VCL's TUpdateSQL.ModifySQL property.
<br><br>
For example:
<br><br>
<br>UPDATE MYTABLE SET
<br>  MYCOL1 = :MYCOL1
<br>  MYCOL2 = :MYCOL2
<br>WHERE MYKEY = :OLD_MYKEY
<br><br>
Prefixing the column name with OLD_ on the parameter will ensure that the proper
association is made with the "OLD." state variable on the server.
<br><br>
It is NOT necessary to use this component if you have properly assigned KeyLinks
to the dataset. IBO automatically generates the standard statements to perform
updates.
<br>
I have found this property useful to perform non-standard actions when editing
a record in a dataset. One case could be where the dataset is actually
derived from a select stored procedure. You can use the EditSQL to call
another stored procedure that you have created to perform the update.
<br><br>
For example:
<br><br>
EXECUTE PROCEDURE MYEDITPROC ( :MYCOL1, :MYCOL2, :OLD_MYKEY )
<br><br>
This will pass in the necessary information to the stored procedure and then as
long as you did your job right it will take care of whatever database actions
are required to effectively update that record.
<br><br>
Keep in mind that when using a stored procedure you do not have the automatic
protection of checking the RowsAffected property performed for you. It will be
a very good idea to test such practices thoroughly.}
  property EditSQL: TIB_StringList read GetEditSQL write SetEditSQL;
{: This property works almost exactly like the EditSQL property except its
purpose is to perform a dummy update to the row which you want locked.
<br><br>
For example:
<br><br>
<br>UPDATE MYTABLE SET
<br>  MYCOL1 = MYCOL1
<br>WHERE MYKEY = :OLD_MYKEY
<br><br>
Notice that only the OLD_MYKEY column uses a parameter.  The "lock" is set by
simply setting the other column directly to its own value.
<br><br>
This property can be ignored except in specialized situations, since IBO will
execute this locking statement automatically when KeyLinks are supplied.
<br><br>
}
  property LockSQL: TIB_StringList read GetLockSQL write SetLockSQL;
{: This property works just like the VCL's TUpdateSQL.InsertSQL property.
<br><br>
For example:
<br><br>
<br>INSERT INTO MYTABLE ( MYCOL1, MYCOL2 )
<br>VALUES ( :MYCOL1, MYCOL2 )
<br><br>
IBO automatically generates an INSERT statement for datasets output from a direct
query.  This property becomes useful in more complex INSERT situations: <ul>
<li>As with EditSQL and DeleteSQL, when your dataset is the output of a SELECT
from a stored procedure, or a joined dataset where you want to your insert to
add rows to more than one table, it provides a way to pass parameters to a
stored procedure you have created to perform one or more operations that
will generate new rows in the dataset.<br><br>
<li>Your dataset is formed across a many-to-many relationship and you need to
insert a row into the junction (or intersecting) table as well.  Employing a
custom InsertSQL can make the interface for the complex master-master-detail
relationship seem as easy as a master-detail relationship.
</ul> }
  property InsertSQL: TIB_StringList read GetInsertSQL write SetInsertSQL;

{ Events }

{: This event serves as a hook to enable customized behavior while a query
is running. By setting the CallbackInc property to 0 or more you will get a
callback for each increment indicated. This gives Windows the opportunity to
process application messages such a mouse clicks, key presses, etc.
<br><br>
Caution must be observed when performing certain operations that may result in a
problem. For example, closing a form that has a running query on it could be
problematic.
<br><br>
One way to use this event is to provide a button for the user which, when
clicked, would call the AbortFetching method to cause the execution of fetches
from the query to cease.
<br><br>
This event also serves well for maintaining a progress meter on a running query.
See the IBF_DataPump form for one such implementation.}
  property OnCallback: TIB_CallbackEvent read FOnCallback
                                         write FOnCallback;
{: This event provides a means to display a custom dialog to prompt the user
before they delete a record.}
  property OnConfirmDelete: TIB_ConfirmationEvent read FOnConfirmDelete
                                                  write FOnConfirmDelete;
{: This event is called for the IB_Cursor component after each row is fetched.}
  property AfterFetchRow: TIB_DatasetEvent read FAfterFetchRow
                                           write FAfterFetchRow;
{: This event is called for the IB_Cursor component after Eof is fetched.}
  property AfterFetchEof: TIB_DatasetEvent read FAfterFetchEof
                                           write FAfterFetchEof;
{: Dataset event.}
  property BeforeOpen:   TIB_DatasetEvent read FBeforeOpen
                                          write FBeforeOpen;
{: Dataset event.}
  property AfterOpen:    TIB_DatasetEvent read FAfterOpen
                                          write FAfterOpen;
{: Dataset event.}
  property BeforeClose:  TIB_DatasetEvent read FBeforeClose
                                          write FBeforeClose;
{: Dataset event.}
  property AfterClose:   TIB_DatasetEvent read FAfterClose
                                          write FAfterClose;
{: If the Search method is called, this event will fire immediately before the
dataset enters dssSearch state.}
  property BeforeSearch: TIB_DatasetEvent read FBeforeSearch
                                          write FBeforeSearch;
{: If the Search method is called, this event will fire just after the dataset
has entered dssSearch state.}
  property AfterSearch:  TIB_DatasetEvent read FAfterSearch
                                          write FAfterSearch;
{: Dataset event.}
  property BeforeEdit:   TIB_DatasetEvent read FBeforeEdit
                                          write FBeforeEdit;
{: Dataset event.}
  property AfterEdit:    TIB_DatasetEvent read FAfterEdit
                                          write FAfterEdit;
{: Dataset event.}
  property BeforeInsert: TIB_DatasetEvent read FBeforeInsert
                                          write FBeforeInsert;
{: Dataset event.}
  property AfterInsert:  TIB_DatasetEvent read FAfterInsert
                                          write FAfterInsert;
{: Dataset event.}
  property BeforeDelete: TIB_DatasetEvent read FBeforeDelete
                                          write FBeforeDelete;
{: Dataset event.}
  property BeforeScroll:  TIB_DatasetEvent read FBeforeScroll
                                           write FBeforeScroll;
{: Dataset event.}
  property AfterDelete:  TIB_DatasetEvent read FAfterDelete
                                          write FAfterDelete;
{: Dataset event.}
  property BeforePost:   TIB_DatasetEvent read FBeforePost
                                          write FBeforePost;
{: Dataset event.}
  property AfterPost:    TIB_DatasetEvent read FAfterPost
                                          write FAfterPost;
{: Dataset event.}
  property BeforeCancel: TIB_DatasetEvent read FBeforeCancel
                                          write FBeforeCancel;
{: Dataset event.}
  property AfterCancel:  TIB_DatasetEvent read FAfterCancel
                                          write FAfterCancel;
{: Dataset event.}
  property AfterScroll:  TIB_DatasetEvent read FAfterScroll
                                          write FAfterScroll;
{: This event is called when a record is inserted into a dataset.
<br><br>
If any changes are performed in this event's handler method, the buffer is
flagged as unmodified.}
  property OnNewRecord: TIB_DatasetEvent read FOnNewRecord
                                         write FOnNewRecord;
{: This event is called when the OrderingItemNo information of a dataset is
changed.
<br><br>
It can also be triggered by a change to the OrderingItems or OrderingLinks
properties.}
  property OnOrderingChanged: TIB_DatasetEvent read FOnOrderingChanged
                                               write FOnOrderingChanged;
{: This event allows for a custom method to determine the number of rows in a
dataset. The default method does not work for queries that have complicated
GROUP BY conditions.
<br><br>
Some alternative SELECT COUNT( * ) statement should be put together to determine
what the count should be.
<br><br>
If you are using an IB_Query it may also be possible to perform a FetchAll
and then assign the number of the items in the buffer. The FetchAll could be
aborted by the user if it takes too long.}
  property OnGetRecordCount: TIB_CustomRecordCount read FOnGetRecordCount
                                                   write FOnGetRecordCount;
{: This event is used to prevent the cursor from being scrolled.
<br><br>
Please be careful that you do not create an infinite loop and please do not
produce a visual prompt to the user. This is not intended for visual use.}
  property OnGetCanScroll: TIB_GetCanScrollEvent
      read FOnGetCanScroll
     write FOnGetCanScroll;
{: This event is used to prevent modifications by custom logic.
<br><br>
Please be careful that you do not create an infinite loop and please do not
produce a visual prompt to the user. This is not intended for visual use.}
  property OnGetCanModify: TIB_GetCanModifyEvent
      read FOnGetCanModify
     write FOnGetCanModify;
{: This event enables a custom handler to be supplied in order to perform
the update on the server. An exception may be raised in this event and the
Post process will be stopped.
<br><br>
It may be easier to assign a statement to the EditSQL property that can be
either an UPDATE statement or an EXECUTE PROCEDURE statement.}
  property OnCustomEdit: TIB_DatasetEvent read FOnCustomEdit
                                          write FOnCustomEdit;
{: This event enables a custom handler to be supplied in order to perform
the delete on the server. An exception may be raised in this event and the
Post process will be stopped.
<br><br>
The CSDemos sample application shows how a stored procedure is used to
perform the delete instead of the default way.
<br><br>
It may be easier to assign a statement to the DeleteSQL property that can be
either a DELETE statement or an EXECUTE PROCEDURE statement.}
  property OnCustomDelete: TIB_DatasetEvent read FOnCustomDelete
                                            write FOnCustomDelete;
{: This event enables a custom handler to be supplied in order to perform
the insert on the server. An exception may be raised in this event and the
Post process will be stopped.
<br><br>
It may be easier to assign a statement to the InsertSQL property that can be
either an INSERT statement or an EXECUTE PROCEDURE statement.}
  property OnCustomInsert: TIB_DatasetEvent read FOnCustomInsert
                                            write FOnCustomInsert;
{: This event allows a custom action to be performed in order to consider
the record about to go into dssEdit state as locked. If the lock fails an
exception may be raised.
<br><br>
It may be easier to assign a statement to the LockSQL property that can be
either an UPDATE statement or an EXECUTE PROCEDURE statement.}
  property OnCustomLockRow: TIB_DatasetEvent read FOnCustomLockRow
                                             write FOnCustomLockRow;
{: This event is used to customize how the dataset will announce that changes
have occurred to other datasets.
<br><br>
This is typically done by calling:<br>
   DefaultDMLCacheAnnounceItem( ADMLCacheItemType );
<br><br>
For your reference, in case you want to emulate it, here is what this method
does internally:
<br><br>
<code>
procedure TIB_Dataset.DefaultDMLCacheAnnounceItem(
                                      ADMLCacheItemType: TIB_DMLCacheItemType );
var
  ii: integer;
  tmpStr: string;
begin
  tmpStr := '';
  for ii := 0 to KeyFields.ColumnCount - 1 do begin
    if ii > 0 then begin
      tmpStr := tmpStr + ';';
    end;
    tmpStr := tmpStr + KeyFields[ii].FieldName;
  end;
  case ADMLCacheItemType of
    ditEdit, ditDelete:
      IB_Transaction.AddDMLCacheItem( IB_Connection,
                                      Self,
                                      tmpStr,
                                      Fields.OldValues[ tmpStr ],
                                      ADMLCacheItemType );
    ditInsert:
      IB_Transaction.AddDMLCacheItem( IB_Connection,
                                      Self,
                                      tmpStr,
                                      Fields.Values[ tmpStr ],
                                      ADMLCacheItemType );
  end;
end;
</code>
<br>
Please see the Survey sample application in the D4Apps folder.}
  property OnDMLCacheAnnounceItem: TIB_AnnounceDMLCacheItemEvent
    read FOnDMLCacheAnnounceItem
   write FOnDMLCacheAnnounceItem;
{: This event is used to customize how the dataset will respond to the
announcement from another dataset that changes have occurred.
<br><br>
Here is an example of what the DefaultDMLCacheReceivedItem( ADMLCacheItem );
method does so that you can make your own customized behavior if necessary.
<br><br>
<code>
procedure TIB_BDataset.DefaultDMLCacheReceivedItem(
  const ADMLCacheItem: TIB_DMLCacheItem);
var
  ii: integer;
  tmpStr: string;
begin
  tmpStr := '';
  for ii := 0 to KeyFields.ColumnCount - 1 do begin
    if ii > 0 then begin
      tmpStr := tmpStr + ';';
    end;
    tmpStr := tmpStr + KeyFields[ii].FieldName;
  end;
  if AnsiCompareText( ADMLCacheItem.KeyFieldNames, tmpStr ) = 0 then begin
    KeyFields.Values[ ADMLCacheItem.KeyFieldNames ] :=
      ADMLCacheItem.KeyFieldValues;
    case ADMLCacheItem.DMLCacheItemType of
      ditEdit: InvalidateBookmark( KeyFields.RowData );
      ditDelete: DeleteBufferBookmark( KeyFields.RowData );
      ditInsert: InsertBufferBookmark( KeyFields.RowData );
    end;
  end;
end;
</code>
<br>
Please see the Survey sample application in the D4Apps folder.}
  property OnDMLCacheReceivedItem: TIB_ReceiveDMLCacheItemEvent
    read FOnDMLCacheReceivedItem
   write FOnDMLCacheReceivedItem;
{: VCL Compatibility.}
  property OnEditError: TIB_DataSetErrorEvent read FOnEditError
                                              write FOnEditError;
{: VCL Compatibility.}
  property OnPostError: TIB_DataSetErrorEvent read FOnPostError
                                              write FOnPostError;
{: VCL Compatibility.}
  property OnDeleteError: TIB_DataSetErrorEvent read FOnDeleteError
                                                write FOnDeleteError;

public

{ Inherited Methods }
{$IFNDEF HELPSCAN}
  constructor Create( AOwner : TComponent ); override;
  destructor Destroy; override;
  procedure Execute; override;
  procedure InvalidateSQL; override;
  function FindKeyField( const FieldName: string ): TIB_Column; override;
  procedure FreeServerResources; override;
{: System method for internal usage only.}
  procedure MasterToChildAction( AAction: TIB_MasterChildAction );
{: System method for internal usage only.}
  procedure KeyToChildAction( AAction: TIB_KeyChildAction; Info: longint );
{$ENDIF}

{ Methods }

  function DoConfirmDelete: boolean; //~virtual;
  function FindBufferField( const FieldName: string ): TIB_Column;

{: This is a very useful method that takes the SQL and search criteria of
one dataset and assigns it to another dataset.
<br><br>
It is ideal for tranferring the search criteria from an IB_Query, that is used
for browsing and searching, across to an IB_Cursor component from which you want
to run a report or export inside a separate, snapshot-isolated (tiConcurrency)
transaction.}
  procedure AssignSQLWithSearch( ADataset: TIB_Dataset );
{: This method is used to assign just the WHERE clause of one SQL to another
dataset's SQL.}
  procedure AssignSQLWhere( ADataset: TIB_Dataset );
{: This method will attempt to return a TIB_Column reference for the field
name passed in for the Buffer Fields array.
<br><br>
An exception is raised if it cannot be found.}
  function BufferFieldByName( const AFieldName: string ): TIB_Column;
{: Move to the first record in the buffer.}
  procedure BufferFirst;
{: Move to the last record in the buffer.}
  procedure BufferLast;
{: Move to the nth record in the buffer from the current position.}
  function BufferMoveBy( Increment: longint ):longint;
{: Move to the next record in the buffer.}
  procedure BufferNext;
{: Move to the prior record in the buffer.}
  procedure BufferPrior;
{: Call this method to invoke the API call that will cancel the running
query on the server.
<br>
<br>
NOTE: This is for InterBase version 7 and up only.}
  procedure CancelQuery; override;
{: Checks to make sure that the dataset is active.}
  procedure CheckActive;
{: This will attempt to post a record if necessary.}
  procedure CheckBrowseMode;
{: This checks to see if the dataset can scroll before the record pointer is
moved to a new record.}
  procedure CheckCanScroll;
{: Checks to make sure that the dataset is prepared.}
  procedure CheckPrepared;
{: Check to make sure that all data aware controls have properly supplied
data for the record.}
  procedure CheckRequiredFields; virtual;
{: Indicate that fetches should processed with a callback.}
  procedure BeginCallbackFetching;
{: Indicate that fetches should no longer be processed with a callback.}
  procedure EndCallbackFetching;
{: This begins a nested bracket where no callbacks are allowed.}
  procedure BeginCallbackFreeze;
{: This ends a nested bracket where no callbacks were allowed.}
  procedure EndCallbackFreeze;
{: In a relationship which has been formed by pointing a column in this dataset
to the unique key of another dataset, by way of the other dataset's KeyLinks
and KeySource properties, this procedure begins a nested bracket that disables
the ability to modify the pointing column by scrolling to and selecting a
different row in the other dataset.}
  procedure BeginKeyDataFreeze;
{: In a relationship which has been formed by pointing a column in this dataset
to the unique key of another dataset, by way of the other dataset's KeyLinks
and KeySource properties, this procedure terminates a nested bracket that
disabled the ability to modify the pointing column by scrolling to and selecting
a different row in the other dataset.}
  procedure EndKeyDataFreeze;
{: Increment the freeze level determining whether state and data change events
are propagated to the datasource and datalink components.}
  procedure DisableControls;
{: This procedure can be used in the corresponding event to make it easy to
peform the default behavior and augment it with additional behavior.  An
appropriate case would be where a dataset is drawn from a view or stored
procedure and more than one item may need to be announced.}
  procedure DefaultDMLCacheAnnounceItem(
                             ADMLCacheItemType: TIB_DMLCacheItemType ); //~virtual;
{: }
  procedure DefaultDMLCacheReceivedItem( const ADMLCacheItem: TIB_DMLCacheItem);
                                                                        virtual;
{: Decrement the freeze level that determines whether state and data change
events are propagated to the datasource and datalink components.}
  procedure EnableControls;
{: This method allows a state change event to be sent to all of the datalink
components.}
  procedure StateChanged;
{: This method applies to the IB_Query component. It releases all of the
individual record buffers and causes them to be refetched from the server the
next time they are referenced.}
  procedure InvalidateRows; virtual;
{: This function gives access to the columns defined in the KeyLinks property.
<br><br>
It is used in conjunction with KeyFields[]. Refer to it for more information.}
  function KeyFieldByName( const AFieldName: string ): TIB_Column;
{: This method applies to the IB_Query component. It ensures that all of the
individual record buffers for the range defined are fetched from the server.}
  function ValidateRows( Start, Finish: longint ): boolean; virtual;
{: This method applies to the IB_Query component. It releases an
individual record buffer and causes it to be refetched from the server the
next time it is referenced.
<br><br>
The IB_Grid and other data aware controls respond to this event and will
immediately refetch the record if it is current.
<br><br>
This is ideal for causing a dataset to reflect changes made in a stored
procedure or trigger without having to dump and refetch the entire dataset,
as the BDE and VCL require.}
  procedure InvalidateRowNum( ARowNum: longint ); virtual;
{: This method applies to the IB_Query component. It releases an
individual record buffer and causes it to be refetched from the server the
next time it is referenced.
<br><br>
The IB_Grid and other data aware controls respond to this event and will
immediately refetch the record if it is current.
<br><br>
This is ideal for causing a dataset to reflect changes made in a stored
procedure or trigger without having to dump and refetch the entire dataset
as the BDE and VCL require.}
  function InvalidateBookmark( const ABookmark: string ): boolean; virtual;
{: Sets this dataset as the current focused dataset for the default session.}
  procedure SetFocus;
{: This method is used to fetch a single record only. False is returned if
0 or more than one record exists for the SELECT being executed.}
  function FetchSingle: boolean;
{: Causes all records to be fetched.

In the IB_Query component this will not affect the current cursor position.}
  procedure FetchAll;
{: Make the dataset become active.}
  procedure Open;
{: Close and re-open the dataset.
<br><br>
For the IB_Query it is done in a manner that preserves all rows that have been
selected previously and fetches in only the key columns.
<br><br>
See also the RefreshAction property to see how the cursor should be re-opened.}
  function Refresh: boolean;
{: This method will refresh just the keys of the dataset in order to pick up or
drop inserted or deleted rows for the dataset.
<br><br>
FetchWholeRows must be false or else this method will simply act the same as
the Refresh method because when it is True, whole rows are being fetched rather
than just the keys independently from their associated record buffers.}
  function RefreshKeys: boolean;
{: This method applies to the buffered dataset and causes all record buffers in
the cache to be removed.  Thus, the next time that row is referenced, a
singleton fetch from the server will be required in order to display the row.
It therefore acts as a refresh mechanism because any new changes on the server
would become visible due to the refresh.}
  function RefreshRows: boolean;
{: This does a normal Refresh but it calls InvalidateRows() as well, resulting
in a complete refresh of all of the buffers for the dataset.
<br><br>
It is the same as the Refresh method.}
  function RefreshAll: boolean;
{: Close the dataset.}
  procedure Close;
{: Go to the first record in the dataset.
<br><br>
For the IB_Cursor component this will cause the current cursor to be closed
and reopened.  With an IB_Cursor, then, don't call Open and then First.  Call
Prepare and then First.
<br><br>
With an IB_Query this simply makes the RowNum become 1.}
  procedure First;
{: This procedure will kill the open cursor and flag the cursor as EOF.}
  procedure KillCursor;
{: This positions the cursor at the end of the dataset.
<br><br>
For an IB_Cursor this is Eof and for an IB_Query it is the last valid row of
the dataset.}
  procedure Last;
{: Goes to the Next record in the Dataset or Eof.}
  procedure Next;
{: For IB_Query only:  goes to the previous row in the dataset.
<br><br>
In an IB_Cursor an exception will be raised.
<br><br>
Push InterBase to make scrollable cursors!}
  procedure Prior;
{: Move the current cursor by an increment.
<br><br>
A negative number in a IB_Cursor will raise an exception.}
  function MoveBy( JumpRecs: longint ): longint;
{: Put current record into dssEdit state or raise an exception.}
  procedure Edit;
{: Move to Eof and put current record into dssInsert state or raise
an exception.}
  procedure Append;
{: Put current record into dssInsert state or raise an exception.}
  procedure Insert;
{: Delete the current record or raise an exception.}
  procedure Delete;
{: Post any pending changes made to the dataset or raise an exception.}
  procedure Post;
{: Effectively post the current changes to the server but retain the edit state
of the dataset. In the case of an insert the record is actually inserted and
then when the final post of the retained insert is performed it effectively
performs an edit. If the PostRetained insert is cancelled then the record is
deleted.
<br><br>
This method should not be used when in dssDelete state.}
  procedure PostRetaining;
{: Cancel any pending changes made to the dataset or raise an exception.}
  procedure Cancel;
{: Cause the fetching process to stop and return full control of the
application back to the user.}
  procedure AbortFetching; //~virtual;
{: Returns the number of records in the current dataset with the search
criteria applied.}
  function RecordCount: longint;
{: Put the dataset into dssSearch state.}
  procedure Search;
{: Save the current search criteria aside in a temporary buffer.}
  procedure SaveSearch;
{: Clear out all of the currently defined search criteria.}
  procedure ClearSearch;
{: Recall search criteria that were set aside in a temporary buffer.}
  procedure RecallSearch;
{: Recall the search criteria that were in use when the dataset was last opened.}
  procedure RecallLastSearch;
{: Takes the current search criteria and streams it into a TStrings object.}
  procedure WriteSearch( AStrings: TStrings );
{: Reads search criteria and streams it from a TStrings object.}
  procedure ReadSearch( AStrings: TStrings );
{: Clears out any defined SearchingLinks parameters so that all of the  records
are visible in the dataset.}
  procedure ClearSearchingLinks;
{:  Calling this method allows a self-contained boolean expression to be
included in the SQLWhere clause. All parsing is done for you.}
  procedure AddSQLWhereClause( const WhereClause: string );
{: Returns True if this DataSource is pointed to as a KeySource.
<br><br>
It is used in order to avoid an infinite cycle.}
  function IsKeyLinkedTo( DataSource: TIB_DataSource ): boolean;
{: Returns True if this DataSource is pointed to as a MasterSource.
<br><br>
It is used in order to avoid an infinite cycle.}
  function IsMasterLinkedTo( DataSource: TIB_DataSource ): boolean;
{: Announce an UpdateData event manually.}
  procedure UpdateData; //~virtual;
{: Announce a DataChange event manually.}
  procedure DataChange; //~virtual;
{: This method is handly to get a quick list of the user-friendly names of
the OrderingItems entries. A dialog can be presented with these names in it.
When the user selects one, the IndexNo - 1 can be assigned to the OrderingItemNo
property. If it is the same, you could check to see whether both ASC and DESC
entries exist and, if so, toggle them.}
  procedure GetOrderingItemsList( const OrderingItemsList: TStrings );
{: Pass a TStrings object to this method to have IBO load it with an ALTER TABLE
DDL statement deduced from the current fields and constraints of the dataset as
known to the Schema Cache.
<br><br>
This is useful for avoiding dependencies with foreign keys, since they are not
included in the GetCreateTableSQL() method.}
  procedure GetAlterTableSQL( AStrings: TStrings );
{: Pass a TStrings object to this method to have IBO load it with a CREATE TABLE
DDL statement deduced from the current fields and constraints of the dataset as
known to the Schema Cache.
<br><br>
This method does not return FOREIGN KEY constraints.  A similar method,
GetAlterTableSQL() will get these for you.}
  procedure GetCreateTableSQL( AStrings: TStrings; DomainInfo,
                                                   ConstraintInfo: boolean );
{: Returns the columns defined in the OrderingItems list of entries.
<br><br>
A blank string is returned if no entry exists.}
  function GetOrderingSQL( ItemNo: integer ): string;
{: This method populates a TList with the TIB_Column references for the
buffer fields.}
  procedure GetBufferFieldList( AList: TList; const FieldNames: string );
{: If you would like to maintain the default OnCallback activity and add in
a bit of your own behavior then this method can be used from within your
event handler to maintain the default activity.}
  procedure DefaultProcessCallback( Status: TIB_CallbackStatus );
{: Causes any grid attached to the dataset to ensure that the row with the
supplied RowNum is in the visible display.
<br><br>
Pass in -1 to have it show the current row;  or pass an expression or a
particular value to have it show a specific RowNum.}
  procedure ShowNearest( ARowNum: longint );
{: Retrieve defined default value for specified column, including any
macro substitution.}
  function GetColumnDefaultValue(AColumn: TIB_Column; var DefStr: string): boolean;

{ Abstract Buffering Properties }

{: This only works well for the IB_Query component for now.}
  property BufferFields: TIB_Row read GetBufferFields;
{: This only works well for the IB_Query component for now.}
  property BufferRowNum: longint read GetBufferRowNum write SetBufferRowNum;
{: This only works well for the IB_Query component for now.}
  property BufferEof: boolean read GetBufferEof;
{: This only works well for the IB_Query component for now.}
  property BufferBof: boolean read GetBufferBof;
{: This only works well for the IB_Query component for now.}
  property BufferRowCount: longint read GetBufferRowCount;
{: This only works well for the IB_Query component for now.}
  property BufferHasBof: boolean read GetBufferHasBof;
{: This only works well for the IB_Query component for now.}
  property BufferHasEof: boolean read GetBufferHasEof;

{ Properties }

{: When a dataset is opened this property determines whether all rows should
be fetched.}
  property AutoFetchAll: boolean read FAutoFetchAll
                                 write FAutoFetchAll
                                 default false;
{: When a dataset is opened this property determines whether the first row should
be selected.}
  property AutoFetchFirst: boolean read FAutoFetchFirst
                                   write FAutoFetchFirst
                                   default true;
{: Determines whether a call to the Delete method will be posted immediately.}
  property AutoPostDelete: boolean read FAutoPostDelete
                                   write SetAutoPostDelete
                                   default true;
{: This only works for the IB_Query component for now.}
  property Bookmark: string read GetBookmark write SetBookmark;
{: This only works for the IB_Query component for now.}
  property BufferBookmark: string read GetBufferBookmark
                                  write SetBufferBookmark;
{: Sets the increment determining the interval at which an OnCallback event is
triggered.
<br><br>
If it is set to -1 then there will be no process callback at all.
<br><br>
If it is set to 0 then calls will be made only to Application.ProcessMessages.
<br><br>
If it is set to 1 or higher, the callback timing increment settings will be
checked to see if a callback should be triggered.
<br><br>
This check is performed every nth fetch processed.}
  property CallbackInc: integer read FCallbackInc
                                write FCallbackInc
                                default 5;
{: This message is used by the default callback dialog that appears when
records are being fetched in from the server in bulk quantity.}
  property CallbackCaption: string read FCallbackCaption
                                   write SetCallbackCaption;
{: Stores a reference to the GetTickCount when the query was started.}
  property CallbackInitTick: DWORD read FCallbackInitTick;
{: Provides a built-in storage for maintaining a time-based increment in
milliseconds.
<br><br>
Using this you could update a dialog every second instead of every 10 fetches.
<br><br>
Each 1000 units is a second.}
  property CallbackRefreshTick: DWORD read FCallbackRefreshTick
                                      write FCallbackRefreshTick;
{: When using default callback processing this determines the length of
initial delay in milliseconds before any visible action is taken to inform the
user that a query is being processed.
<br><br>
Each 1000 units is a second.}
  property CallbackInitInt: DWORD read FCallbackInitInt
                                  write FCallbackInitInt
                                  default 3000;
{: When using default callback processing this determines the length of
incremental delay in milliseconds before any visible action is taken to update
the user during the processing of a query.
<br><br>
Each 1000 units is a second.}
  property CallbackRefreshInt: DWORD read FCallbackRefreshInt
                                     write FCallbackRefreshInt
                                     default 250;
{: Returns whether callbacks have been frozen.
<br><br>
If it is Zero then callbacks will be performed if CallbackInc is not -1.
<br><br>
See also the BeginCallbackFreeze and EndCallbackFreeze methods.}
  property CallbackFreezeLevel: integer read GetCallbackFreezeLevel;
{: Indicates if callbacks have been requested for fetching activity.
<br><br>
This is incremented by calling BeginCallbackFetching and Decremented by
calling EndCallbackFetching.}
  property CallbackFetchingLevel: integer read FCallbackFetchingLevel;
{: This indicates whether the dataset's record count has been computed on the
server and whether RecordCount can be read without invoking a potentially
lengthy select statement.}
  property CursorRecordCountValid: boolean read FCursorRecordCountValid;
{: This property returns True if the user aborted the fetching of records by
calling AbortFetching or by closing the dataset.
<br><br>
Fetching will also be aborted if the dataset is put into an edit or insert
state while fetching is going on.
<Br><br>
Be careful that you don't use this flag "after the fact".  For example, it
would be erroneous to call Locate() and then after the call is complete
check the value of this property.  Once the locate was aborted and its
execution was wrapped up then the flag is set back to normal to be ready
for the next call.
<br><br>
The best property to use to find out if the call to Locate() was aborted is
the FetchingAbortedGen flag.  If you store its value aside in a local
variable and then check it again after the call to Locate() then if the user
aborted the number will be different.}
  property FetchingAborted: boolean read FFetchingAborted;
{: This property gives a mark so that IBO can determine, with more accuracy than
just reading a Boolean flag, whether fetching was aborted or not.
<br><br>
This is a sample code snippet:
<br>
<br>var
<br>  FAGen: integer;
<br>begin
<br>  FAGen := MyQuery.FetchingAbortedGen;
<br>  if MyQuery.Locate( ... ) then
<br>    ...
<br>  else
<br>  if FAGen <> MyQuery.FetchingAbortedGen then
<br>    ShowMessage( 'User aborted the locate.' );
<br>end;
}
  property FetchingAbortedGen: integer read FFetchingAbortedGen;
{: Returns the number of datasource components that reference this dataset.}
  property DataSourceCount: integer read GetDataSourceCount;
{: Gives an indexed access to all DataSources that reference this dataset.}
  property DataSources[ Index: integer ]: TIB_DataSource read GetDataSources;
{: Returns the current state of the dataset.}
  property State: TIB_DatasetState read GetState;
{: Returns True if the dataset can scroll backwards.}
  property Unidirectional: boolean read GetUnidirectional;
{: Returns True if the dataset is at the end of the file.}
  property Eof: boolean read GetEof;
{: Returns True if the dataset is at the beginning of the file.}
  property Bof: boolean read GetBof;
{: Current record number of the output.}
  property RecNo: longint read GetRecNo write SetRecNo;
{: This property tells the row number of the BOF position.
<br><br>
This is always 0 unless horizontal dataset refinement is in effect. In this
case, it will indicate which negative row number the BOF position is
currently mapped to. The more records that are fetched with the descending
cursor the more records are appended into the front part of the dataset which
pushes the BOF row more and more negative.}
  property BofRowNum: longint read GetBofRowNum;
{: This is the row number at which the EOF record is positioned.}
  property EofRowNum: longint read GetEofRowNum;
{: Returns the row number at which the cursor is currently positioned.}
  property RowNum: longint read GetRowNum write SetRowNum;
{: Returns True if the dataset can modify the current row or insert a new one.}
  property CanModify: boolean read GetCanModify;
{: Returns True if the dataset can modify the current row.}
  property CanEdit: boolean read GetCanEdit;
{: Returns True if the dataset can insert a new row.}
  property CanInsert: boolean read GetCanInsert;
{: Returns True if the dataset can delete the current row.}
  property CanDelete: boolean read GetCanDelete;
{: Returns True if the dataset can go into search mode to select a subset of rows.}
  property CanSearch: boolean read GetCanSearch;
{: Returns True if the dataset can Scroll to a different row.}
  property CanScroll: boolean read GetCanScroll;
{: Determine whether REQUIRED fields are to be checked just before posting an
edit or insert.}
  property CheckRequired: boolean read FCheckRequired
                                  write FCheckRequired
                                  default true;
{: Returns True if the dataset is in the process of being closed.}
  property ClosePending: boolean read FClosePending;
{: Returns True if the dataset is in the process of being Opend.}
  property OpenPending: boolean read FOpenPending;
{: Returns True if the dataset has changes that are pending.}
  property NeedToPost: boolean read GetNeedToPost;
{: Returns True if any modifications have been made to the current row.}
  property Modified: boolean read GetModified;
{: Returns True if the dataset is currently in the process of cancelling changes.}
  property IsCancelling: boolean read FIsCancelling;
{: Returns True if the dataset is currently in the process of posting changes.}
  property IsPosting: boolean read GetIsPosting;
{: Returns True if a dataset is readonly. This will also return True if a
transaction is ReadOnly.}
  property ReadOnly: boolean read GetReadOnly write SetReadOnly default false;
{: Returns True if a dataset is currently in the process of refreshing.}
  property Refreshing: boolean read GetRefreshing;
{: Prevents the dataset from going into dssEdit state by forcing CanEdit to
return False. Otherwise, any other factors affecting the CanEdit property
will be calculated.}
  property PreventEditing: boolean read FPreventEditing
                                   write SetPreventEditing
                                   default false;
{: Prevents the dataset from going into dssInsert state by forcing CanInsert to
return False. Otherwise, any other factors affecting the CanInsert property
will be calculated.}
  property PreventInserting: boolean read FPreventInserting
                                     write SetPreventInserting
                                     default false;
{: Prevents the dataset from going into dssDelete state by forcing CanDelete to
return False. Otherwise, any other factors affecting the CanDelete property
will be calculated.}
  property PreventDeleting: boolean read FPreventDeleting
                                    write SetPreventDeleting
                                    default false;
{: Determines whether the dataset can go into dssSearch state.}
  property PreventSearching: boolean read FPreventSearching
                                     write SetPreventSearching
                                     default false;
{: Flag to determine whether the KeySource dataset will be prevented from
causing the lookup dataset to perform the seek in order to do the lookup.}
  property PreventKeySeeking: integer read FPreventKeySeeking;
{: When using PessimisticLocking this property indicates whether a lock has been
obtained for the current row in the dataset.
<br><br>
In most cases a lock will not be issued until the row goes into dssEdit state.}
  property IsRowLocked: boolean read FIsRowLocked;
{: Upon going into dssEdit state you can have a dummy update automatically
performed to lock the record that just went into dssEdit state.
<br><br>
If another user has already obtained a lock on the record then an exception
is raised and the record will remain as readonly.
<br><br>
With IB Objects, datasets output from joins across multiple tables are updatable
through custom SQL, so the dummy update executes upon each updatable ROW on the
server that is contributing output to the current row, to ensure that the user
will always be able to post work successfully.
<br><br>
InterBase has row-level locking, which makes it friendly about pessimistic
locking - only those rows that are actually updated get locked.
<br><br>
Because these locks survive only for the duration of the transaction, they are
certain to be cleaned up if a connection failure occurs.}
  property PessimisticLocking: boolean read FPessimisticLocking
                                       write FPessimisticLocking
                                       default false;
{: Returns True if the dataset is currently processing the query by fetching rows
from the server.}
  property Fetching: boolean read FFetching;
{: Rows are being fetched from the server in order to move the current cursor.}
  property ScanningLevel: integer read FScanningLevel;
{: Reference to the KeyDataset being linked to by the KeySource property. Be
sure to check for nil before referencing it.}
  property KeyDataset: TIB_Dataset read GetKeyDataset;
{: Reference to the MasterDataset being linked to by the MasterSource property.
Be sure to check for nil before referencing it.}
  property MasterDataset: TIB_Dataset read GetMasterDataset;
{: Gives the name of the Master Table for the indexed reference in the
MasterLinks property.}
  property MasterRelation [ Index: integer ]: string read GetMasterRelation;
{: Gives the name of the Master Column for the indexed reference in the
MasterLinks property.}
  property MasterFieldName[ Index: integer ]: string read GetMasterFieldName;
{: Not fully implemented yet.}
  property ParamValueLinks;
{: Returns True if a search criterion was changed or other modification was made
to the WHERE clause during the OnPrepareSQL event cycle.}
  property SQLWhereChanged: boolean read GetSQLWhereChanged;
{: Returns True if a search criterion was changed or other modification was made
to the ORDER BY clause during the OnPrepareSQL event cycle.}
  property SQLOrderChanged: boolean read FSQLOrderChanged;
{: During the OnPrepareSQL cycle a self contained boolean expression can be
added into the WHERE clause at different priority levels.
<br><br>
There are three levels that accumulate WHERE clause criteria.  At the last point
of the cycle, these values are taken and placed into the SQL statement sent
to the server according to this priority scheme.}
  property SQLWhereLow: TStrings read FSQLWhereLow;
{: During the OnPrepareSQL cycle a self contained boolean expression can be
added into the WHERE clause at different priority levels.
<br><br>
There are three levels that accumulate WHERE clause criteria.  At the last point
of the cycle, these values are taken and placed into the SQL statement sent
to the server according to this priority scheme.}
  property SQLWhereMed: TStrings read FSQLWhereMed;
{: This property is very useful for having IBO handle the parsing of items to
be added to the WHERE clause of the SELECT statement being constructed. The
OnPrepareSQL phase is the only point where it is valid to add items to this
stringlist.
<br><br>
Each unit of entries must aggregate to a syntactically valid SQL expression that
evaluates to a Boolean True or False.  It is possible to include parentheses to
to provide for nesting of items. By default, all items are "ANDed" together,
but if the OR operator is placed as an item on its own, it will be used instead
of the default 'AND'.
<br><br>
Here are some sample uses of this property:
<br>
<code>
SQLWhereItems.Add( '(' );
SQLWhereItems.Add( 'MYCOL1 = 100' );
SQLWhereItems.Add( 'OR' );
SQLWhereItems.Add( 'MYCOL1 = 200' );
SQLWhereItems.Add( ')' );
</code>
<br>
<br>and when parsed into the finalized SQL statement will end up as:
<br>
<br>WHERE .... < original criteria plus other system stuff >
<br>   AND ( MYCOL1 = 100 OR MYCOL1 = 200 )
<br>
<br>This may seem simple and of little use but it will make it much easier to
<br>build the SQL criteria when combining the logic of numerous controls that
<br>impact the statement.
<br>
<br>
<b>Nesting the parentheses</b><br>
This example shows how the nesting of parentheses can be accomplished.  If an
operator is omitted, AND is used as the default.
<br>
<code>
SQLWhereItems.Add( '(' );
SQLWhereItems.Add( '(' );
SQLWhereItems.Add( 'MYCOL > 100' );
SQLWhereItems.Add( 'MYCOL = 200' );
SQLWhereItems.Add( ')' );
SQLWhereItems.Add( 'MYCOL < 300' );
SQLWhereItems.Add( ')' );
</code>
<br>
<br>when parsed into the finalized SQL statement will end up as:
<br>
<br>WHERE ....
<br>   AND (( MYCOL = 100 AND MYCOL = 200 ) AND MYCOL = 300 )}
  property SQLWhereItems: TStrings read FSQLWhereMed;
{: During the OnPrepareSQL cycle a self contained boolean expression can be
added into the WHERE clause at different priority levels.
<br><br>
There are three levels that accumulate WHERE clause criteria.  At the last point
of the cycle, these values are taken and placed into the SQL statement sent
to the server according to this priority scheme.}
  property SQLWhereHigh: TStrings read FSQLWhereHigh;
{: This property contains a list of columns that the dataset is ordered by.}
  property SQLOrderLinks: TIB_StringList read FSQLOrderLinks;
{: If an OrderingLink is currently active then this will have the name of
the column used to define the OrderingLink entry.}
  property OrderingLink: string read FOrderingLink write SetOrderingLink;
{: If an OrderingLink is currently active then this will have the TIB_Column
reference to the column used to define the OrderingLink entry.}
  property OrderingField: TIB_Column read FOrderingField;
{: Parameter used to improve performance when dealing with large datasets.
<br><br>
This enables the use of dataset refinement with any operation that seeks a
record in the buffered dataset.
<br><br>
This really only applies when working with a buffered dataset but it is
declared at a lower level in abstract fashion.}
  property OrderingParam: TIB_Column read FOrderingParam;
{: If a SearchingLink is currently active then this will have the name of
the column used to define the SearchingLink entry.}
  property SearchingLink: string read FSearchingLink write SetSearchingLink;
{: If a SearchingLink is currently active then this will have the TIB_Column
reference to the parameter used to define the OrderingLink entry.
<br><br>
Get the field reference using the OrderingField property.}
  property SearchingParam: TIB_Column read FSearchingParam;
{: If a SearchingLink is currently active then this will have the name of
the parameter used in the SearchingLink entry.}
  property SearchingParamName: string read FSearchingParamName;
{: Returns True if the OrderingItemNo is in the process of changing.}
  property OrderingItemNoChanging: boolean read GetOrderingItemNoChanging;
{: Returns True if the OrderingLink is in the process of changing.}
  property OrderingLinkChanging: boolean read GetOrderingLinkChanging;
{: Returns True if the SearchingLink is in the process of changing.}
  property SearchingLinkChanging: boolean read GetSearchingLinkChanging;
//{: Returns True if the RefiningLink is currently active.}
//  property RefiningLinksActive: boolean read GetRefiningLinksActive;
{: Returns True if the SearchingLink is currently active.}
  property SearchingLinksActive: boolean read GetSearchingLinksActive;
{: In some cases it is necessary to have deletes performed via a searched
delete instead of a positioned delete. This property will cause the SQL
that is generated for you when deleting a record to be a positioned delete if
False (the default) or a searched delete if True.}
  property SearchedDeletes: boolean read GetSearchedDeletes
                                    write SetSearchedDeletes
                                    default false;
{: This property will cause updates executed behind the scenes to be performed
with searched updates instead of positioned updates. In other words, if you look
in the SQL trace monitor you will see:
<br><br>
UPDATE < table > SET < col > = < val >
WHERE < pk col >= ?< pk param >
<br><br>
instead of:
<br><br>
WHERE CURRENT OF < cursor name >.
<br><br>
Only datasets with a single relation can be live with SearchedUpdates. If you
have a joined dataset that you want to be live then you must use positioned
updates.}
  property SearchedEdits: boolean read GetSearchedEdits
                                  write SetSearchedEdits
                                  default false;
{: This property determines whether all updates are performed through a
prepared DSQL statement or whether a DSQL statement for just the updated
columns should be constructed and immediately executed (the default).}
  property PreparedEdits: boolean read GetPreparedEdits
                                  write SetPreparedEdits
                                  default false;
{: This property determines whether all inserts are performed through a
prepared DSQL statement or whether a DSQL statement for just the modified
(non-NULL) columns should be constructed and immediately executed.}
  property PreparedInserts: boolean read GetPreparedInserts
                                  write SetPreparedInserts
                                  default true;
{: This property allows default column values to be defined on the client.  As
soon as the dataset enters dssInsert state, AsString is assigned the string
values stored in this StringList property.
<br><br>
Use this standard format:
<br><br>[< tablename >.]< columnname >=< string value to be assigned >}
  property DefaultValues: TIB_StringList read FDefaultValues
                                         write SetDefaultValues;

{: Reference to allow access to the columns defined in the KeyLinks property.
<br><br>
<br>This property is used in conjunction with these methods:
<br>function SeekKeyForBufferFields: boolean;
<br>function LookupKeyForBufferFields: boolean;
<br>function LookupKeyForFields: boolean;
<br><br>
Set the values for the KeyLinks columns and then call one of the above three
methods to act upon the key column values.}
  property KeyFields: TIB_Row read GetKeyFields;

{: This property indicates whether any cached updates are waiting to be applied.}
  property UpdatesPending: boolean read GetUpdatesPending;
{: This property returns the current CachedUpdates status of the currently
selected record.}
  property UpdateStatus: TIB_UpdateStatus read GetUpdateStatus;

{------------------------------------------------------------------------------}

{$IFDEF HELPSCAN}
published
{$ELSE}
public
{$ENDIF}

{------------------------------------------------------------------------------}

{: IBO native data aware controls are designed to use a color scheme when a
dataset is in dssSearch, dssEdit, dssInsert states.
<br><br>
It is possible to customize these colors by changing them on the
TIB_SessionProps component.}
  property ColorScheme: boolean read FColorScheme
                                write FColorScheme
                                default false;
{: This property makes it very easy to populate a column with a value from
a generator. Provide entries in the following format:
<br><br>
[< tablename >.]< columnname >=< generatorname >
<br><br>
See the Contact sample application to see this implemented.}
  property GeneratorLinks: TIB_StringList read GetGeneratorLinks
                                          write SetGeneratorLinks;
{: This determines whether DEFAULT constraints that are declared at the server
level should be queried, parsed and used when an insert is performed on the
client.
<br><br>
This can be a bit expensive since the metadata must be queried.}
  property GetServerDefaults: boolean read FGetServerDefaults
                                      write FGetServerDefaults
                                      default false;
{: Contains a list of hints that are used to display more user friendly
messages to the user.
<br><br>
They are used by the various IB_XXXBar controls.}
  property Hints;
{: This property plays a very important role in the IB_Query component.
<br><br>
This property must contain the column(s) that define a unique identifier for
each row of the data on the server.
<br><br>
An IB_Query component maintains a permanent buffer of these key columns that
serve as the backbone of the dataset buffer. Then, on demand, the remaining
whole record buffer is selected in one at a time by internal prepared cursors.
By this means, scrolling to the end of a dataset only requires fetching in all
of the keys and then the last whole record buffer.
<br><br>
The BDE/VCL requires that the entire dataset be fetched in, which can take a
very long time.  The key-driven functionality of IB Objects makes it magnitudes
more responsive than the BDE/VCL when browsing rows at random.
<br><br>
This property is also used in order to define an XBASE-style SET RELATION
relationship between two datasets. With this relationship defined the
following behavior can be observed:
<br><br>
Parent record scrolls: A seek for the new matching child record is performed.
First a check is done in the existing fetched records. If it is found in the
client's buffers, that record becomes the current record in the child dataset.
If it is not found there, a special SELECT statement is formed for searching for
it on the server.  If it is found, the key column values required for fetching
it are returned.<br><br>
With these keys, the columns of the IB_Query's key buffer are fetched until a
matching key is returned.  Because only the key column(s) are being fetched, not
the entire row, it is very fast. <br><br>
The special SELECT also takes any search criteria for the data set into
consideration, so as to fetch in just those keys which it is certain to match
eventually.  Otherwise, non-matching parent data could exist, causing child
dataset to be set to Bof - yes, Bof and not Eof.
<br><br>
Child record is scrolled: This is taken as an indication that the parent
should be made to reference a different child record. So, the parent record
is put into dssEdit state and the necessary columns in the parent record are
updated so that they refer to the newly selected record in the child table.
<br><br>
With this feature it is possible to have multiple columns define the
relationship between two datasets. This is unlike the Lookup stuff in the VCL
that allows tables to be related only through a single column:column link.
<br><br>
It differs also in that the lookup controls do not need properties like
LookupDataset, LookupDataSource, LookupFields, etc., because the linkages are
defined at the dataset level, using KeyLinks and KeySource.  Binding the linkages
of the controls to those of the dataset allows greater programmer control and
flexibility.
<br><br>
See KeySource for a few more details.
<br><br>
There is a sample application called KeyLinks that shows how to set it up.}
  property KeyLinks: TIB_StringList read GetKeyLinks
                                    write SetKeyLinks
                                    stored IsKeyLinksStored;
{: This property is used to determine which columns in the KeyLinks parent
should be updated in order to maintain description columns for display purposes
as the KeyLinks child is scrolled.
<br><br>
Typically, the description column in the parent dataset is driven from an
in-line singleton SELECT in the SELECT list of columns, for example:
<br><br>
SELECT COLUMN1, COLUMN2, LOOKUPCOL
(SELECT DESCRIPTION FROM CHILD_TABLE
WHERE PK = PARENT_TABLE.LOOKUPCOL)
AS MYDESCRIPTION
FROM PARENT_TABLE
<br><br>
This property of the child dataset is additional to the Keysource and KeyLinks
properties that have already been set up to form the relationship with the
KeyDataset parent.
<br><br>
Using the example above, the standard KeyDescLinks format is:<br>
DESCRIPTION=PARENT_DATASET.MYDESCRIPTION
<br><br>
See the Company sample application for a demonstration of how this can be used
to integrate the lookup combobox with a grid.}
  property KeyDescLinks: TIB_StringList read FKeyDescLinks
                                        write SetKeyDescLinks;
{: This property determines whether seeking is performed in the child dataset
of a KeyLinks relationship.}
  property KeySeeking: boolean read FKeySeeking
                               write SetKeySeeking
                               default true;
{: This property defines which dataset is referenced in order to complete
the KeyLinks relationship.  It is set on the child dataset and points to the
datasource of the parent dataset.
<br><br>
This works very much like an XBASE SET RELATION command where one dataset
scrolls another dataset and performs a locate.}
  property KeySource: TIB_DataSource read GetKeySource write SetKeySource;
{: It is possible to make a dataset that is output from a join between tables
capable of inserts and deletes without custom SQL, by indicating which relation
these operations should be applied to.
<br><br>
Observe that only one table can be subjected to update or delete operations by
this means.  If you need to make the insert, update and delete methods operate
on two or more of the tables, stored procedures will be required.  Refer to the
InsertSQL, EditSQL and DeleteSQL properties.}
  property KeyRelation;
{: This property can play a very important role in the IB_Query component when
the dataset is formed by an SQL statement that includes an implicit join.
<br><br>
An implicit join is old SQL-89 syntax, deprecated in IB since version 5.x, of
this form:
<br><br>
SELECT TABLE1.COLUMN1, TABLE1.COLUMN2, TABLE2.COLUMNA, TABLE2.COLUMNB
FROM TABLE1, TABLE2
WHERE TABLE1.COLUMN1 = TABLE2.COLUMNA
AND TABLE2.COLUMNX > TABLE1.COLUMNY
<br><br>
In this statement, the JOIN criteria are mixed up with the SELECT criteria.
This is confusing and inefficient for IBO to parse.  The JoinLinks property is
used to define join and filter criteria necessary for ensuring that the
columns in the KeyLinks property will define each row of the output uniquely
and accurately.
<br><br>
Principally, JoinLinks tells the parser which expressions in the WHERE clause
are JOIN criteria.  JoinLinks for the statement above would be:
<br><br>
TABLE1.COLUMN1=TABLE2.COLUMNA
<br><br>
This simplifies what the internal cursors, that are used to
fetch single record buffers, will be passed in their WHERE clause criteria.  It
removes the need to subject the dataset to being re-prepared every time the
values of the search criteria change. It also reduces the amount of input
parameter data necessary to fetch the individual rows to exactly what is
required - ideally, only key values.
<br><br>
Distinguishing the JOIN criteria avoids the need to pass parameters for the JOIN
criteria into the WHERE clause each time an individual row is fetched and removes
a source of severe performance degradation.
<br><br>
If you are using a version of IB 5.x or higher, you are strongly recommended to
use explicit JOIN syntax and take special care to avoid mixing the two syntaxes
in one statement, which can cause server-side ambiguities that IBO can not
detect or control.  You do not need JoinLinks for a statement that uses pure
explicit JOIN syntax.}
  property JoinLinks: TIB_StringList read FJoinLinks write SetJoinLinks;
{: Reference to the dataset that will serve as the master dataset in a
master detail relationship.}
  property MasterSource: TIB_DataSource read GetMasterSource
                                        write SetMasterSource;
{: This property is used to define the column to column relationships
between the child and parent datasets in a master-detail construction.
The format is as follows:
<br><br>
< childtable >.< childcol >=< mastertable >.< mastercol >
<br><br>
You must include the table name if it exists for MasterSearch to work properly.
<br><br>
MasterLinks has features that help define the relationship between the parent
and child datasets. For example, when the child dataset is inserting a new
record, the proper values from the parent are written into the linking columns of
the child dataset.}
  property MasterLinks: TIB_StringList read FMasterLinks write SetMasterLinks;
{: This property determines whether a child dataset will follow its master into
dssSearch state. If it is True, the search criteria for the child dataset are
used in order to select records from the master dataset.
<br><br>
This is accomplished by adding an EXISTS( SELECT  ) clause that contains
the appropriate relations for the master-detail relationship and the child's
search criteria.
<br><br>
This will only take effect if there is a valid MasterLinks definition.}
  property MasterSearch: boolean read FMasterSearch
                                 write FMasterSearch
                                 default false;
{: Property to take better control over the behavior of datasets when using the
MasterSearch option.}
  property MasterSearchFlags: TIB_MasterSearchFlags read FMasterSearchFlags
                                                    write FMasterSearchFlags
                                       default [ msfOpenMasterOnOpen,
                                                 msfSearchAppliesToMasterOnly ];
{: This property works just like parameterized queries in a TQuery.
<br><br>
It defines which parameters correspond to which fields in the dataset that
MasterSource is referring to.}
  property MasterParamLinks: TIB_StringList read FMasterParamLinks
                                            write SetMasterParamLinks;
{: Maximum number of rows to be fetched. 0 means all rows to Eof.}
  property MaxRows: longint read FMaxRows write FMaxRows default 0;
{: This property will limit a query that is fetching rows to fetching for the
specified period of time and then to abort the fetching process.}
  property MaxTicks: DWORD read FMaxTicks write FMaxTicks default 0;
{: This property determines when the MaxTicks can begin checking to abort the
process of fetching rows. It sets the the minimum number of rows required before
a MaxTicks timeout is allowed.}
  property MinTicksRows: longint read FMinTicksRows
                                 write FMinTicksRows
                                 default 0;
{: Number of rows that a connected control has requested to be scrolled when the
PAGE DOWN or PAGE UP commands are received.}
  property PageRows: longint read FPageRows write FPageRows default 0;
{: This property defines the action that should be taken upon Refreshing a
dataset.
<br><br>
raOpen should be used with the TIB_Cursor.  Any of them can be used for the
TIB_Query component.}
  property RefreshAction: TIB_RefreshAction read FRefreshAction
                                            write FRefreshAction
                                            default raOpen;
{: WIP}
  property CommitAction: TIB_CommitAction read FCommitAction
                                          write FCommitAction
                                          default caClose;
{: This property determines which of the OrderingItems entries is driving the
current sort order of the dataset. If this property is changed whilst the dataset
is Active, the dataset will be refreshed using the new ordering selection.
<br><br>
A value of 0 causes the ORDER BY clause as it appears in the SQL property of
the dataset to be used.
<br><br>
If you give a value less than zero, it will select the DESCending criterion
attached to the OrderingItem that is referenced in the table by that absolute
value.
<br><br>
A positive value causes the ascending criterion of the referenced item to be
used.
<br><br>
Please refer to the Contact sample application to see how this is implemented.
<br><br>
<b>More hints about OrderingItems</b>
<br><br>
OrderingItems supports two ORDER BY criteria items per each line item entry.
They should be separated with a semicolon. The first should be an ASCENDING
criterion and the second (which is optional) should be DESCENDING, using the same
columns.
<br>
<br>OrderingItems:
<br>Name=LASTNAME;LASTNAME DESC
<br>ZIP=ZIP;ZIP DESC
<br>
<br>OrderingLinks:
<br>LASTNAME=1
<br>ZIP=2
<br>
<br>For these entries, any OrderingItemNo from -2 to 2 is valid.
<br>
<br>This table shows OrderingItemNo and the corresponding ORDER BY and
<br>OrderingLink:
<br>
<br> 2    ORDER BY ZIP                    ZIP
<br> 1    ORDER BY LASTNAME                LASTNAME
<br> 0    ORDER BY < whatever is in the SELECT statement >    < blank >
<br>-1    ORDER BY LASTNAME DESC                LASTNAME
<br>-2    ORDER BY ZIP DESC                ZIP
<br><br>
The OrderingLink property tells the grid which column to put the arrow on.
The sign of OrderingItemNo indicates which way the arrow points. If there are
both ASC and DESC criteria for the corresponding OrderingLink, the arrow has a
dash above or below it to indicate that the column can be sorted both ways.}
  property OrderingItemNo: integer read FOrderingItemNo
                                   write SetOrderingItemNo
                                   default 0;
{: This property is used to define a list of valid sort criteria for a dataset.
<br><br>
Entries should be made in the following format:
<br><br>
< user readable name of sort >=< ascending ORDER BY criteria>[;< descending
ORDER BY criteria >}
  property OrderingItems: TIB_StringList read FOrderingItems
                                         write SetOrderingItems;
{: This property defines the relationship between columns and entries in the
OrderingItems table.
<br><br>
Entries should be of the following format:
<br><br>
[< tablename >.]< columnname >=< numerical reference in OrderingItems >
<br><br>
The first item in OrderingItems is considered to be 1 and not 0.
<br><br>
If this property is defined correctly, the IB_Grid can display a glyph in the
corresponding column's header so that, when the user clicks on it, the grid is
sorted in the order of the column clicked on.  If both an ascending and a
descending criterion are supplied, each click on the grid column header will
toggle between the two sort orders.
<br><br>
Please refer to the Contact sample application to see how this is implemented.}
  property OrderingLinks: TIB_StringList read FOrderingLinks
                                         write SetOrderingLinks;
{: This property is used in order to make a SELECT statement updatable
and deletable. Even if a dataset is not "live" it can still be inserted into
because in SQL there is no concept of a INSERT INTO CURSOR.
<br><br>
RequestLive causes a FOR UPDATE clause to be included in the SELECT statement.
Be aware that, if you include the FOR UPDATE clause explicitly in your SQL
property, the effect will be to will toggle the setting of this property.
<br><br>
It was set up this way to enable you to include the list of columns with the OF
clause.  For example, the following SQL will restrict updating to COL1 only;
IBO will make all columns not included in the OF list read-only automatically.
<br><br>
SELECT COL1, COL2 FROM TABLE FOR UPDATE OF COL1
}
  property RequestLive: boolean read FRequestLive
                                write SetRequestLive
                                default false;
{: This property is used to provide incremental searching on columns that
have been defined in the OrderingLinks property.  It determines which input
parameter corresponds to the column that is currently the OrderingLink column.
<br><br>
It should be defined in this format:
<br><br>
[< tablename >.]< columnname >=< parametername >
<br><br>
See the MDIApp or the SearchingLinks sample applications for a demonstration of
this property.}
  property SearchingLinks: TIB_StringList read FSearchingLinks
                                          write SetSearchingLinks;
{$IFNDEF HELPSCAN}
{: The purpose of this property is to provide a design-time indication of
how many components are referencing this dataset.}
  property _DataSourceCount: integer read GetDataSourceCount
                                     write flag_junk_value
                                     stored false;
{$ENDIF}
private
  FOnKeySourceStateChanged: TIB_KeyDatasourceEvent;
protected
  procedure IB_KeyStateChanged( Sender: TIB_DataLink;
                                ADataSource: TIB_DataSource );
public
{: 	This method lets the query import default values from the server.}
  procedure ImportServerDefaults;
  property OnKeySourceStateChanged: TIB_KeyDatasourceEvent
    read FOnKeySourceStateChanged
    write FOnKeySourceStateChanged;
end;

{                                                                              }
{ TIB_Cursor                                                                   }
{                                                                              }

{: This component is most suitable for tasks, such as reports or table scans,
where the data do not need to be buffered and bi-directionally scrollable.
<br><br>
For processing datasets this component is the fastest and most efficient. Use
it whenever possible instead of the buffered dataset components if you don't
need backward scrolling.
<br><br>
It is possible to use this component as you use the IB_DSQL component, to execute
DSQL statements.
<br><br>
All native IBO controls except the IB_Grid and IB_CtrlGrid can be used with this
component.}
TIB_Cursor = class(TIB_Dataset)
{$I IBA_Statement.PBL }
{$I IBA_Dataset.PBL   }
public
{: This method is for doing low-level fetching for even more performance.
<br><br>
This method call avoids all notifications so that only the buffer for the
current row is modified. Any data aware controls will not be aware that there
is new data to display.}
  procedure APIFirst;
{: This method is for doing low-level fetching for even more performance.
<br><br>
This method call avoids all notifications so that only the buffer for the
current row is modified. Any data aware controls will not be aware that there
is new data to display.}
  procedure APINext;
{: This method has been slightly specialized to deliver better performance.}
  procedure Next;
published
{: Event that provides notification that a new row has been fetched.}
  property AfterFetchRow;
{: Event that provides notification that the end of the dataset has been
reached.}
  property AfterFetchEof;
  property ParamChar;
  property ParamCheck;
end;

// IBA_BDataset.IMP
// IBA_Dataset.IMP

{                                                                              }
{ IBA_BindingCursor                                                            }
{                                                                              }

{$IFNDEF HELPSCAN}
{: This class is used internally to provide individual row handling.}
TIB_BindingCursor = class(TIB_Cursor)
private
  FNeedPrepare: boolean;
  FHavePrepare: boolean;
  FBDataset: TIB_BDataset;
  FBindBufferOffset: integer;
  FProblemInBindingCursor: boolean;
  procedure GetPrepare;
  procedure GetParamsData;
protected
  procedure SysPreparedChanged; override;
  function SysAllocate: boolean; override;
  function API_Prepare(     Text: PChar;
                        var InVar,
                            OutVar: smallint ): integer; override;
  procedure SysDescribeVARList( ARow: TIB_Row ); override;
  procedure DoCalculateField( ARow: TIB_Row; AField: TIB_Column ); override;
  function GetStatementType: TIB_StatementType; override;
  procedure KillCursor;
public
  procedure FreeServerResources; override;
  function QuickFetch( Node: PIB_Node;
                       NeedCursor,
                       KeepCursor: boolean ): boolean;
  property BDataset: TIB_BDataset read FBDataset;
  property NeedPrepare: boolean read FNeedPrepare;
  property HavePrepare: boolean read FHavePrepare;
end;
{$ENDIF}

// IBA_BindingCursor.IMP

{                                                                              }
{ IBA_LocateCursor                                                             }
{                                                                              }

{: VCL compatibility. }
TIB_LocateOption = ( lopCaseInsensitive, lopPartialKey, lopFindNearest );
{: VCL compatibility. }
TIB_LocateOptions = set of TIB_LocateOption;

{$IFNDEF HELPSCAN}
{: This is an internal cursor that is used to process the Locate() and Lookup()
operations. You should never have any need to use this directly.}
TIB_LocateCursor = class( TIB_Cursor )
private
  FBDataset: TIB_BDataset;
  FLocateLinks: TIB_StringList;
  FLocateKeyFields: string;
  FLocateKeyValues: Variant;
  FLocateOptions: TIB_LocateOptions;
  FLocateNearest: boolean;
  FLocateMustBeOnServer: boolean;
  procedure SetLocateLinks( AValue: TIB_StringList );
  procedure SetLocateKeyFields( const AValue: string );
  procedure SetLocateKeyValues( AValue: Variant );
  procedure SetLocateOptions( AValue: TIB_LocateOptions );
  procedure SetLocateNearest( AValue: boolean );
protected
  procedure SysPrepareSQL; override;
  procedure SysBeforeOpen; override;
  function GetParamName( ALink: string ): string; 
public
  constructor Create( AOwner: TComponent ); override;
  destructor Destroy; override;
  procedure FreeServerResources; override;
  property BDataset: TIB_BDataset read FBDataset;
  property LocateKeyFields: string read FLocateKeyFields
                                   write SetLocateKeyFields;
  property LocateKeyValues: Variant read FLocateKeyValues
                                    write SetLocateKeyValues;
  property LocateLinks: TIB_StringList read FLocateLinks
                                       write SetLocateLinks;
  property LocateNearest: boolean read FLocateNearest
                                  write SetLocateNearest;
  property LocateOptions: TIB_LocateOptions read FLocateOptions
                                            write SetLocateOptions;
  property ParamName[ Alink: string ]: string read GetParamName;
end;
{$ENDIF}

// IBA_LocateCursor.IMP

{                                                                              }
{ IBA_BDataset                                                                 }
{                                                                              }

{: These are flags used to maintain buffer synchronization with the server.}
TIB_BufferSynchroFlags = ( bsBeforeEdit, bsAfterEdit, bsAfterInsert );

{: These are flags used to maintain buffer synchronization with the server.}
TIB_BufferSynchroFlagsSet = set of TIB_BufferSynchroFlags;

{: VCL compatibility.}
TIB_FilterRecordEvent = procedure(     ARow: TIB_Row;
                                   var Accept: Boolean ) of object;

TQueryCanInsertChild = procedure (Sender: TIB_BDataset; var InsertChild: boolean) of object;

{: This class serves as a base to the IB_Query component.
<br><br>
It is constructed in such a way that a fully scrollable/updateable dataset is
obtained. The buffering of data is all on the client.
<br><br>
All properties and events above the TIB_Dataset class are documented here.
<br><br>
Please see IB_Query for more specific information about how this class is
implemented in an application.}
TIB_BDataset = class(TIB_Dataset)
private
  FCurrentCursor: TIB_BindingCursor;
  FBufferCursor: TIB_BindingCursor;
  FLocateCursor: TIB_LocateCursor;
  FFilterCursor: TIB_FilterCursor;
  FSeekCursor: isc_stmt_handle;
  FSeekCursorPrepared: boolean;
  PSeekDA: PXSQLDA;
  FDescCursor: TIB_Dataset;
//  FNullCursor: TIB_Dataset;
  FNodeList: TIB_NodeList;
  FCanInsert: boolean;
  FBufferSynchroFlags: TIB_BufferSynchroFlagsSet;
  FKeyLookupRowData: string;
  FKeyLookupRef: TIB_NodeRef;
  FKeyVARMap: TList;
  FLocateLevel: integer;
  FIncSearchKeyInt: DWORD;
  FIncSearchSeekInt: DWORD;
  FIncSearchLastTick: DWORD;
  FIncSearchKeyString: string;
  FIncSearchString: string;
  FIncSearchLevel: integer;
  FIncSearchMaxLevel: integer;
  FIncSearchCurRow: longint;
  FIncSearchRowNum: longint;
  FIncSearchNearestRowNum: longint;
  FFilterText: string;
  FFilterPlan: string;
  FFetchWholeRows: boolean;
  FKeySQLSelect: string;
  FCursorIsKeyFields: boolean;
  FLastAllocatedKeyVal: integer;
  FFound: boolean;
  FBufferKeyIsValid: integer;
  FTransactionCachedUpdatesFlag: boolean;
  FProcessingUpdates: boolean;
  FSuppressDupRecs: boolean;
  FRefineZone: TIB_RefineZone;
  FOnRowSelectedChanged: TIB_RowSelectedChanged;
  FOnMultiSelect: TIB_DatasetEvent;
  FOnFilterRecord: TIB_FilterRecordEvent;
  FRevertingRecord: boolean;
{ Tree support temporarily held back }
  FIsTree: boolean;
  FTreeTrash: integer;
  FTreeExpandLevel: integer;
{ Tree support temporarily held back -
  FNewAsChild: boolean;
  FTreeID: string;
  FTreeParID: string;
  FNewParID: integer;
  FNewLevel: integer;
  FOnCanInsertChild: TQueryCanInsertChild;
  FFirstTreeRebuild: boolean;
  FTreeRoot: integer;
  function GetBufferLevel: integer;
  function GetLevel: integer;
- Tree support temporarily held back }

  function SysScrollCurrent( NeedCursor,
                             KeepCursor,
                             ClearBlobs: boolean ): boolean;
  function SyncBufferFields: boolean;
  procedure SysProcessUpdates( AProcess: TIB_CachedUpdatesProcess );
  procedure CheckTransactionFlagForCachedUpdates;
  procedure GetRecordIsFiltered(     Sender: TObject;
                                     ANode: PIB_Node;
                                 var ABoolean: boolean );
  procedure FreeExtraCursors;
  procedure DescCursorAfterFetchEof(IB_Dataset: TIB_Dataset);
  procedure DescCursorAfterFetchRow(IB_Dataset: TIB_Dataset);
  procedure DescCursorBeforeUnprepare(Sender: TIB_Statement);
{!!!
  procedure NullCursorAfterFetchEof(IB_Dataset: TIB_Dataset);
  procedure NullCursorAfterFetchRow(IB_Dataset: TIB_Dataset);
  procedure NullCursorBeforeUnprepare(Sender: TIB_Statement);
!!!}
  function AfterFetchRow( IB_Dataset: TIB_Dataset ): boolean;
  procedure NodeListBeginBusy( Sender: TObject );
  procedure NodeListEndBusy( Sender: TObject );
  function NeedRecords( Sender: TObject;
                        AAscending: boolean;
                        ANumOfRecs: longint ): longint;
  function GetCalculatingFields: boolean; override;
  function GetRefineSQLWhere( Ascending, CheckNull: boolean ): string;
protected
  FIBODataset: pointer;
  property CurrentCursor: TIB_BindingCursor read FCurrentCursor;
  property BufferCursor: TIB_BindingCursor  read FBufferCursor;
  property LocateCursor: TIB_LocateCursor   read FLocateCursor;
  property FilterCursor: TIB_FilterCursor   read FFilterCursor;
  function CanUseDualCursors: boolean;
  procedure GetRestoreInsertedRecord(     Sender: TObject;
                                          ANode: PIB_Node;
                                      var ABoolean: boolean ); virtual;
  procedure UpdateOperation(     Operation: TIB_DataOperation;
                                 UpdateKind: TIB_UpdateKind;
                             var UpdateAction: TIB_UpdateAction );
  procedure UpdateError(     E: Exception;
                             UpdateKind: TIB_UpdateKind;
                         var UpdateAction: TIB_UpdateAction ); virtual;
  function DoCompareText( IB_Field: TIB_Column; const S1,S2: string ): integer;
  function DoCompareStr( IB_Field: TIB_Column; const S1,S2: string ): integer;
  procedure DoUpdateRecord(     UpdateKind: TIB_UpdateKind;
                            var UpdateAction: TIB_UpdateAction ); virtual;
  procedure ProcessTransactionEvent( ATransactionLink: TIB_TransactionLink;
                                     AEvent: TIB_TransactionEventType ); override;
{ Inherited Property Access Methods }
  procedure SetConnection( AValue: TIB_Connection ); override;
  procedure SetTransaction( AValue: TIB_Transaction ); override;
  function GetCanEdit: boolean; override;
  function GetCanInsert: boolean; override;
  function GetFields: TIB_Row; override;
  function GetKeyFields: TIB_Row; override;
  function GetBofRowNum: longint; override;
  function GetEofRowNum: longint; override;
  function GetRowNum: longint; override;
  procedure SetRowNum( AValue: longint ); override;
  function GetUnidirectional: boolean; override;
  procedure SetUnidirectional( AValue: boolean ); virtual;
  function GetBof: boolean; override;
  function GetEof: boolean; override;
  function SysRecordCount: longint; override;
  function SysGetCursorRecordCount: longint; override;
  procedure SysLayoutChange( Sender: TObject ); override;
  function GetStatementType: TIB_StatementType; override;
  function GetBookmark: string; override;
  procedure SetBookmark( const ABookMark: string ); override;
  function GetBufferBookmark: string; override;
  procedure SetBufferBookmark( const ABookmark: string ); override;
  procedure SysAfterFieldDataChange( Sender: TIB_Row;
                                     AField: TIB_Column); override;
  procedure SetDatabaseName( const AValue: string ); override;
  function GetUpdatesPending: boolean; override;
  function GetUpdateStatus: TIB_UpdateStatus; override;
{ SQL Generator procedures }
  function IsUsingManualDML( UpdateKind: TIB_UpdateKind ): boolean; override;
  procedure SQL_DeleteRow; override;
  procedure SQL_EditRow; override;
  function SQL_LockRow: boolean; override;
{ Property Access Methods }
  function GetBufferFields: TIB_Row; override;
  function GetBufferRowNum: longint; override;
  procedure SetBufferRowNum( AValue: longint ); override;
  function GetBufferEof: boolean; override;
  function GetBufferBof: boolean; override;
  function GetBufferRowCount: longint; override;
  function GetBufferHasBof: boolean; override;
  function GetBufferHasEof: boolean; override;
  function GetBufferRowFlags: TIB_RowFlagSet;
  function GetRowFlags: TIB_RowFlagSet;
  function GetSysFieldNames: TIB_StringList; override;
{ System Methods }
  procedure SysBeforeUnprepare; override;
  procedure SysAfterUnprepare; override;
  function SysNeedToRefineSQL: boolean; override;
  procedure SysRefineSQL; override;
  procedure SysPrepareSQL; override;
  procedure SysUpdateDescriptors; override;
  procedure SysPreparedChanged; override;
  function SysAfterFetchCursorRow: boolean; override;
  procedure SysAfterFetchCursorEof; override;
  procedure SysBeforeExecuteForOutput; override;
  procedure SysBeforeScroll; override;
  procedure SysAfterScroll; override;
  procedure SysBeforeSearch; override;
  procedure SysAfterExecuteForOutput; override;
  procedure SysJustBeforeOpen; override;
  procedure SysBeforeOpen; override;
  procedure SysAfterOpen; override;
  procedure SysAfterClose; override;
  procedure SysAfterCancel; override;
  procedure SysFirst; override;
  function SysMoveBy( IncRows: longint ): longint; override;
  procedure SysLast; override;
  procedure SysPostEditedRow; override;
  procedure SysPostInsertedRow; override;
  procedure SysLockRow; override;
  procedure SysEditCursorRow; override;
  procedure SysInsertCursorRow; override;
  procedure SysDeleteCursorRow; override;
  procedure SysInsertRow; override;
  procedure SysAfterEdit; override;
  procedure SysAfterInsert; override;
  procedure SysAfterDelete; override;
  procedure SysCancelInsertedRow; override;
  procedure SysAdjustCurrentRow( ADeleteNode, AScrollEvents: boolean );
  procedure SysAfterPost; override;
  procedure SysPost( CancelUnmodified, IsRetaining: boolean ); override;
  procedure SysExecPost( CancelUnmodified: boolean ); override;
  procedure SysUpdateKeyData( PreserveOldKeyData: boolean ); override;
  procedure DoKeyDataChange( AField: TIB_Column ); override;
{ New Property Methods }
  function GetAsXML: string; //~virtual;
  function GetCachedEditCount: longint;
  function GetCachedInsertCount: longint;
  function GetCachedDeleteCount: longint;
  function GetFilterDeletedRecords: boolean;
  procedure SetFilterDeletedRecords( AValue: boolean );
  function GetFiltered: boolean;
  procedure SetFiltered( AValue: boolean );
  procedure SetFilterText( const AValue: string ); //~virtual;
  function GetFilterOptions: TIB_FilterOptions;
  procedure SetFilterOptions( AValue: TIB_FilterOptions ); //~virtual;
  function GetIsEmpty: boolean;
  procedure SetSelected( ARowNum: longint; AValue: boolean ); //~virtual;
  function GetSelected( ARowNum: longint ): boolean; //~virtual;
  procedure SetBookmarkSelected( const ABookmark: string;
                                       AValue: boolean ); //~virtual;
  function GetBookmarkSelected( const ABookmark: string ): boolean; //~virtual;
  function GetOrderingRefinePos: smallint; //~virtual;
  procedure SetOrderingRefinePos( AValue: smallint ); //~virtual;
  function GetOrderingRefineStr: string; //~virtual;
  procedure SetOrderingRefineStr( {const} AValue: string ); //~virtual;
  function mkOrderingRefineStr( const AValue: string;
                                      Desc: boolean ): string; //~virtual;
  procedure SetFetchWholeRows( AValue: boolean ); //~virtual;
  procedure SetCachedUpdates( AValue: boolean ); //~virtual;
  procedure SetOnFilterRecord( AValue: TIB_FilterRecordEvent ); //~virtual;
  procedure CheckCursorFields;
  function CheckRowNum( var ARowNum: longint ): boolean;
  function GetFound: boolean; //~virtual;
  procedure SetFound( AValue: boolean ); //~virtual;
{ New system methods }
  procedure SysBeforeSearchedDML; //~virtual;
  procedure SysDeallocateSeekCursors; //~virtual;
  function SysGetCurrentCursor( CheckSynchro: boolean ): boolean; //~virtual;
  function SysLookupKeyForBufferFields( ANodeRef: TIB_NodeRef ): boolean;
  function SysIncSearch( AString: string;
                         ACol: string;
                         SeekNearest: boolean;
                         PartialMatch: boolean ): boolean;
  function LocateRecord( const AKeyFields: string;
                         const AKeyValues: Variant;
                               AOptions: TIB_LocateOptions;
                               SyncCursor: boolean;
                               ALocateNearest: boolean ): boolean;
  function SysLocateRecord( const AKeyFields: string;
                            const AKeyValues: Variant;
                            const FAGen: integer;
                                  AOptions: TIB_LocateOptions;
                                  SyncCursor: boolean;
                                  StartPos,
                                  EndPos: longint ): longint;
  procedure SysUpdateOperation(     Operation: TIB_DataOperation;
                                    UpdateKind: TIB_UpdateKind;
                                var UpdateAction: TIB_UpdateAction ); virtual;
  procedure NodeApplyUpdate(     Sender: TObject;
                                 ANode: PIB_Node;
                                 UpdateKind: TIB_UpdateKind;
                             var UpdateAction: TIB_UpdateAction );
  procedure NodeCancelUpdate( Sender: TObject; ANode: PIB_Node );
  function GotoBOF: boolean;
  function GotoEOF: boolean;
  procedure BufferGotoNULL; //~virtual;
  function SeekKeyForBufferFields( var ANodeRef: TIB_NodeRef ): boolean;
  procedure SetIsTree( const Value: boolean );
  procedure SynchronizeCachedUpdates( ASyncType: TIB_SynchronizeCachedUpdates;
                                      AField: TIB_Column );
public
{Inherited Methods}
  constructor Create( AOwner: TComponent ); override;
  destructor Destroy; override;
  procedure FreeServerResources; override;
{: Begin a bracketed operation on the FieldsXXXX properties for efficiency.}
  procedure BeginLayout; override;
{: End a bracketed operation on the FieldsXXXX properties for efficiency.}
  procedure EndLayout; override;

{Methods}

{: Call this method to invoke the API call that will cancel the running
query on the server.
<br>
<br>
NOTE: This is for InterBase version 7 and up only.}
  procedure CancelQuery; override;
{: Calculate the fields of the row indicated.}
  function CheckRecordFilter( ARow: TIB_Row ): boolean; 
{: This call will clear out the current incremental search criteria
used in conjunction with the key-by-key behavior when calling the
IncSearchKey() method.}
  procedure ClearIncSearchString;
{: Work in progress.}
  procedure ApplyUpdates; dynamic;
{: Work in progress.}
  procedure CancelUpdates; dynamic;
{: Work in progress.}
  procedure CommitUpdates; dynamic;
{: Change the record back to the state it was in when the edit or insert mode
was started..}
  procedure RevertRecord;
{: Manually initiate the recalculation of calculated fields for the current
row.}
  procedure CalculateFields; override;
{:}
  procedure DefaultDMLCacheReceivedItem( const ADMLCacheItem: TIB_DMLCacheItem);
                                                                       override;
{: When True, this setting causes the query to be processed like the Delphi
TQuery, to fetch whole rows instead of splitting the buffer to get the keys and
then the individual rows as required.
<br><br>
Set FetchWholeRows to False if you have to deal with a very large dataset (both
rows and cols) and the user wants to scroll around it at random and do refreshes,
etc.  Because scrolling at random means calling Locate(), doing IncSearches,
etc., a small key, such as a single-column integer, is desirable to make this
perform effectively.
<br><br>
A query that is a child in a KeyLinks relationship is also a candidate for
split buffering if it has a large number of rows.  This way, when the parent asks
it to seek, it will only have to fetch keys to get to the desired record if it
is not already in the buffer.
<br><br>
The down-side to this setting is that, with three cursors instead of one, the
rows are not able to be batched up into a single network packet.  Instead, they
come one at a time which could possibly end up being less efficient if,
eventually, the whole dataset has to be fetched.
<br><br>
Normally, it is better to leave FetchWholeRows set to True so that IB can batch
rows together in a packet for more efficient transmission across the wire.
Arrange for the user to make use of the search mode, rather than scroll around.
Entering search criteria is more accurate for locating target rows and offloads
work to the server more efficiently.
<br><br>
The rule of thumb is this. If you expect to fetch more than one-third of the
dataset, keep FetchWholeRows at the default setting of True.  Only set it to
False if you have a small key and a wide row and will only be peeking at small
slices of the dataset.
<br><br>
FetchWholeRows must be True when selecting from a stored procedure and will be
ignored if set to False.}
  property FetchWholeRows: boolean read FFetchWholeRows
                                   write SetFetchWholeRows
                                   default true;
(* Tree support held back
  procedure RebuildNodeList;  //rebuilds the node list to be ordered as the tree requires
  procedure InsertChild;   //insert new record as a child of the current record
  procedure ExpandAll(AExpandLevel: integer {$ifdef IBO_VCL40_OR_GREATER}
   = 99999 {$ENDIF} ); //tree could be expanded up to the desired level, for example. we want to expand only first 2 levels or so
  function IsParentOf(Id: integer; ANode: PIB_Node): boolean;
  property Level: integer read GetLevel;
  property BufferLevel: integer read GetBufferLevel;
*)

{: This property indicates whether an incremental search is in progress.}
  property IncSearchLevel: integer read FIncSearchLevel;
{: Method to tell the dataset to position the record pointer away from any valid
record.}
  function GotoNULL: boolean;
{: Used for processing key-by-key incremental searching.
<br><br>
Returns True if the criteria match up exactly with a row, False if not.}
  function IncSearchKey( AKey: char;
                         KeyByKey: boolean;
                         AllowTimeout: boolean;
                         SeekNearest: boolean ): boolean; //~virtual;
{: Used to process a whole string to be searched for within the current data.}
  function IncSearchString( AString: string;
                            StartRowNum: longint;
                            SeekNearest: boolean ): boolean; //~virtual;
{: This keeps track of the timings during incremental searching so that the
proper timeouts will be calculated for automatically clearing the incremental
searching criteria.}
  property IncSearchLastTick: DWORD read FIncSearchLastTick;


{: For a dataset having calculated fields declared, this method will cause the
entire dataset to be recalculated.
<br><br>
This is useful when a condition changes that requires a recalculation of all
calculated fields for the whole dataset.}
  procedure InvalidateCalculatedFields; //~virtual;
{: This method will remove all of the individual record buffers for the Dataset
and leave the key buffer intact.  The fetch-on-demand logic of this Dataset will
cause any record buffers to be fetched from the server again as required, when
they are subsequently referred to.}
  procedure InvalidateRows; override;
{: This method will invalidate a single row from the Dataset, causing it to be
re-fetched from the server next time it is referred to.  This provides a very
efficient method of reflecting back to the client any changes made in a
server-side DML operation.}
  procedure InvalidateRowNum( ARowNum: longint ); override;
{: This method will invalidate a single row from the Dataset, causing it to be
re-fetched from the server next time it is referred to.  This provides a very
efficient method of reflecting back to the client any changes made in a
server-side DML operation.}
  function InvalidateBookmark( const ABookmark: string ): boolean; override;
{: By default, the internal cursors are NOT invalidated when the main SQL is
invalidated. Call this method in conjunction with InvalidateSQL so that
the internal cursors will be reprepared the next time the Dataset is prepared.}
  procedure InvalidateSQLWithCursors;
{: Property which provides the most efficient way to tell whether the dataset is
empty.<br><br>
Don't try to use a SELECT COUNT(*) query or to read RecordCount to ascertain
this condition!!}
  property IsEmpty: boolean read GetIsEmpty;
{: This property determines whether the KeyLinks should be defined automatically
if none were provided by the developer.  A setting of True is not applicable if
the dataset is providing data, for display purposes, to a parent dataset through
KeySource and KeyLinks linkages.}
  property KeyLinksAutoDefine default true;
{: Same as in TDataset }
  function Lookup( const AKeyFields: string;
                   const AKeyValues: Variant;
                   const AResultFields: string ): Variant;
{: Same as in TDataset }
  function Locate( const AKeyFields: string;
                   const AKeyValues: Variant;
                         AOptions: TIB_LocateOptions ): boolean;
{: Method to toggle the dataset's rows Selected state.}
  procedure ToggleSelected;
{: Method to force the Selected state of all rows in the dataset to True or
False.
<br><br>
IBO applies this method to all rows, whether they have so far been fetched into
the dataset yet or not.}
  procedure SelectAll( State: boolean );
{: This method will populate a TStrings list with bookmarks of the selected
records.}
  procedure SelectedBookmarks( AStrings: TStrings );
{: Gives control over a particular range of rows and their Selected status.}
  procedure SelectRange( StartRow, EndRow: longint;
                         State, Exclusive: boolean );

{: System reference to the in-memory portion of the buffered dataset.}
  property NodeList: TIB_NodeList read FNodeList;
{: This method is used to verify whether all of the rows in the range specified
are valid. If they are not, IBO will attempt to make them valid.}
  function ValidateRows( Start, Finish: longint ): boolean; override;
{: This property tells if a record is in the process of being reverted.}
  property RevertingRecord: boolean read FRevertingRecord;

{ New Methods }

{: This method will remove a record from the buffer in memory only.}
  procedure DeleteBufferBookmark( ABookmark: string );
{: This method will remove a row from the buffer in memory but not generate any
SQL to delete it from the server. This provides a way to delete rows from memory
that have been deleted from the server in another component. If the row was not
deleted from the server, it will be returned into the dataset next time Refresh
is called. }
  procedure DeleteBufferRowNum( ARowNum: longint );
{: W.I.P.
<br><br>
This method will manually add a row to the key buffer of the dataset, at the
position of the BufferCursor in the NodeList underlying the dataset.
<br><br>
It is a direct hack into the in-memory table that represents the buffered
dataset so use it with great care!
<br><br>
After Refresh is called, the added row will be removed if not on the server.}
  procedure InsertBufferBookmark( ABookmark: string );
{: This method is used to locate a record that currently exists in the
buffer or if it would when all of the records for the Dataset were fetched
from the server. If the record is not in the current buffer then a SELECT
statement is put together according to the current valid Dataset and the
search is performed on the server for this record. If it is not valid then
false will be returned and if it is valid then that individual record is
immediatly fetched and put into the buffer row. BufferRowNum will be -1 in
this case. The contents of this row fetched are also put into the temporary
buffer so that if its key is ever fetched from the server it will be
associated properly. Then it would not have to be fetched again if accessed.
<br><br>
First, place the key values in KeyFields and then call this method. If the
record is in the current buffer then it will be moved to it and true will be
returned. Otherwise, false will be returned and the cursor position will
become invalid. The means that BufferRowNum will reflect the row if it was
found.}
  function LookupKeyForBufferFields: boolean;
{: This method is used to locate a record that currently exists in the
Dataset or if it would when all of the records for the Dataset were fetched
from the server. If the record is not in the current buffer then a SELECT
statement is put together according to the current valid Dataset and the
search is performed on the server for this record. If it is not valid then
false will be returned and if it is valid then keys are selected from the
server until the matching one is located sequentially. This happens very
quickly when your key is very narrow. Plus you avoid having to fetch the bulk
of the records like the BDE does in order to perform the search.
<br><br>
If false is returned then the RowNum was not changed from its current position.
<br><br>
If true is returned then the RowNum will be the newly selected row.
<br><br>
First, place the key values in KeyFields and then call this method. If the
record is in the current buffer then it will be moved to it and true will be
returned. Otherwise, false will be returned and the cursor position will
become invalid. The means that BufferRowNum will reflect the row if it was
found.}
  function LookupKeyForFields: boolean;
{: Since it is possible to interrupt operations when callbacks are enabled it
is potentially useful to know if there is a lookup being interrupted. This
property gives an indication if a lookup is underway or not.}
  property KeyLookupCurLevel: integer read FKeyLookupCurLevel;
{: This method causes the dataset to walk all the records of the primary and
secondary buffers in order to recalculate their Filtered flag.
<br><br>
The OnFilterRecord event will be triggered for each row as a result of this
call.}
  procedure RefreshFilteredRows;
{: This is used in debugging the internal dynamic memory structures that
compose the buffering of the data.}
  procedure TraceBufferNodes(       Ascending,
                                    AllowDeleted,
                                    AllowFiltered: boolean;
                              const AStrings: TStrings );

{ Properties }

{: This returns the XML equivalent for the entire table.
<br><br>
It is not recommended to use this for large tables since they will all be
buffered in memory. Use the TIB_Cursor and write a simple loop to output
records and use the AsXML property of the TIB_Column.}
  property AsXML: string read GetAsXML;
{: The number of rows in the buffer that have been edited via CachedUpdates.}
  property CachedEditCount: longint read GetCachedEditCount;
{: The number of rows in the buffer that have been inserted via CachedUpdates.}
  property CachedInsertCount: longint read GetCachedInsertCount;
{: The number of rows in the buffer that have been deleted via
CachedUpdates.}
  property CachedDeleteCount: longint read GetCachedDeleteCount;
{: VCL compatibility. It is now ready for use!}
  property CachedUpdates: boolean read FCachedUpdates
                                  write SetCachedUpdates
                                  default false;
{: This property determines whether records deleted via cached updates will
remain visible in the buffer or will be filtered out.}
  property FilterDeletedRecords: boolean read GetFilterDeletedRecords
                                         write SetFilterDeletedRecords
                                         default true;
{: VCL compatibility.}
  property Filtered: boolean read GetFiltered write SetFiltered default false;
{: VCL compatibility.}
  property Filter: string read FFilterText write SetFilterText;
{: VCL compatibility.}
  property FilterOptions: TIB_FilterOptions read GetFilterOptions
                                            write SetFilterOptions
                                            default [];
{: Provides the ability to specify a particular PLAN when a dataset is prepared
with Filtered=True. <br>Notes<br>
1. While Filtered=True, any PLAN specified here will REPLACE any PLAN that was
previously specified.
<br>
2. To have any effect, the FilterPlan property must be set before Filtered is
set to True.}
  property FilterPlan: string read FFilterPlan write FFilterPlan;
{: VCL compatibility.}
  function FindFirst: boolean;
{: VCL compatibility.}
  function FindLast: boolean;
{: VCL compatibility.}
  function FindNext: boolean;
{: VCL compatibility.}
  function FindPrior: boolean;
{: VCL compatibility.}
  function FindRecord( Restart, GoForward: boolean ): boolean; //~virtual;
{: VCL compatibility.}
  property Found: boolean read GetFound;
{: The period of time a matching record will be sought, in milliseconds.
<br><br>
If searching is going on after this period expires it will stop at the row it
has reached and sound a MessageBeep() to indicate that the search has terminated.
<br><br>
This property has no effect if a process is hung on the server because of a slow
query or a failure of some sort.  It can only operate during a Callback.}
  property IncSearchSeekInt: DWORD read FIncSearchSeekInt
                                   write FIncSearchSeekInt
                                   default 180000;
{: The time in milliseconds that will elapse before a new search string is
started when the user is entering in incremental key presses.
<br><br>
This takes effect when the IncSearchKey() method is called.}
  property IncSearchKeyInt: DWORD read FIncSearchKeyInt
                                  write FIncSearchKeyInt
                                  default 3500;
{: The character position (count of user-entered characters) at which the
incremental search is to begin.
<br><br>
For tables of over 1,000 records a value of 1 may improve responsiveness.
<br><br>
For tables of 5,000 - 10,000 a value of 2 and for 50,000 above 4 or 5 may be
appropriate.
<br><br>
It will depend most upon how your server is set up and the type of connection
you are working with. If you are using a modem then a higher value may be best.
<br><br>
If set to 0 then no refinement will happen and all records will be brought to
the client.}
  property OrderingRefinePos: smallint read GetOrderingRefinePos
                                       write SetOrderingRefinePos;
{: This property stores the buffer that is used by the IncSearchString()
method to perform incremental searching.
<br><br>
It directly correlates with the refinement criteria used to make incremental
searching much more efficient with large Datasets.}
  property OrderingRefineStr: string read GetOrderingRefineStr
                                     write SetOrderingRefineStr;
{: This is a hack that will suppress duplicate records which are returned from
a query that is, for example, a self-join in a full text search using partial
word matching criteria.
<br><br>
In order for it to work, set the KeyLinks to the distinct column(s) so that it
will ignore any records fetched with that same bookmark.}
  property SuppressDuplicates: boolean read FSuppressDupRecs
                                       write FSuppressDupRecs;
{: Access to the search string generated by the IncSearchKey() method.}
  property IncSearchKeyString: string read FIncSearchKeyString;
{: This is the SELECT list that is used to fetch the keys.}
  property KeySQLSelect: string read FKeySQLSelect;
{: Used internally to indicate whether the CursorFields is also serving as the
KeyFields.}
  property CursorIsKeyFields: boolean read FCursorIsKeyFields;
{: This gives a raw pointer to the parent TDataset object that this instance is
serving as an internal cursor to.
<br>
<br>
This is useful in the case where you need to scan through all datasets for a
connection and you would like to do so for all TDataset based datasets.
<br>
<br>
To use this property, first check that it is not nil and, if is assigned, cast
it as a TIBODataset() reference and operate on it.
<br>
<br>
It is an untyped pointer, to avoid forcing the DB.PAS unit to be included in the
native IBO suite of components.}
  property IBODataset: pointer read FIBODataset;
{: Redeclaration of property to better suit a buffered dataset.}
  property PreparedEdits default true;
{: The addition to the Dataset of scrolling and buffering capability at this
level is going to change the default behavior for this property.}
  property RefreshAction default raKeepRowNum;
  property SearchedEdits default true;
  property SearchedDeletes default true;
{: Access multi-select information for the Dataset based on the RowNum.
<br><br>
Additional methods are planned, to provide easier ways to work with this feature,
for example, SelectAll, DeselectAll, ToggleAll, DeleteSelected, etc.}
  property Selected[ ARowNum: longint ]: boolean read GetSelected
                                                 write SetSelected;
{: Use a Bookmark to access multi-select information for the Dataset.
<br><br>
It will retrieve a value for rows in the storage buffer as well.
<br><br>
These are valid rows previously cached but not currently included in the active
Dataset.}
  property BookmarkSelected[ const ABookmark: string ]: boolean
      read GetBookmarkSelected
     write SetBookmarkSelected;
{: Direct access to the row flags of the buffer row.}
  property BufferRowFlags: TIB_RowFlagSet read GetBufferRowFlags;
{: Direct access to the row flags of the current row.}
  property RowFlags: TIB_RowFlagSet read GetRowFlags;
{: This property helps maintain correct synchronization between the client
buffer and the server.}
  property BufferSynchroFlags: TIB_BufferSynchroFlagsSet
      read FBufferSynchroFlags
     write FBufferSynchroFlags
   default [];
{: This property is redeclared in order to change its default behavior for
buffered Datasets.
<br><br>
If it is necessary to change multiple parameters and you do not want it to
refresh the Dataset for each one, use the BeginUpdate and EndUpdate() methods
of the Params property in order to have all of the changes to it handled in a
single event notification.}
  property RefreshOnParamChange default true;
{: This is a flag to determine whether cached updates are in the process of
being applied to the server.}
  property ProcessingUpdates: boolean read FProcessingUpdates;
{: Implement a unidirectional cursor for a class is that is actually buffered.
<br><br>
If you want a unidirectional cursor it is better, if possible, to use the
TIB_Cursor component and thus avoid the overhead of the buffering layer.}
  property Unidirectional: boolean read GetUnidirectional
                                   write SetUnidirectional
                                   default false;
{: This property tells which refinement zone is currently in use in the internal
buffers for this dataset. Unless you have set up the OrderingLinks property to
use horizontal refinement for navigating large datasets, this will always
return the default rzTop.}
  property RefineZone: TIB_RefineZone read FRefineZone;
{: This event allows each individual record to be flagged as to whether it is
currently within or outside the filtered set. It integrates fully with all of
the other filtering and navigation mechanisms such as Filtered, FindFirst,
FindNext, FindPrior, FindLast, Locate(), and so on.
<br><br>
New features in version 4 mean it is no longer necessary to refresh the rows
from the server in order to re-evaluate the row's Filtered flag. The flag is
now applied to all of the rows which remain in the buffers.  If the filtering
criteria change, it is now sufficient to call only the RefreshFilteredRows
method. It does not involve the server at all - it just recalculates all of
the filtered flags of the rows in the buffers.
<br><br>
If this event is assigned it will be called for each record whether or not
the Filtered property is true or false. In the future I may make it so that it
won't call this event until Filtered is set to true.}
  property OnFilterRecord: TIB_FilterRecordEvent read FOnFilterRecord
                                                 write SetOnFilterRecord;
{: This event provides notification when a multi-row selection is performed.}
  property OnMultiSelect: TIB_DatasetEvent
     read FOnMultiSelect
    write FOnMultiSelect;
{: This event provides notification of when a row's Selected status is
changed.}
  property OnRowSelectedChanged: TIB_RowSelectedChanged
     read FOnRowSelectedChanged
    write FOnRowSelectedChanged;
{: VCL Compatibility.}
  property OnUpdateError: TIB_UpdateErrorEvent read FOnUpdateError
                                               write FOnUpdateError;
{: VCL Compatibility.}
  property OnUpdateRecord: TIB_UpdateRecordEvent read FOnUpdateRecord
                                                 write FOnUpdateRecord;
{ Tree support held back }
  property IsTree: boolean read FIsTree write SetIsTree stored false; //default false;
{: Value of the ID, which is interpreted as a trash.}
  property TreeTrash: integer read FTreeTrash write FTreeTrash stored false; // default -1;
{: Level to which the tree is automatically expanded after opening }
  property TreeExpandLevel: integer read FTreeExpandLevel write FTreeExpandLevel stored false; //default -1;
{ Tree support held back
  property TreeID: string read FTreeID write FTreeID;       //name of the column, which holds the the primary key
  property TreeParID: string read FTreeParID write FTreeParID; //name of the column, which holds the the parent id pointing to the parent node id
  property TreeRoot: integer read FTreeRoot write FTreeRoot default 0; //value of the ID, which is interpreted as a root (0 or NULL default)
  Tree support held back }

end; { TIB_BDataset }

{                                                                              }
{ TIB_Query                                                                    }
{                                                                              }

{: It is possible to process ANY InterBase DSQL statement using this component.
<br><br>
It is primarily a buffered scrollable Dataset that is ideal for
browsing and providing a dataset for random scrolling.
<br><br>
This component also has many built-in features to do operations such as
incremental searching, filtering, finding, etc.
<br><br>
The optional KeyLinks property is normally defined by the developer, to identify
each row of the output uniquely.  If KeyLinks is undefined, an arbitrary, unique
numerical value will be assigned to each row of output.
<br><br>
The KeyLinks entries are usually the columns from a primary or unique key;  or,
where no suitable unique key is available, the DB_KEY record pointer can be used.
Note, however, that using the DB_KEY renders the dataset incapable of inserting
any new rows.
<br><br>
IMPORTANT: It is important to know that now every type of SQL statement can
be used in this component. But, pay very close attention to what is going on
in the IB_Monitor to see if it will give the dataset that you are expecting.
<br><br>
This biggest gotcha occurs if you place join criteria in the WHERE clause, i.e.
when you use a statement containing implicit joins or, worse, mixing up the
implicit and explicit join syntaxes.  IBO does not like this in the buffered
query and problems could result.
<br><br>
You will NOT get the expected Dataset with FetchWholeRows False, because the
IB_Query component's Dataset is defined by a special cursor deduced from the
KeyLinks property. This special cursor for this statement would look like this:
<br><br>
SELECT TABLE.COL FROM TABLE
<br><br>
and then the scrolling cursors will look like this:
<br><br>
SELECT DISTINCT( COL ) FROM TABLE WHERE TABLE.COL=?BIND_COL
<br><br>
The scrolling cursors are designed to fetch a single row for each key that is
fetched by the special cursor. The purpose is to optimize the buffering so that
the whole rows are fetched only when needed.
<br><br>
If a user wants to see the last row of the Dataset, the internal cursor fetches
just the key columns.  This is very quick, especially when the key is only four
bytes wide.  When it gets that last key, it puts it into the input parameter
of the scrolling cursor and fetches whole row buffer.  By this means, a series of
very narrow fetches is performed instead of a whole succession of wide fetches.
<br><br>
SELECT statements that include JOINs can be used with this component as long as
the JoinLinks (if applicable) and KeyLinks are properly defined.  Always keep in
mind that it is essential for each row of the output to be uniquely identified
by the KeyLinks and that the JoinLinks have to be applied to both the special
cursor for keys and the scrolling cursors used to fetch the whole individual
record buffers.  JoinLinks therefore plays an important part in ensuring that
only individual rows are fetched by the scrolling cursors when you have implicit
JOIN syntax.
<br><br>
For more information on its properties, methods and events see the TIB_Statement,
TIB_Dataset, and TIB_BDataset classes.}
TIB_Query = class(TIB_BDataset)
{$I IBA_Statement.PBL }
{$I IBA_Dataset.PBL }
{$I IBA_BDataset.PBL }
end;

// IBA_Dataset.IMP
// IBA_BDataset.IMP

{                                                                              }
{ IBA_FilterCursor                                                             }
{                                                                              }

{$IFNDEF HELPSCAN}
TIB_FilterCursor = class(TIB_BDataset)
private
  FBDataset: TIB_BDataset;
protected
  procedure SysPrepareSQL; override;
  procedure SysBeforeOpen; override;
public
  constructor Create( AOwner: TComponent ); override;
  property BDataset: TIB_BDataset read FBDataset;
end;
{$ENDIF}

// IBA_FilterCursor.IMP

{                                                                              }
{ IBA_UpdateSQL                                                                }
{                                                                              }

{: This component is very much like the TUpdateSQL component in the standard
data aware components except that it is owned and contained by the dataset
instead of being a separate reference.
<br><br>
The properties for this component are actually a part of the properties of the
TIB_Dataset class and are published for the TIB_Query component. Please refer
to the help for this component to see the descriptions.}
TIB_UpdateSQL = class(TComponent)
private
  FDataset: TIB_Dataset;
  FPreparedEdits: boolean;
  FPreparedInserts: boolean;
  FSearchedDeletes: boolean;
  FSearchedEdits: boolean;
  FDeleteSQL: TIB_StringList;
  FModifySQL: TIB_StringList;
  FLockSQL: TIB_StringList;
  FInsertSQL: TIB_StringList;
  FEditDSQL: TIB_Statement;
  FLockDSQL: TIB_Statement;
  FInsertDSQL: TIB_Statement;
  FDeleteDSQL: TIB_Statement;
  FNeedRecordResync: boolean;
  procedure AssignKeyRefs( ADSQL: TIB_Statement );
  procedure CheckKeyRelation;
  function CheckDSQLNeedsInit( var ADSQL: TIB_Statement ): boolean;
  function GetEditDSQL: TIB_Statement;
  function GetLockDSQL: TIB_Statement;
  function GetInsertDSQL: TIB_Statement;
  function GetDeleteDSQL: TIB_Statement;
  procedure PrepareCustomDML( ADSQL: TIB_Statement; const ASQL: string );
  procedure SetPreparedEdits( AValue: boolean );
  procedure SetPreparedInserts( AValue: boolean );
  procedure SetSearchedDeletes( AValue: boolean );
  procedure SetSearchedEdits( AValue: boolean );
  procedure SetDeleteSQL( AValue: TIB_StringList );
  procedure SetModifySQL( AValue: TIB_StringList );
  procedure SetLockSQL( AValue: TIB_StringList );
  procedure SetInsertSQL( AValue: TIB_StringList );
  procedure SQLChanged( Sender: TObject );
protected
  procedure HandleOutputValues( ADSQL: TIB_Statement );
  procedure DoHandleError(       Sender: TObject;
                           const errcode: isc_long;
                                 ErrorMessage,
                                 ErrorCodes: TStringList;
                           const SQLCODE: isc_long;
                                 SQLMessage,
                                 SQL: TStringList;
                             var RaiseException: boolean ); 
  procedure SysDeallocate;
  procedure SysUnprepare;
{ SQL Generator procedures }
  procedure SQL_Edit;
  function SQL_Lock: boolean;
  procedure SQL_Insert;
  procedure SQL_Delete;
{ Properties }
  property Dataset: TIB_Dataset read FDataset;
  property PreparedEdits: boolean read FPreparedEdits
                                  write SetPreparedEdits;
  property PreparedInserts: boolean read FPreparedInserts
                                    write SetPreparedInserts;
  property SearchedDeletes: boolean read FSearchedDeletes
                                    write SetSearchedDeletes;
  property SearchedEdits: boolean read FSearchedEdits
                                  write SetSearchedEdits;
  property DeleteSQL: TIB_StringList read FDeleteSQL write SetDeleteSQL;
  property ModifySQL: TIB_StringList read FModifySQL write SetModifySQL;
  property LockSQL: TIB_StringList read FLockSQL write SetLockSQL;
  property InsertSQL: TIB_StringList read FInsertSQL write SetInsertSQL;
public
{$IFNDEF HELPSCAN}
  constructor Create( AOwner: TComponent ); override;
  destructor Destroy; override;
  procedure CancelQuery; dynamic;
  property EditDSQL: TIB_Statement read GetEditDSQL;
  property LockDSQL: TIB_Statement read GetLockDSQL;
  property InsertDSQL: TIB_Statement read GetInsertDSQL;
  property DeleteDSQL: TIB_Statement read GetDeleteDSQL;
  property NeedRecordResync: boolean read FNeedRecordResync;
{$ENDIF}
end;

// IBA_UpdateSQL.IMP

{                                                                              }
{ TIB_DataSource                                                               }
{                                                                              }

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Dirk Schiffler <ds@rcs.de>                                                  }
{  10/13/2001                                                                  }
{     I make FInterfaceDisabledLevel public like FControlsDisabledLevel.       }
{                                                                              }
{******************************************************************************}

{: Event to notify of a column that has had a data change or update request.}
TIB_DataChangeEvent = procedure( Sender: TIB_DataSource;
                                 ADataset: TIB_Dataset;
                                 AField: TIB_Column) of object;
{: General event type used by the TIB_DataSource class.}
TIB_DataSourceEvent = procedure( Sender: TIB_DataSource;
                                 ADataset: TIB_Dataset ) of object;
{: This component provides a reference to an IB_Dataset
component.
<br><br>
It has the mechanisms for attaching data aware controls to a dataset.
<br><br>
It also serves, when required, to implement the linkages between datasets that
are related in one or more ways, via the KeySource and MasterSource properties
of the TIB_Dataset class.}
TIB_DataSource = class(TIB_StatementLink)
private
{ Utility Storage Fields }
  FDataLinkList: TList;
{ Property Storage Fields }
  FAutoEdit: boolean;
  FAutoInsert: boolean;
  FAnnounceFocus: boolean;
  FAllowFocus: boolean;
  FDSStateChange: boolean;
  FDSDataChange: boolean;
  FControlsDisabledLevel: integer;
  FInterfaceDisabledLevel: integer;
  FEnabled: boolean;
{ Flag for testing }
  flag_junk_value: integer;
{ Event Storage Fields }
  FBeforeAssignment: TIB_DataSourceEvent;
  FAfterAssignment: TIB_DataSourceEvent;
  FOnGainFocus: TIB_DataSourceEvent;
  FOnLoseFocus: TIB_DataSourceEvent;
  FOnStateChanged: TIB_DataSourceEvent;
  FOnDataChange: TIB_ColLinkEvent;
  FOnUpdateData: TIB_ColLinkEvent;
{ Property Access Methods }
  function GetDataLinkCount: integer;
  function GetDataset: TIB_Dataset; //~virtual;
  procedure SetDataset( AValue: TIB_Dataset ); //~virtual;
  function GetState: TIB_DatasetState; //~virtual;
  function GetRowState: TIB_RowState; //~virtual;
  function GetReadOnly: boolean; //~virtual;
  function GetCanModify: boolean; //~virtual;
  function GetNeedToPost: boolean; //~virtual;
  function GetEof: boolean;
  function GetBof: boolean;
  procedure SetAnnounceFocus( AValue: boolean );
  procedure SetAllowFocus( AValue: boolean );
  function IsAllowFocusStored: boolean;
protected
{ Inherited Methods }
  procedure SetSession( ASession: TIB_Session ); override;
  procedure SetStatement( AValue: TIB_Statement ); override;
  procedure SysPrepareSQL; override;
  procedure ProcessEvent( AEvent: TIB_StatementEventType;
                          Info: longint ); override;
{ System Methods }
  procedure SysStateChanged; //~virtual;
  procedure SysDataChange( AField: TIB_Column ); virtual;
  procedure SysUpdateData( AField: TIB_Column ); //~virtual;
{ Event Dispatch Methods  }
  procedure DoGainFocus; //~virtual;
  procedure DoLoseFocus; //~virtual;
  procedure DoStateChanged; //~virtual;
{ Utility Methods }
  procedure AddDataLink( ADataLink: TObject ); //~virtual;
  procedure RemoveDataLink( ADataLink: TObject ); //~virtual;
{ Property Access Methods }
  procedure SetEnabled( AValue: boolean ); //~virtual;
  property DataLinkList: TList read FDataLinkList;
public
  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;
{ Methods }
{: Method to cache up any state and datachange notifications. All other
event notifications will be discarded except the before and after assignment
notifications.}
  procedure DisableControls;
{: Method to dispatch cached-up state and datachange notifications.
<br><br>
All other event notifications will resume operating.
<br><br>
Note, this call may be nested.  Cached events will not be dispatched until
the last level terminates.
<br><br>
Be sure to use a "try...finally" block to guarantee that every call to
DisableControls has a corresponding call to EnableControls.}
  procedure EnableControls;
{: Similar to DisableControls but calling this only disables user interface
controls.  Master-Detail and Lookup links will continue to operate.
NB. Isolates user interface controls by checking for datalinks that have
been derived from TIB_ControlDataLink.
<br><br>
Be sure to use a "try...finally" block to guarantee that every call to
DisableInterface has a corresponding call to EnableInterface.}
  procedure DisableInterface;
{: Method to restore enabled status of interface controls.  See
DisableInterface. }
  procedure EnableInterface;
{: Method that checks for a circular relationship between datasets.}
  function IsLinkedTo(Dataset: TIB_Dataset): boolean;
{: Method used to attempt to put the dataset into dssEdit state in order for
a control to allow its data to be modified.}
  function Edit: boolean; dynamic;
{: Method used to attempt to put the dataset into dssInsert state in order
to allow data to be inserted.}
  function Insert: boolean; dynamic;
{: General request made to see if the dataset can go into a state that will
allow a general change to data in a data aware control.}
  function Modify: boolean; dynamic;
{: Method that returns whether the dataset is in a state that allows data to
be modified.}
  function Modifying: boolean; dynamic;
{: This method will cause all data aware controls to be reset to the current
values in the data buffers. }
  procedure Reset; dynamic;
{: This method causes this component to become the currently focused
TIB_DataSource component for the current Session.}
  procedure SetFocus; dynamic;

{ Properties }

{: This property indicates whether this component will be announcing state and
datachange events coming from the parent dataset.}
  property ControlsDisabledLevel: integer read FControlsDisabledLevel;
{: This property is similar to ControlsDisabledLevel but it pertains only
to preventing the announcement of data change events that are tied to visual
controls.}  
  property InterfaceDisabledLevel: integer read FInterfaceDisabledLevel;
{: Number of DataLinks that are associated with this DataSource.}
  property DataLinkCount: integer read GetDatalinkCount;
{: Returns the current state of the associated dataset. }
  property State: TIB_DatasetState read GetState;
{: Current state of the Fields.RowState of the associated dataset. }
  property RowState: TIB_RowState read GetRowState;
{: Returns the statement type that was prepared in the associated dataset.}
  property StatementType: TIB_StatementType read GetStatementType;
{: Indicates whether the controls attached through this component should be
Read-Only.}
  property ReadOnly: boolean read GetReadOnly;
{: This property returns True if the dataset contents can be modified.}
  property CanModify: boolean read GetCanModify;
{: This property returns True if the associated dataset is in a state
that needs to be posted.}
  property NeedToPost: boolean read GetNeedToPost;
{: This property returns True if the associated dataset is at Eof.}
  property Eof: boolean read GetEof;
{: This property returns True if the associated dataset is at Bof.}
  property Bof: boolean read GetBof;

published
{ Properties }

{: When a DataSource receives the current focus, this property determines
whether the session should announce that a new DataSource has become focused.}
  property AnnounceFocus: boolean read FAnnounceFocus
                                  write SetAnnounceFocus
                                  default false;
{: This property determines whether the session should consider this DataSource
focused if its SetFocus method is called. Set this to True if you want the
controls associated with this datasource to be announced and considered the
focused datasource in the session.}
  property AllowFocus: boolean read FAllowFocus
                               write SetAllowFocus
                               stored IsAllowFocusStored;
{: This property determines whether a data-aware control can place the dataset
into dssEdit state if not already in that state.}
  property AutoEdit: boolean read FAutoEdit
                             write FAutoEdit
                             default true;
{: This property determines whether a data-aware control can place the dataset
into dssInsert state if not already in that state.}
  property AutoInsert: boolean read FAutoInsert
                               write FAutoInsert
                               default true;
{: This is the reference to the Dataset used by this component.}
  property Dataset: TIB_Dataset read GetDataset write SetDataset;
{: Turns off the announcement of data and state change event notifications.
<br><br>
It will also turn off the announcement of many other events;  but the assignment
events will always stay in effect.
<br><br>
This property should not be used to manipulate visual controls. That portion
of the standard VCL components will not be fully supported.
<br><br>
There are other more powerful and fitting techniques for working with datasets
in ways that avoid affecting the user interface.  For example, using the
AssignSQLWithSearch method of the TIB_Cursor component, it is possible to
transfer all of the SQL criteria from the component that defines the dataset for
the visual interface and process it separately.  Reports and exports can be run
in a separate transaction with minimum effort this way, too.
<br><br>
With the IB_Query component there is a technique to scan the entire dataset if
needed, without impacting any of the visual controls.  It uses the BufferFirst,
BufferNext, BufferEof, BufferRowNum, BufferBookmark, BufferFields,
BufferFieldByName(), etc. resources.  It is like having a second cursor to
operate within the dataset. It is really easy to move data between them, too.
BufferFields.RowData := Fields.RowData will transfer the
contents of the whole record from the Fields to the BufferFields.}
  property Enabled: boolean read FEnabled write SetEnabled default true;

{------------------------------------------------------------------------------}

{: Number of DataLinks associated to this component.
<br><br>
This is used for design-time visibility only.}
  property _DataLinkCount: integer read GetDatalinkCount
                                   write flag_junk_value
                                   stored false;
{------------------------------------------------------------------------------}

{ Events }

{: This event is fired before the Dataset property is about to be changed to
a new value.}
  property BeforeAssignment: TIB_DataSourceEvent read FBeforeAssignment
                                                 write FBeforeAssignment;
{: This event is fired after the Dataset property has been changed to
a new value.}
  property AfterAssignment: TIB_DataSourceEvent read FAfterAssignment
                                                write FAfterAssignment;
{: When this component gains the focus this event is fired.}
  property OnGainFocus: TIB_DataSourceEvent read FOnGainFocus
                                            write FOnGainFocus;
{: When this component loses the focus this event is fired.}
  property OnLoseFocus: TIB_DataSourceEvent read FOnLoseFocus
                                            write FOnLoseFocus;
{: When the dataset announces a data change event this event gets fired.}
  property OnDataChange: TIB_ColLinkEvent read FOnDataChange
                                          write FOnDataChange;
{: When the dataset announces an update data event this event gets fired.}
  property OnUpdateData: TIB_ColLinkEvent read FOnUpdateData
                                          write FOnUpdateData;
{: When the dataset announces a state change event this event gets fired.}
  property OnStateChanged: TIB_DataSourceEvent read FOnStateChanged
                                               write FOnStateChanged;
end; { TIB_DataSource }

//IBA_DataSource.IMP

{                                                                              }
{ TIB_DataLink                                                                 }
{                                                                              }

{: General event notification for the IB_DataLink component.}
TIB_DataLinkEvent = procedure( Sender: TIB_DataLink;
                               ADataSource: TIB_DataSource ) of object;

{: General event notification for the IB_DataLink component that also
includes a reference to a single column, which might be either an input or an
output column object.}
TIB_ColumnDataLinkEvent = procedure( Sender: TIB_DataLink;
                                     ADataSource: TIB_DataSource;
                                     AField: TIB_Column ) of object;

{: This component is used to make another component or control data aware.
<br><br>
It is the base class of many specialized links tailored to suit more specific
needs.
<br><br>
It is typically used as a contained or owned object that gives a clean
interface to interact with the data. See the IB_Controls and IB_Grid units to
see how it is implemented.}
TIB_DataLink = class(TIB_Component)
private
  FDataSource: TIB_DataSource;
  FReceiveFocus: boolean;
  FEditing: boolean;
  FChangingLevel: integer;
  FUpdating: boolean;
  FBoundToBuffer: boolean;
  FIgnoreColorScheme: boolean;
  FSearchAlways: boolean;
  FSearchBuffer: string;
  FSearchSaved: string;
  FSearchLast: string;
  FAdvSearchDisplay: string;
  FAdvSearchDisplaySaved: string;
  FAdvSearchDisplayLast: string;
  FAdvSearchWhere: string;
  FAdvSearchWhereSaved: string;
  FAdvSearchWhereLast: string;
  FAdvSearchData: string;
  FAdvSearchDataSaved: string;
  FAdvSearchDataLast: string;
  FAfterAssignment: TIB_DataLinkEvent;
  FAfterExecute: TIB_DataLinkEvent;
  FBeforeAssignment: TIB_DataLinkEvent;
  FBeforeExecute: TIB_DataLinkEvent;
  FOnPreparedChanged: TIB_DataLinkEvent;
  FOnPrepareSQL: TIB_DataLinkEvent;
  FOnReceiveFocus: TIB_DataLinkEvent;
  FOnEditingChanged: TIB_DataLinkEvent;
  FOnActiveChange: TIB_DataLinkEvent;
  FOnStateChanged: TIB_DataLinkEvent;
  FOnLayoutChanged: TIB_DataLinkEvent;
  FOnDataChange: TIB_ColumnDataLinkEvent;
  FOnUpdateData: TIB_ColumnDataLinkEvent;

  { Pha Inicio }
  FOnOrderingChanged: TIB_DataLinkEvent;
  FOnOrderingLinkChanged: TIB_DataLinkEvent;
  FOnSearchingLinkChanged: TIB_DataLinkEvent;
  { Pha Fim }

{ Property Access methods }
  function GetActiveRecord: longint;
  procedure SetActiveRecord( AValue: longint );
  function GetBufferRecord: longint;
  procedure SetBufferRecord( AValue: longint );
  function GetKeySource: TIB_DataSource;
  function GetMasterSource: TIB_DataSource;
  function GetBufferRowCount: longint;
  function GetDisabled: boolean;
  function GetBufferHasBof: boolean;
  function GetBufferHasEof: boolean;
  function GetBufferActive: boolean;
  procedure SetSearchAlways( AValue: boolean );
  function GetDataChanging: boolean;
protected
  FIsCtrlLink: boolean;
  FIsGridLink: boolean;
  FInternalSearchUpdate: integer;
  FSearchUpdateRequired: boolean;
  FSessionCursorIsLocked: boolean;
{ Inherited Methods }
  procedure Loaded; override;
  procedure SetSession( ASession: TIB_Session ); override;
{ Property access methods }
  procedure SetDataSource( AValue: TIB_DataSource ); //~virtual;
  procedure SetBoundToBuffer( AValue: boolean ); //~virtual;
  procedure SetIgnoreColorScheme( AValue: boolean ); //~virtual;
  function GetDataset: TIB_Dataset; //~virtual;
  function GetPrepared: boolean; //~virtual;
  function GetActive: boolean; //~virtual;
  function GetNeedToPost: boolean; //~virtual;
  function GetState: TIB_DatasetState; //~virtual;
  function GetRowState: TIB_RowState; //~virtual;
  function GetCanModify: boolean; //~virtual;
  function GetReadOnly: boolean; virtual;
  function GetPreventDeleting: boolean; virtual;
  function GetPreventEditing: boolean; virtual;
  function GetPreventInserting: boolean; virtual;
  function GetPreventSearching: boolean; virtual;
  function GetSearchEntryName: string; virtual;
  function GetField: TIB_Column; virtual;
  procedure SetSearchBuffer( const AValue: string ); //~virtual;
  function GetSearchBuffer: string; virtual;
  function GetAdvSearchDisplay: string; //~virtual;
  procedure SetAdvSearchWhere( const AValue: string ); //~virtual;
  function GetAdvSearchWhere: string; //~virtual;
  procedure SetAdvSearchData( const AValue: string ); virtual;
  function GetAdvSearchData: string; //~virtual;
  procedure ClearSearchCriteria( WithUpdate: boolean ); //~virtual;
  function GetColor: TColor; virtual;
  function GetEditingColor: TColor; virtual;
  function GetInsertingColor: TColor; virtual;
  function GetDeletingColor: TColor; virtual;
  function GetSearchingColor: TColor; virtual;
  function GetInvalidColor: TColor; virtual;
  function GetActiveColor: TColor; //~virtual;
  procedure SetReceiveFocus( AValue: boolean ); //~virtual;
  function GetColorScheme: boolean; //~virtual;
  procedure SetEditing( AValue: boolean ); //~virtual;
  function GetEditing: boolean; //~virtual;
  function GetRecordCount: longint; //~virtual;
  procedure SetBufferCount( AValue: longint ); //~virtual;
  function GetBufferCount: longint; //~virtual;
  function GetBof: boolean; //~virtual;
  function GetEof: boolean; //~virtual;
{ System Methods }
  procedure SysPrepareSQL; virtual;
  procedure SysPreparedChanged; virtual;
  procedure SysBeforeExecute; //~virtual;
  procedure SysAfterExecute; //~virtual;
  procedure SysEditingChanged; //~virtual;
  procedure SysActiveChange; //~virtual;
  procedure SysStateChanged; virtual;
  procedure SysLayoutChanged; virtual;
  procedure SysDataChange( AField: TIB_Column ); virtual;
  procedure SysUpdateData( AField: TIB_Column ); virtual;
  procedure SysBindingChanged; virtual;
  procedure SysAssignField; virtual;
{ Focusing stuff }
  procedure DoReceiveFocus( DS: TIB_DataSource ); //~virtual;
{ Event Dispatch methods }

  { Pha Inicio }
  procedure ProcessStatementEvent( AEvent: TIB_StatementEventType;
                                   Info: longint ); virtual;
  { Pha Fim }

  procedure ProcessGridEvent( AEvent: TIB_StatementEventType;
                              Info: longint ); virtual;
  procedure DoBeforeAssignment; virtual;
  procedure DoAfterAssignment; virtual;
  procedure DoPrepareSQL; virtual;
  procedure DoDataChange( AField: TIB_Column ); //~virtual;
  procedure DoUpdateData( AField: TIB_Column ); //~virtual;
  procedure CheckBrowseMode; virtual;

public

{ Methods }

  procedure DataChange;

{ Properties }
  property BoundToBuffer: boolean read FBoundToBuffer write SetBoundToBuffer;
  property BufferHasBof: boolean read GetBufferHasBof;
  property BufferHasEof: boolean read GetBufferHasEof;
  property BufferActive: boolean read GetBufferActive;
  property DataChangingLevel: integer read FChangingLevel;
  property DataChanging: boolean read GetDataChanging;
  property DataUpdating: boolean read FUpdating;
  property Bof: boolean read GetBof;
  property Eof: boolean read GetEof;
  property IgnoreColorScheme: boolean read FIgnoreColorScheme
                                      write SetIgnoreColorScheme;
  property InvalidColor: TColor read GetInvalidColor;

{: This property can make the control act as though it were always in search
mode.}
  property SearchAlways: boolean read FSearchAlways write SetSearchAlways;
{: Property indicates whether the datalink is currently updating from its
controls.}
  property UpdatingRecord: boolean read FUpdating;

{ Event Hooks }

{: Event notification when this datalink has just been assigned to a new
datasource.}
  property AfterAssignment: TIB_DataLinkEvent read FAfterAssignment
                                              write FAfterAssignment;
{: General event notification.}
  property AfterExecute: TIB_DataLinkEvent read FAfterExecute
                                           write FAfterExecute;
{: Event notification when this datalink is about to be assigned to a new
datasource.}
  property BeforeAssignment: TIB_DataLinkEvent read FBeforeAssignment
                                               write FBeforeAssignment;
{: General event notification.}
  property BeforeExecute: TIB_DataLinkEvent read FBeforeExecute
                                            write FBeforeExecute;
{: General event notification.}
  property OnPreparedChanged: TIB_DataLinkEvent read FOnPreparedChanged
                                                write FOnPreparedChanged;
{: General event notification.}
  property OnPrepareSQL: TIB_DataLinkEvent read FOnPrepareSQL
                                           write FOnPrepareSQL;
{: General event notification.}
  property OnReceiveFocus: TIB_DataLinkEvent read FOnReceiveFocus
                                             write FOnReceiveFocus;
{: General event notification.}
  property OnEditingChanged: TIB_DataLinkEvent read FOnEditingChanged
                                               write FOnEditingChanged;
{: General event notification.}
  property OnActiveChange: TIB_DataLinkEvent read FOnActiveChange
                                             write FOnActiveChange;
{: General event notification.}
  property OnStateChanged: TIB_DataLinkEvent read FOnStateChanged
                                             write FOnStateChanged;
{: General event notification.}
  property OnLayoutChanged: TIB_DataLinkEvent read FOnLayoutChanged
                                              write FOnLayoutChanged;
{: General event notification.}
  property OnDataChange: TIB_ColumnDataLinkEvent read FOnDataChange
                                                 write FOnDataChange;
{: General event notification.}
  property OnUpdateData: TIB_ColumnDataLinkEvent read FOnUpdateData
                                                 write FOnUpdateData;

{ Pha Inicio }
  property OnOrderingChanged: TIB_DataLinkEvent read  FOnOrderingChanged
                                                write FOnOrderingChanged;
  property OnOrderingLinkChanged: TIB_DataLinkEvent read  FOnOrderingLinkChanged
                                                    write FOnOrderingLinkChanged;
  property OnSearchingLinkChanged: TIB_DataLinkEvent read  FOnSearchingLinkChanged
                                                     write FOnSearchingLinkChanged;
{ Pha Fim }

{ Inherited Methods }
  constructor Create( AOwner: TComponent ); override;
  destructor Destroy; override;

{ New methods }

{: Method to denote that the Active status of the data link has been changed.}
  procedure ActiveChanged; virtual;
{: Used to insert a record at the last position in the dataset.}
  procedure Append; dynamic;
{: Method to indicate when the dataset property has been changed.}
  procedure DatasetChanged; virtual;
{: Attempt to go into dssEdit state and return the result without raising an
exception.}
  function Edit: Boolean;
{: Make the control to which this datalink may be bound receive the focus.}
  procedure FocusControl( AField: TIB_ColumnRef ); dynamic;
{: Attempt to go into dssInsert state and return the result without raising
an exception.}
  procedure Insert; dynamic;
{: Attempt to go into dssEdit state (if a row is current in the buffer) or into
dssInsert state (if no row is current) and return the result without raising
an exception.}
  function Modify: boolean; dynamic;
{: Refresh the data in the control to which this datalink is displaying to
reflect the most current.}
  procedure Reset; dynamic;
{//: This method simply causes the Before and After Scroll events to fire in the
dataset.}
//  procedure DataSetScrolled( Distance: Integer ); //~virtual;
{: VCL compatibility.}
  procedure EditingChanged; virtual;
{: VCL compatibility.}
  procedure LayoutChanged; //~virtual;
{: VCL compatibility.}
  procedure RecordChanged(Field: TIB_Column); virtual;
{: Set the AdvSearchDisplay value.  Note that the AdvSearchDisplay property
is read-only because it will return the internally stored AdvSearchDisplay only
if it is defined.  If there is not one defined, it simply returns the SearchBuffer. }
  procedure SetAdvSearchDisplay( const AValue: string ); //~virtual;
{: Announce the focusing of the IB_DataSource, IB_Dataset, IB_Transaction
and IB_Connection for this datalink.}
  procedure SetFocus; dynamic;
{: Save to the dataset any changes the control is holding.}
  procedure UpdateData; dynamic;
  procedure UpdateRecord; dynamic;
{: This method will cause the screen cursor for the session to be left alone.}
  procedure LockSessionCursor;
{: This method will allow the screen cursor for the session to be altered.}
  procedure UnlockSessionCursor;

{ Properties }

public
{: Returns True if the dataset is Active or False if not.}
  property Active: boolean read GetActive;
{: VCL compatibility property. This is simply the RowNum.}
  property ActiveRecord: longint read GetActiveRecord write SetActiveRecord;
{: This is simply the BufferRowNum.}
  property BufferRecord: longint read GetBufferRecord write SetBufferRecord;
{: VCL compatibility property.
<br><br>
IB Objects does not have an individual buffer for each datalink. All
datalinks make direct use of the dataset's buffer.}
  property BufferCount: longint read GetBufferCount write SetBufferCount;
{: Number of rows in the dataset buffer. This applies to the TIB_BDataset
class only. (IB_Query)}
  property BufferRowCount: longint read GetBufferRowCount;
{: General datalink property.}
  property CanModify: boolean read GetCanModify;
{: If using the ColorScheme, this returns the appropriate color as defined in
the IB_Session.}
  property Color: TColor read GetColor;
{: Returns True if the dataset is using the colorscheme.}
  property ColorScheme: boolean read GetColorScheme;
{: Dataset to which this datalink is bound or nil if none.}
  property Dataset: TIB_Dataset read GetDataset;
{: Returns the DataSource to which this datalink is bound or nil if none.}
  property DataSource: TIB_DataSource read FDataSource write SetDataSource;
{: Returns True if the referenced DataSource is disabled.}
  property Disabled: boolean read GetDisabled;
{: Same as NeedToPost.}
  property Editing: boolean read GetEditing;
{: Returns the KeySource of the referenced dataset or nil if none.}
  property KeySource: TIB_DataSource read GetKeySource;
{: Returns the MasterSource of the referenced dataset or nil if none.}
  property MasterSource: TIB_DataSource read GetMasterSource;
{: Returns True if the dataset is in a state that needs to be posted or
cancelled.}
  property NeedToPost: boolean read GetNeedToPost;
{: Returns the Prepared status of the dataset or false if none.}
  property Prepared: boolean read GetPrepared;
{: General datalink property.}
  property PreventDeleting: boolean read GetPreventDeleting;
{: General datalink property.}
  property PreventEditing: boolean read GetPreventEditing;
{: General datalink property.}
  property PreventInserting: boolean read GetPreventInserting;
{: General datalink property.}
  property PreventSearching: boolean read GetPreventSearching;
{: General datalink property.}
  property ReadOnly: boolean read GetReadOnly;
{: Determines whether the announcements of focusing datasources should be
received by this component.}
  property ReceiveFocus: boolean read FReceiveFocus write SetReceiveFocus;
{: Returns the number of rows in the entire dataset if it were to be
completely fetched.}
  property RecordCount: longint read GetRecordCount;
{: Dataset property extender.}
  property RowState: TIB_RowState read GetRowState;
{: Access to the SearchBuffer used in dssSearch state.
<br><br>
This may also be modified when the dataset is only prepared. It will call
InvalidateSQL as necessary.}
  property SearchBuffer: string read GetSearchBuffer write SetSearchBuffer;
{: Returns the internal AdvSearchDisplay value if it is defined (using
SetAdvSearchDisplay); otherwise, it simply returns the SearchBuffer. }
  property AdvSearchDisplay: string read GetAdvSearchDisplay;
{: This property allows certain controls to define an explicit WHERE subclause
directly, without the value being parsed by the normal search buffer
processing.  Such a definition is in ADDITION to any normal search buffer
value that may be defined in the normal way. }
  property AdvSearchWhere: string read GetAdvSearchWhere write SetAdvSearchWhere;
{: This is simply a storage area for a control to hold its own search
information, in its own format.  It is not interpreted or used in any way by
the normal search processing.  Generally a control will store information in
this area which will allow it to build/restore values that it places in the
AdvSearchWhere property. }
  property AdvSearchData: string read GetAdvSearchData write SetAdvSearchData;
{: State of the referenced Dataset or dssInactive if none.}
  property State: TIB_DatasetState read GetState;
{: Indicates whether the session cursor is in a locked status.}
  property SessionCursorIsLocked: boolean read FSessionCursorIsLocked;
end;

// IBA_DataLink.IMP

{                                                                              }
{ IB_KeyDataLink                                                               }
{                                                                              }

{$IFNDEF HELPSCAN}
{: This component is used to handle the relationship between two datasets
formed when the parent dataset is supplying dynamic lookup information to its
child dataset by
<ul>
<li>Child connecting its KeySource property to the Datasource of the
parent and
<li>matching the child dataset's KeyLinks to a parent column that acts as a
key pointing to one and only one of the child rows
</ul>
}
TIB_KeyDataLink = class(TIB_DataLink)
private
  FChildDataset: TIB_Dataset;
  FSearchSync: boolean;
protected
  procedure DoBeforeAssignment; override;
  procedure DoAfterAssignment; override;
  procedure SysPreparedChanged; override;
  procedure SysStateChanged; override;
  procedure SysDataChange( AField: TIB_Column ); override;
  procedure SetAdvSearchData( const AValue: string ); override;
  function GetSearchEntryName: string; override;
public
  procedure DatasetChanged; override;
  procedure CheckBrowseMode; override;
{: Reference to the IB_Dataset that owns this link and is the child part
of the relationship.}
  property ChildDataset: TIB_Dataset read FChildDataset;
{: Updates the search criteria according to the data in the lookup key in the
current row.  It expects that the parent dataset (the KeyDataset) has already
been confirmed to be in dssSearch mode.<br>
This property is made public only to allow access from BDataset.}
  procedure SetSearchFromKeyData;
{: Used when considering the read status of the key columns in the parent
dataset (the KeyDataset).}
  function Modify: boolean; override;
end;
{$ENDIF}

// IBA_KeyDataLink.IMP

{                                                                              }
{ IB_MasterDataLink                                                            }
{                                                                              }

{$IFNDEF HELPSCAN}
{: This component is used to handle the MasterLinks/MasterSource based
master-detail relationship between datasets.}
TIB_MasterDataLink = class(TIB_DataLink)
private
  FChildDataset: TIB_Dataset;
  FCheckBrowseModeLevel: integer;
protected
  procedure CheckBrowseMode; override;
  procedure DoAfterAssignment; override;
  procedure SysPreparedChanged; override;
  procedure SysPrepareSQL; override;
  procedure SysStateChanged; override;
public
{: Reference to the IB_Dataset that owns this link and is the child part
of the relationship.}
  property ChildDataset: TIB_Dataset read FChildDataset;
end;
{$ENDIF}

// IBA_MasterDataLink.IMP

{                                                                              }
{ TIB_Row                                                                      }
{                                                                              }

{: System defined event type for a row buffer.}
TIB_RowEvent = procedure( ARow: TIB_Row ) of object;
{: System defined event type for a row buffer or column object.}
TIB_RowChangedEvent = procedure( ARow: TIB_Row;
                                 AField: TIB_Column ) of object;
{: Structure used to store blob node mappings for the row.}
PIB_BlobFieldMap = ^TIB_BlobFieldMap;
TIB_BlobFieldMap = record
  BlobIndex: array [0..0] of integer;
end;
{: This object is used to maintain the row buffer and the TIB_Columns array
associated with the buffer.
<br><br>
It is implemented by a these properties at the TIB_Statement level:
<br><br>
Params - Gives access to the input parameters defined in the SQL statement.
Fields - Gives access to the output fields defined in the SQL statement.
<br><br>
It is accessed additionally with these properties at the TIB_BDataset level:
<br><br>
BufferFields - A way to access data from a buffered dataset without having to
move the current record pointer.
<br><br>
KeyFields - An exclusive TIB_Dataset property to give a TIB_Row reference so
that key lookup methods can be called in order to seek a record in the dataset.
<br><br>
Because the Params property can change each time search criteria are applied, be
careful not to depend on the numerical reference of a parameter's TIB_Column
object.
<br><br>
Re-use of an input parameter name for subsequent input parameters and having
each one bound to the same parameter object is allowed, provided the input
parameters are all of the same type.
<br><br>
If a SELECT list has sub-selects in the output column lists that use input
parameters, InterBase parses them in a different order than expected.  However,
unlike the BDE, IB Objects anticipates this and correctly lines up the
parameters.
<br><br>
Much of the interface for this object is accessed from the TIB_Statement
class itself via methods such as FieldByName() or ParamByName().}
TIB_Row = class(TObject)
private
{ Property Storage fields }
  lastColumnIndex : Integer;
  FStatement: TIB_Statement;
  FArrayCount: word;
  FArrayList: TList;
  FBlobCount: word;
  FBlobList: TList;
  FCalcCount: word;
  FCalcList: TList;
  FColumnCount: word;
  FSysColumnCount: word;
  FColumnList: TList;
  FPSQLDA: PXSQLDA;
  FRowType: TIB_RowType;
  FRowState: TIB_RowState;
  FCursorName: string;
  FUpdatedColumns: TList;
  FUpdatedWholeRow: boolean;
  FUpdatingCounter: word;
  FIsKeyFields: boolean;
  FIsKeyFieldsWhole: boolean;
  FIsBufferFields: boolean;
  FCalculatingFields: boolean;
  FIsRowNumChanged: boolean;
  FIsUpdatedRowNumChanged: boolean;
  FRowNode: PIB_Node;
  FSysBlobHead: PIB_BlobNode;
  FBDENamesValid: boolean;
{ Record buffer storage Columns }
  FBufferLength: word;
  FRowBuffer: pointer;
  FOldBuffer: pointer;
  FBlobNodeList: pointer;
  FBlobFieldMap: PIB_BlobFieldMap;
  FTempBlobIDCount: integer;
{ Info property storage Columns }
  FRelationList: TStringList;
{ Event storage Columns }
  FBeforeModify: TIB_RowChangedEvent;
  FAfterModify: TIB_RowChangedEvent;
  FOnRowStateChanged: TIB_RowEvent;
  function GetUpdatedColumnCount: word;
  function GetUpdatedWholeRow: boolean;
  procedure SysPrepareFailed;
protected
{ Property Access Methods }
  function GetColumns( Index: word ): TIB_Column;
  function GetRelationCount: word;
  function GetRelationName( Index: word ): string;
  procedure SetRowState( AValue: TIB_RowState );
  function GetRowData: string;
  procedure SetRowData( const AValue: string );
  function GetOldRowData: string;
  procedure SetOldRowData( const AValue: string );
  function GetPBlobHead: PPIB_BlobNode;
  procedure FillRelAliasInfo;
{ Utility Methods }
  procedure AfterPostBuffers( Posted: boolean );
  procedure AllocateBlobNodes;
  procedure AllocateVARList;
  procedure PostBlobBuffers;
  procedure CancelBlobBuffers;
  procedure ClearBlobNodes( ClearCache: boolean );
  procedure CreateVARList;
  procedure DefaultColumnCreation( const NewIndex: smallint;
                                   const PSQLVAR: PXSQLVAR;
                                   var NewIB_Column: TIB_Column );
  procedure FreeVARList;
  procedure NameParams;
  procedure PrepDuplicateParamList;
  procedure NameVARListForBDE;
  procedure TrimBlobNodeCache;
  procedure LoadFromNode( Notify, LoadBlobs: boolean );
  procedure LoadBlobsFromNode;
  procedure StoreBlobsToNode;
  procedure TrimBlobsFromList;
{ System Methods }
  procedure SysClearBuffers;
  procedure SysRowStateChanged;
  procedure SysBeforeColumnModify( IB_Field: TIB_Column );
  procedure SysAfterColumnModify( IB_Field: TIB_Column );
  procedure SysBeforeModify( IB_Field: TIB_Column );
  procedure SysAfterModify( IB_Field: TIB_Column );
{ Event Dispatch Methods }
  procedure DoBeforeModify( IB_Field: TIB_Column );
  procedure DoAfterModify( IB_Field: TIB_Column );
{ System Methods }
  procedure SysUpdate( NewColumnCount: word );
  procedure SysApplyUpdates( SingleEventOnly: boolean );
public
  FStrictModifyOnlyLevel: integer;
  constructor Create( AStatement: TIB_Statement;
                      DescType: TIB_RowType );
  destructor Destroy; override;

{ Utility Methods }

{: Method to cause the calculated fields to be recalculated.}
  procedure CalculateFields;
{: Sets the buffers to null and blank.}
  procedure ClearBuffers( NewRowState: TIB_RowState );
{: Throws away any changes made to the buffers.  For an update, it restores the
original values to the columns;  for an insert, it clears them out.}
  procedure CancelBuffers( AClearBlobNodes: boolean );
{: This method is called after each fetch.
<br><br>
Its responsibility is to populate the OldXXX buffer and to refresh BLOB and
ARRAY columns.}
  procedure RefreshBuffers( NewRecord, ClearBlobs, NeedCalc: boolean );
{: This method binds the columns' SQLDATA pointers properly within the
record buffer.}
  procedure UpdateBufferPointers;
{: This method makes the duplicate column buffer pointers match after there
has been an alteration to the SQLData pointer.}
  procedure SetDuplicateBufferPointers;
{: }
  function GetColumnValue( const ColumnName: string ): Variant;
{: }
  procedure SetColumnValue( const ColumnName: string;
                            const AValue: Variant );
{: }
  function GetColumnOldValue( const ColumnName: string ): Variant;
{: }
  procedure GetColumnList( AList: TList; const ColumnNames: string );
{: This method makes those columns that are not nullable to be not null.

It also cleans the unused memory on variable length strings to #0. }
  procedure CleanBuffers( ResetNullInd: boolean );
{: This method starts a suspension of data change notifications, caused by
altering the values of the associated TIB_Column objects for the row, which
will last until all of the intended processing to the data of the buffer is
complete.
<br><br>
This should be used in the context of a "try...finally" block, with the
EndUpdate() method being called regardless of an exception.
<br><br>
Here is some sample code which demonstrates how to use this:
<br>
<br>// Given a query, an array of names and an array of values; this routine
<br>// will set all the parameters that match the names. It will use the values
<br>// to decide what kind of parameter it is.
<br>
<br>procedure Dol_Set_Query_Parameters
<br>(
<br>query : TIB_Query;
<br>const input_paramNames : array of string;
<br>const input_ParamValues : array of const
<br>);
<br>type
<br>  ParamCount_t = Integer;
<br>var
<br>  param_i : ParamCount_t;
<br>  pName : string;
<br>  pValue : TVarRec;
<br>  fieldParam : TIB_Column;
<br>begin
<br>  with query do begin
<br>    Prepared := true;
<br>    Params.BeginUpdate;
<br>    try
<br>      for param_i := 0 to High(input_paramNames) do begin
<br>        pName := input_paramNames[param_i];
<br>        pValue := input_ParamValues[param_i];
<br>        fieldParam := ParamByName(pname);
<br>        with pValue do begin
<br>          case VType of
<br>            vtInteger : fieldParam.AsInteger := VInteger;
<br>            vtBoolean : fieldParam.AsBoolean := VBoolean;
<br>            vtChar : fieldParam.AsString := VChar;
<br>            vtExtended : begin
<br>            case fieldParam.SQLType of
<br>              Sql_Date : fieldParam.AsDateTime := VExtended^;
<br>              Sql_Float,
<br>              Sql_DFloat : fieldParam.AsFloat := VExtended^;
<br>              Sql_Double : FieldParam.AsDouble := VExtended^;
<br>            end;
<br>          end;
<br>          vtString : fieldParam.AsString := VString^;
<br>          vtPChar : fieldParam.AsString := VPChar;
<br>          vtAnsiString : fieldParam.AsString := string(VAnsiString);
<br>          vtCurrency : fieldParam.AsCurrency := VCurrency^;
<br>          else
<br>          end;
<br>        end;
<br>      end;
<br>    finally
<br>    // Make it so that there is only a single change notification even when
<br>    // multiple parameters are modified.
<br>    // If RefreshOnParamChange is true then the dataset will be refreshed
<br>    // here if it was Active to start with.
<br>      Params.EndUpdate( true );
<br>    end;
<br>  end;
<br>end;}
  procedure BeginUpdate;
{: This method causes dispatch of any waiting data change notifications that
were delayed by calling BeginUpdate for the purpose of letting all of the
intended processing to the data of the buffer be completed.

<br><br>
The SingleEventOnly parameter is used to consolidate multiple column change
notifications into a single general change notification event. If only a
single column was changed, this parameter will not make any difference.
<br><br>
If no columns were actually changed, no events will be dispatched.}
  procedure EndUpdate( SingleEventOnly: boolean );

{ Column access methods }

{: This is used to get the node that stores the extended blob information
and data.}
  function GetBlobNode( SQLNo: integer ): PIB_BlobNode;

{: Quiet way to get a reference to a column object. If a match is not found,
nil is assigned to the AIB_Field parameter instead of raising an exception.
<br><br>
It also returns True or False, indicating whether a matching column was found.}
  function GetByName(     AFieldName: string;
                      var AIB_Field: TIB_Column ): boolean;
{: Quiet way to get a reference to a column object. If a matching number is not
found, nil is assigned to the AColumn parameter instead of raising an exception.
<br><br>
AFieldName is the full or column name only of the column. Case-insensitive.
<br><br>
ASQLNo is the actual numerical order in the SQL statement.
<br><br>
It also returns True or False, indicating whether a matching number was found.}
  function GetBySQLNo(     ASQLNo: smallint;
                       var AColumn: TIB_Column ): boolean;
{: Function that returns a column object for the AFieldName specified.}
  function ByName( const AFieldName: string ): TIB_Column;
{: Function that returns a column object for the ASQLNo specified.}
  function BySQLNo( ASQLNo: smallint ): TIB_Column;
{: Function that returns a column object for the AFieldName specified.
<br><br>
This has been included for VCL compatibility.}
  function ParamByName( const AFieldName: string ): TIB_Column;

{ Properties }

{: List of array columns.}
  property ArrayList: TList read FArrayList;
{: List of blob columns.}
  property BlobList: TList read FBlobList;
{: Reference to the head of the Blob Cache for the column.}
  property PBlobHead: PPIB_BlobNode read GetPBlobHead;
{: List of calculated columns.}
  property CalcList: TList read FCalcList;
{ System Properties }
  property CursorName: string read FCursorName;
{: When a RefreshBuffer() call is made it has a parameter called NewRecord.
When a new record is selected a DataChange notification is generated. It is
useful to know whether a DataChange is due to a new record or just the same
record getting altered in some way.}
  property IsRowNumChanged: boolean read FIsRowNumChanged;
{: Reference to the statement that this Row belongs to.}
  property Statement: TIB_Statement read FStatement;
{: Indicates whether the row buffer is input type or an output type.}
  property RowType: TIB_RowType read FRowType;
{: Current state of the row buffer.}
  property RowState: TIB_RowState read FRowState;
{: This is a pointer to the IB API XSQLDA structure used to define the row
buffer.}
  property PSQLDA: PXSQLDA read FPSQLDA;
{: Number of columns that are array columns.}
  property ArrayCount: word read FArrayCount;
{: Number of columns that are blob columns.}
  property BlobCount: word read FBlobCount;
{: Number of columns that are defined by the CalculatedFields property.}
  property CalcCount: word read FCalcCount;
{: This property returns the number of columns that belong to this row.}
  property ColumnCount: word read FColumnCount;
{: This gives an array reference to the TIB_Column objects.}
  property Columns[ Index: word ]: TIB_Column read GetColumns; default;
{: This gives the number of bytes for the row buffer.}
  property BufferLength: word read FBufferLength;
{: Pointer reference to the OldBuffer's memory.}
  property OldBuffer: pointer read FOldBuffer write FOldBuffer;
{: Pointer reference to the RowBuffer's memory.}
  property RowBuffer: pointer read FRowBuffer write FRowBuffer;
{: String buffer containing the data for the row.}
  property RowData: string read GetRowData write SetRowData;
{: Reference to the node in the buffer if used in a buffered dataset.}
  property RowNode: PIB_Node read FRowNode;
{: String buffer containing the data for the original row before anything was
modified.}
  property OldRowData: string read GetOldRowData write SetOldRowData;
{: Number of relations involved in the row. It is usually only one unless
the dataset is a JOIN across two or more tables.}
  property RelationCount: word read GetRelationCount;
{: Access to the names of the relations involved in the row buffer.}
  property RelationNames[ Index: word ]: string read GetRelationName;
{: System property.}
  property UpdatedColumnCount: word read GetUpdatedColumnCount;
{: This is a raw TList containing object references to TIB_Column instances
which were changed. This is useful for a OnDataChange event when nil is passed
in the Field because more than one column was changed. By referencing this
property it is possible to determine which all fields were changed. If the
count is 0 then this is a generic record change notification meaning the whole
record has changed.}
  property UpdatedColumns: TList read FUpdatedColumns;
{: System property. In an OnDataChange event notification you can determine if
during the time event notifications are cached whether or not there was an
event indicating that the whole row of data had changed.}
  property UpdatedWholeRow: boolean read GetUpdatedWholeRow;
{: This property returns or accepts a variant representation of the column
passed to it.}
  property Values[const ColumnName: string]: variant
      read GetColumnValue
     write SetColumnValue;
{: This property gives a variant representation of the old value of the column
passed to it.}
  property OldValues[const ColumnName: string]: variant
      read GetColumnOldValue;
{: This property is for VCL emulation.
<br><br>
It property returns or accepts a variant representation of the column
passed to it.}
  property ParamValues[const ColumnName: string]: variant
      read GetColumnValue
     write SetColumnValue;
{: This is a system maintained property. Do not change its value.}
  property IsKeyFields: boolean read FIsKeyFields write FIsKeyFields;
{: System property.}
  property IsKeyFieldsWhole: boolean read FIsKeyFieldsWhole
                                     write FIsKeyFieldsWhole;
{: This is a system maintained property. Do not change its value.}
  property IsBufferFields: boolean read FIsBufferFields write FIsBufferFields;

{ Events }

{: This event is triggered before a change is about to be applied to the
row buffer.
<br><br>
This is public for internal reasons only. Please do not set this unless you
know what you are doing.}
  property BeforeModify: TIB_RowChangedEvent read FBeforeModify
                                             write FBeforeModify;
{: This event is fired just after a change was applied to the row buffer.
<br><br>
This is public for internal reasons only. Please do not set this unless you
know what you are doing.}
  property AfterModify: TIB_RowChangedEvent read FAfterModify
                                            write FAfterModify;
{: Upon a change of state on this row buffer, this event is triggered.
<br><br>
This is public for internal reasons only . Please do not set this unless you
know what you are doing.}
  property OnRowStateChanged: TIB_RowEvent read FOnRowStateChanged
                                           write FOnRowStateChanged;
end;

// IBA_Row.IMP

{                                                                              }
{ TIB_Column                                                                   }
{                                                                              }

{: Exception type for column exceptions.}
EIB_ColumnError = class( EIB_Error );
{: Event type used with the TIB_Column object. }
TIB_ColumnEvent = procedure( Sender: TIB_Column ) of object;
{: Event type used with the TIB_Column object. }
TIB_ColumnEventAs = procedure(     Sender: TIB_Column;
                               var AValue: string ) of object;
{: The character case that should be used when accessing this column through a
data bound control. }
TIB_CharCase = ( ccNormal, ccUpper, ccLower, ccProper );
{: This object is like the VCL TField and TParam combined. It serves as both
the input and output column specifier.
<br><br>
Design-time fields are not possible with the native IBO components so it is
necessary to do one of two things if you need static references to a column or
parameter:
<ul>
<li>1) Use the FieldByName( ) method, Fields[ ] array;  or
<li>2) At prepare time assign STATIC variables as references to the
field objects that are defined by storing the results of the FieldByName()
method. Be sure to refresh the list each time a prepare is done too.
</ul>
<br><br>
Blob contents can be accessed using the AsString property, Assign() and
AssignTo() methods.  You can also use blob streams directly with the
CreateBlobStream() method of the TIB_Statement class.  Casting the reference
to a TIB_ColumnBlob is unnecessary, unless you need something that is made
accessible specifically only at that level of sub-classing.}
TIB_Column = class(TObject)
private
  FRow: TIB_Row;
  FFieldNo: smallint;
  FPXSQLVAR: PXSQLVAR;
  FOldColumnInd: ^smallint;
  FNewColumnInd: ^smallint;
  FOldColumnBuffer: pointer;
  FNewColumnBuffer: pointer;
  FDomainName: string;
  FNotInForUpdate: boolean;
  FReadOnly: boolean;
  FForceControlsReadOnly: boolean;
  FAlignment: TAlignment;
  FCharCase: TIB_CharCase;
  FEditMask: string;
  FMaskIntf: IIB_MaskProcessor;
  FDisplayFormat: string;
  FDisplayLabel: string;
  FGridDisplayLabel: string;
  FGridTitleHint: string;
  FDisplayWidth: integer;
  FTrimming: TIB_ColumnTrimming;
  FVisible: boolean;
  FBooleanTrue: string;
  FBooleanFalse: string;
  FIsBoolean: boolean;
  FIsText: boolean;
  FIsNumeric: boolean;
  FIsDateTime: boolean;
  FPadChar: char;
  FDateOnlyFmt: string;
  FTimeOnlyFmt: string;
  FIsBlob: boolean;
  FIsArray: boolean;
  FIsCurrency: boolean;
  FPreventEditing: boolean;
  FPreventInserting: boolean;
  FPreventSearching: boolean;
  FBDEFieldName: string;
  FDBFFieldName: string;
  FNoCaseFieldName: string;
  FBlankIsNull: boolean;
  FValidating: boolean;
  FValidateBuffer: pointer;
  FPreserveBuffer: pointer;
  FOrderingLinkItemNo: integer;
  FIsCurrencyDataType: boolean;
  FDefaultValue: string;
  FBound: boolean;
  FOnBeforeModify: TIB_ColumnEvent;
  FOnAfterModify: TIB_ColumnEvent;
  FOnValidate: TIB_ColumnEvent;
  FOnGetText: TIB_ColumnEventAs;
  FOnSetText: TIB_ColumnEventAs;
  FSetText: TIB_ColumnEventAs;
  FSetTextCol: TIB_Column;
  FRelAliasName: string;
  FColInfoValid: boolean;
  FIsPrimary: boolean;
  FIsAlternate: boolean;
  FIsForeign: boolean;
  FIsDefaulted: boolean;
  FIsIndexField: boolean;
  FDefaultSource: string;
{ Property access methods }
  function GetBDEFieldName: string;
  function GetDBFFieldName: string;
  function GetDisplayLabel: string;
  function GetGridDisplayLabel: string;
  function GetIsDateOnly: boolean;
  function GetIsTimeOnly: boolean;
  function GetBooleanTrue: string;
  function GetBooleanFalse: string;
  function GetDataSize: word;
  function GetSQLName: string;
  function GetRelName: string;
  function GetRelAliasName: string;
  function GetOwnerName: string;
  function GetFieldName: string;
  function GetFullFieldName: string;
  function GetFullSQLName: string;
  function GetBestFieldName: string;
  function GetDomainName: string;
  function GetSQLType: smallint;
  function GetSQLScale: smallint;
  function GetSQLSubType: smallint;
  function GetSQLLen: smallint;
  function GetOldIsNull: boolean;
  function GetOldIsNotNull: boolean;
  procedure SetIsNull( const NewValue: boolean );
  function GetComputed: boolean;
  function GetIsNotNull: boolean;
  function GetIsNull: boolean;
  function GetIsNullable: boolean;
  function GetIsPrimary: boolean;
  function GetIsForeign: boolean;
  function GetIsDefaulted: boolean;
  function GetIsIndexField: boolean;
  function GetIsAlternate: boolean;
  function GetStatement: TIB_Statement;
  function GetIndex: integer;
  procedure SetIndex( AValue: integer );
  function GetColData: string;
  procedure SetColData( const AValue: string );
  function GetOldColData: string;
  procedure SetOldColData( const AValue: string );
  procedure SetBooleanTrue( const AValue: string );
  procedure SetBooleanFalse( const AValue: string );
  procedure SetPreventEditing( AValue: boolean );
  procedure SetPreventInserting( AValue: boolean );
  procedure SetPreventSearching( AValue: boolean );
  procedure SetForceControlsReadOnly( AValue: boolean );
  function GetOrderingLinkItemNo: integer;
  procedure FreeMaskIntf;
  function CalcDefaultWidth( IsGrid: boolean ): integer;
  procedure SysApplyTrimming( var AValue: string );
  procedure CheckInfoValid;
protected
{ Property access methods }
  function GetBlobSize: longint; virtual;
  function GetFieldSource( DomainInfo: boolean ): string; //~virtual;
  function GetReadOnly: boolean; //~virtual;
  function GetControlsReadOnly: boolean; //~virtual;
  function GetIsLoaded: boolean; virtual;
  function GetIsModified: boolean; virtual;
  function GetIsCalculated: boolean; //~virtual;
  function GetRequired: boolean; //~virtual;
  function GetOldAsString: string; virtual;
  function GetOldAsVariant: variant; virtual;
  function GetAsString: string; virtual; abstract;
  function GetAsRawString: string; virtual; 
  function GetAsXML: string; virtual;
{$IFDEF IBO_VCL30_OR_GREATER}
  function GetAsWideString: widestring; virtual;
{$ENDIF}
  function GetAsDate: TDateTime; virtual;
  function GetAsDateTime: TDateTime; virtual;
  function GetAsDateTimeEncodeString: string; virtual;
  function GetAsBoolean: boolean; //~virtual;
  function GetAsInteger: integer; virtual;
  function GetAsInt64: ISC_INT64; //~virtual;
  function GetAsFloat: double; virtual;
  function GetAsSmallint: smallint; virtual;
  function GetAsDouble: double; virtual;
  function GetAsCurrency: currency; virtual;
{$IFDEF IBO_ASCOMP_SUPPORT}
  function GetAsComp: comp; virtual;
{$ENDIF}
  function GetAsExtended: extended; virtual;
  function GetAsVariant: variant; virtual;
{ Property Manipulation Methods }
  procedure SetAsString( const NewValue: string ); virtual; abstract;
  procedure SetAsRawString( const NewValue: string ); virtual; 
{$IFDEF IBO_VCL30_OR_GREATER}
  procedure SetAsWideString( const NewValue: widestring ); virtual;
{$ENDIF}
  procedure SetAsDate( const NewValue: TDateTime ); virtual;
  procedure SetAsDateTime( const NewValue: TDateTime ); virtual;
  procedure SetAsDateTimeEncodeString( const NewValue: string ); virtual;
  procedure SetAsBoolean( const NewValue: boolean ); //~virtual;
  procedure SetAsInteger( const NewValue: integer ); virtual;
  procedure SetAsInt64( const NewValue: ISC_INT64 ); //~virtual;
  procedure SetAsFloat( const NewValue: double ); virtual;
  procedure SetAsSmallint( const NewValue: smallint ); virtual;
  procedure SetAsDouble( const NewValue: double ); virtual;
  procedure SetAsCurrency( const NewValue: currency ); virtual;
{$IFDEF IBO_ASCOMP_SUPPORT}
  procedure SetAsComp( const NewValue: comp ); virtual;
{$ENDIF}
  procedure SetAsExtended( const NewValue: extended); virtual;
  procedure SetAsVariant( const NewValue: variant ); virtual;
  function GetSQLTypeSource( const BaseTypeOnly: boolean ): string; virtual;
  function GetDisplayName: string; //~virtual;
  function GetGridDisplayName: string; //~virtual;
  function GetDisplayText: string; virtual;
  function GetDefaultWidth: integer; virtual;
  function GetDisplayWidth: integer; //~virtual;
{ Attribute Manipulation Methods }
  function GetAttributeParams( const AParam: string ): string;
  procedure SetAttributeParams( const AParam: string; AValue: string );
  function GetIsAttributeSet( const AParam: string ): boolean;
  procedure SetIsAttributeSet( const AParam: string; AValue: boolean );
{ Property coordination methods }
  procedure SetAlignment( AValue: TAlignment ); //~virtual;
  procedure SetCharCase( AValue: TIB_CharCase ); //~virtual;
  function GetDisplayFormat: string; //~virtual;
  procedure SetDisplayFormat( const AValue: string ); //~virtual;
  function GetEditMask: string;
  procedure SetEditMask( const AValue: string ); //~virtual;
  function IsMasked: boolean; //~virtual;
  function EmptyMaskText: string; //~virtual;
  function FormatTextWithMask( const SourceStr: string ): string; //~virtual;
  procedure SetDisplayLabel( const AValue: string ); //~virtual;
  procedure SetGridDisplayLabel( const AValue: string ); //~virtual;
  procedure SetGridTitleHint( const AValue: string ); //~virtual;
  procedure SetDisplayWidth( AValue: integer ); //~virtual;
  procedure SetTrimming( AValue: TIB_ColumnTrimming ); //~virtual;
  procedure SetVisible( AValue: boolean ); //~virtual;
{ System methods }
  procedure SysBeforeModify; //~virtual;
  procedure SysAfterModify; //~virtual;
  function SysInternalChanged: boolean; virtual;
  procedure SysSetIsNull( const NewValue: boolean ); virtual;
  procedure SysLayoutChanged; virtual;
{ Event dispatch methods }
  procedure DoBeforeModify; //~virtual;
  procedure DoAfterModify; //~virtual;
public
  constructor Create( ARow: TIB_Row;
                      PSQLVAR: PXSQLVAR;
                      AFieldNo: smallint ); virtual;
  destructor Destroy; override;
{ Methods }

  property BlankIsNull: boolean read FBlankIsNull write FBlankIsNull;
{: }
  property BlobSize: longint read GetBlobSize;
{: }
  procedure LoadFromFile( const AFileName: string ); dynamic;
{: }
  procedure SaveToFile( const AFileName: string ); dynamic;

{: This function is used by data-aware controls to determine if a key press is
valid for the column to which it is bound.}
  function IsValidChar( AChar: Char ): boolean; //~virtual;
{: This function sets a column to NULL.}
  procedure Clear; virtual;
{: This function scans through all data bound controls and tries to bring
to a focused state one which is bound to this column.
<br><br>
It is used in the CheckRequiredFields after data entry, to bring the user's
focus directly, if possible, to a Required field that has been left out.}
  procedure FocusControl;
{: Assigns the data contents of the column or other data member passed in.
<br><br>
All necessary data conversions are performed or an exception is raised.
<br><br>
This works very well when moving BLOB data to and from TStrings, TStream,
TBitmap, TPicture, etc.
<br><br>
It does not support passing in a VCL TField.}
  procedure Assign( Source: TObject ); virtual;
{: Assigns the column's data contents to another data member passed in.
<br><br>
All necessary data conversions are performed or an exception is raised.
<br><br>
This works very well when moving BLOB data to and from TStrings, TStream,
TBitmap, TPicture, etc.
<br><br>
It does not support passing in a VCL TField.}
  procedure AssignTo( Dest: TObject ); virtual;
{: This method is for improving accuracy when working with the ColumnAttributes
settings.}
  function GetAttributeParamsEx( const AParam: string;
                                   var IsSet: boolean ): string;
{: Method to cause this column to go back to its original value.}
  procedure Revert; //~virtual;
{: Method to transfer data from a raw buffer to the contents of the column.}
  procedure SetBlobData( ABuffer: Pointer; ASize: integer ); virtual;

{ Properties }

{: This is used for input parameters and master-detail to allow a parameter to
be explicitely bound rather than always follow a link that may exist in a
master-detail related dataset.}
  property Bound: boolean read FBound write FBound;
{: This is the FieldName that the BDE will use.}
  property BDEFieldName: string read GetBDEFieldName;
{: This is the FieldName that a DBF file will use.}
  property DBFFieldName: string read GetDBFFieldName;
{: This property indicates the name of the column in the table that is the
uppercase equivalent of this column. It is set by making an entry in the
ColumnAttributes property of the connection or dataset.}
  property NoCaseFieldName: string read FNoCaseFieldName;
{: Reference to the statement to which this object belongs.}
  property Statement: TIB_Statement read GetStatement;
{: Reference to the row to which this object belongs.

The row determines whether it is an input (Params) or output (Fields) row to
which the column objects belong.}
  property Row: TIB_Row read FRow;
{: Size of the memory block used to store the contents of this column. The
storage for the NULL indicator is included.
<br><br>
For BLOB and ARRAY columns this only indicates the size of storage for the
8-byte ID.}
  property DataSize: word read GetDataSize;
{: The numeric position that the column occupies in the row buffer, starting at
0 in the first position.  It represents the native SQL ordering of the
column buffer within the row buffer.}
  property FieldNo: smallint read FFieldNo;
{: Pointer to the SQLVAR SQL column structure used by the IB API.}
  property PSQLVAR: PXSQLVAR read FPXSQLVAR;
{: Name of the column as it maps to the table.}
  property SQLName: string read GetSQLName;
{: Name of the table to which the column belongs.}
  property RelName: string read GetRelName;
{: Name of the Relation Alias to which the column belongs.}
  property RelAliasName: string read GetRelAliasName;
{: Name of the owner of the resource in the database.}
  property OwnerName: string read GetOwnerName;
{: Name of column as defined in the SELECT statement.
<br><br>
Otherwise known as the Alias. }
  property FieldName: string read GetFieldName;
{: Return the source code DDL from a CREATE TABLE statement that made this
column.}
  property FieldSource[ DomainInfo: boolean ]: string read GetFieldSource;   
{: A fully qualified column name that includes the name of the table or its
alias and a period with the name or alias name of the column.}
  property FullFieldName: string read GetFullFieldName;
{: A fully qualified column name that includes the name of the actual table and
not the relation alias name if there is one defined.  It also does not include
the alias name of the column if one is used.}
  property FullSQLName: string read GetFullSQLName;
{: A fully qualified column name that omits the table prefix if that can be done
and still distinguish it from other columns in the dataset.
<br>
This is mostly for the purpose of simple display of column names and more
efficient processing if the relation name is not required to identify a
column.}
  property BestFieldName: string read GetBestFieldName;
{: Name of the InterBase domain to which this column belongs.
<br><br>
Use of this property will invoke a performance hit since it will need to
query the database metadata.}
  property DomainName: string read GetDomainName;
{: This property returns the text which serves as a display string
for this column in the application. If there is no DisplayFormat or
EditFormat then AsString is used.}
  property DisplayText: string read GetDisplayText;
{: This property stores the text which serves as a standard display string
to accompany this column as a label in the application.
<br><br>
If DisplayName is left blank then the column's field name will be used.}
  property DisplayName: string read GetDisplayName;
{: This property stores the text which serves as to override the DisplayName
when displaying the column title of a grid.
<br><br>
If GridDisplayName is left blank then DisplayName or the column's field name
will be used.}
  property GridDisplayName: string read GetGridDisplayName;
{: This is a default width of a column in pixels to be used by data bound
controls.}
  property DefaultWidth: integer read GetDefaultWidth;
{: }
  property IsCurrencyDataType: boolean read FIsCurrencyDataType;
{: Set the NOEDIT param in the FieldsReadOnly property to configure this value
at design-time.}
  property PreventEditing: boolean read FPreventEditing
                                   write SetPreventEditing;
{: Set the NOINSERT param in the FieldsReadOnly property to configure this value
at design-time.}
  property PreventInserting: boolean read FPreventInserting
                                     write SetPreventInserting;
{: Set the NOSEARCH param in the FieldsReadOnly property to configure this value
at design-time.}
  property PreventSearching: boolean read FPreventSearching
                                     write SetPreventSearching;
{: This property indicates whether controls associated with this column will
allow user manipulation.}
  property ControlsReadOnly: boolean read GetControlsReadOnly;
{: This property is to be used at runtime to make a column read-only for a
special case that may be temporary. Setting it to True does not alter a column
that is already read-only. It will just force it to be read-only if some other
setting or factor has not made it read-only.}
  property ForceControlsReadOnly: boolean read FForceControlsReadOnly
                                          write SetForceControlsReadOnly;
{: This property indicates the character that is used to pad the white space of
the column. This applies when it is null and when a varchar is shorter than the
declared potential length.}
  property PadChar: char read FPadChar;
{: This returns the SQL code that will provide the data type of the column when
it was created in a CREATE TABLE statement.}
  property SQLTypeSource[ const BaseTypeOnly: boolean ]: string
    read GetSQLTypeSource;
{: Native InterBase SQLType code for this column.}
  property SQLType: smallint read GetSQLtype;
{: The number of digits of precision for numerical storage.}
  property SQLScale: smallint read GetSQLscale;
{: The BLOB type is indicated with this property.
<ul>
<li> >1 - System defined
<li>  1 - Text
<li>  0 - Binary
<li> <0 - User-defined
</ul>}
  property SQLSubType: smallint read GetSQLsubtype;
{: The number of characters used in the data portion of the column buffer.
<br><br>
For VARCHAR fields this does not include the two-byte length indicator which
is located in the beginning of the column buffer.}
  property SQLLen: smallint read GetSQLLen;
{: Returns True if this column's content has been loaded.
<br><br>
This really only applies to BLOB and ARRAY columns.}
  property IsLoaded: boolean read GetIsLoaded;
{: Returns True if this column has been modified.}
  property IsModified: boolean read GetIsModified;
{: Returns True if this column's value is obtained from a calculation.}
  property IsCalculated: boolean read GetIsCalculated;
{: Returns True if this column has a server defined default value.}
  property IsDefaulted: boolean read GetIsDefaulted;
{: Returns True if the CURRENCY column attribute was set.}
  property IsCurrency: boolean read FIsCurrency;
{: Returns True if this column must provide a value to the server, i.e. is
defined in the database as NOT NULL.
<br><br>
Set a ColumnAttribute setting of NOTREQUIRED to flag a column so that it will
be ignored by the CheckRequiredFields method.  It is also possible to enforce a
user entry in a nullable column by setting it to REQUIRED in the
ColumnAttributes.}
  property Required: boolean read GetRequired;
{: Returns True if this column can contain a NULL on the server.}
  property IsNullable: boolean read GetIsNullable;
{: Returns True if this column is used in a table's primary key.}
  property IsPrimary: boolean read GetIsPrimary;
{: Returns True if this column is used in a table's foreign key.}
  property IsForeign: boolean read GetIsForeign;
{: Work in progress}
  property IsIndexField: boolean read GetIsIndexField;
{: Work in progress}
  property IsAlternate: boolean read GetIsAlternate;
{: Run-time only. This a conversion property.
<br><br>
AsString can be used to read or set the value of the column as a string.
<br><br>
It will even work with the contents of BLOB columns.
<br><br>
All trimming rules will be honored when reading and writing to this property.
If you want to get or set the raw string value of the column please use the
AsRawString property.}
  property AsString: string read GetAsString write SetAsString;
{: This will get and set the value of the column exactly as it is directly in
the buffers, without any trimming rules or OnGetText/OnSetText events applied.}
  property AsRawString: string read GetAsRawString write SetAsRawString;
{$IFDEF IBO_VCL30_OR_GREATER}
{: This is for use with Unicode strings.}
  property AsWideString: widestring read GetAsWideString write SetAsWideString;
{$ENDIF}
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsDate: TDateTime read GetAsDate write SetAsDate;
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsDateTime: TDateTime read GetAsDateTime write SetAsDateTime;
{: Conversion property. An exception will be raised if a conversion cannot
be performed.  See IB_Utils.pas functions DateTimeToEncodeString and
EncodeStringToDateTime for details of the string value expected by this
property.}
  property AsDateTimeEncodeString: string read GetAsDateTimeEncodeString
                                          write SetAsDateTimeEncodeString;
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsBoolean: boolean read GetAsBoolean write SetAsBoolean;
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsInteger: integer read GetAsInteger write SetAsInteger;
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsInt64: ISC_INT64 read GetAsInt64 write SetAsInt64;
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsSmallint: smallint read GetAsSmallint write SetAsSmallint;
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsFloat: double read GetAsFloat write SetAsFloat;
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsDouble: double read GetAsDouble write SetAsDouble;
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsCurrency: currency read GetAsCurrency write SetAsCurrency;
{$IFDEF IBO_ASCOMP_SUPPORT}
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsComp: comp read GetAscomp write SetAscomp;
{$ENDIF}
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsExtended: extended read GetAsExtended write SetAsExtended;
{: Conversion property. An exception will be raised if a conversion cannot
be performed.}
  property AsVariant: variant read GetAsVariant write SetAsVariant;
{: This property returns the text for a cell of XML for the column's value.}
  property AsXML: string read GetAsXML;
{: Returns the boolean inverted NULL status of the column.}
  property IsNotNull: boolean read GetIsNotNull;
{: Returns the NULL status of the column.}
  property IsNull: boolean read GetIsNull write SetIsNull;
{: Returns the original value of a column if it has changed. Otherwise, it
returns the current value.}
  property OldAsString: string read GetOldAsString;
{: Returns the original value of a column if it has changed. Otherwise, it
returns the current value.}
  property OldAsVariant: variant read GetOldAsVariant;
{: Returns whether or not the column was originally NULL.}
  property OldIsNull: boolean read GetOldIsNull;
{: Returns whether or not the column was originally NOT NULL.}
  property OldIsNotNull: boolean read GetOldIsNotNull;
{: This property is controlled by the BOOLEAN attribute in the
ColumnAttributes property of the connection or dataset.}
  property BooleanTrue: string read GetBooleanTrue write SetBooleanTrue;
{: This property is controlled by the BOOLEAN attribute in the
ColumnAttributes property of the connection or dataset.}
  property BooleanFalse: string read GetBooleanFalse write SetBooleanFalse;
{: This column is used to indicate whether the underlying column in a table is
defined with a COMPUTED BY expression. This makes it become a calculated column,
even though it appears to be derived from a valid table column.}
  property Computed: boolean read GetComputed;
{: Flag to quickly check whether the column is an ARRAY column.}
  property IsArray: boolean read FIsArray;
{: Flag to quickly check whether the column is a BLOB column.}
  property IsBlob: boolean read FIsBlob;
{: Flag to quickly check whether the column is defined as a boolean.}
  property IsBoolean: boolean read FIsBoolean;
{: Flag to quickly check whether the column is date-time based.}
  property IsDateTime: boolean read FIsDateTime;
{: Flag to quickly check whether the column is date-only based.}
  property IsDateOnly: boolean read GetIsDateOnly;
{: Flag to quickly check to whether the column is time-only.}
  property IsTimeOnly: boolean read GetIsTimeOnly;
{: Flag to quickly check whether the column is based on a numeric type.}
  property IsNumeric: boolean read FIsNumeric;
{: Flag to quickly check whether the column is based on a text type.}
  property IsText: boolean read FIsText;
{: Provides information about whether this column can be modified.}
  property ReadOnly: boolean read GetReadOnly;
{: A particular member of the user-defined column properties can be read and
modified via this property.}
  property IsAttributeSet[ const Index: string ]: boolean
      read GetIsAttributeSet
     write SetIsAttributeSet;
{: User-defined column properties can be stored via this property.}
  property AttributeParams[ const Index: string ]: string
      read GetAttributeParams
     write SetAttributeParams;
{: Direct binary based string of the column's buffer.
<br><br>
No notification is given when the buffer is changed.  Changes will be detected at
the time of posting.}
  property ColData: string read GetColData write SetColData;
{: Direct binary based string of the column's old value buffer. }
  property OldColData: string read GetOldColData write SetOldColData;
{: BDE compatibility property.
<br><br>
This property is also very handy with ARRAY columns since the results are
bundled up into a variant array.}
  property Value: variant read GetAsVariant write SetAsVariant;
{: This provides the value of the OrderingItemNo from the OrderingLinks property
that would take effect if this column were used to initiate the Ordering.  This
value would be available, for example, if the column's title in a grid is
clicked while IndicateOrdering is set to True and OrderingLinks are present for
this column.}
  property OrderingLinkItemNo: integer read GetOrderingLinkItemNo;
published
{ Properties }

{: Used to align the data in the TIB_Grid and other data bound controls.}
  property Alignment: TAlignment read FAlignment write SetAlignment;
{: Used by databound controls to provide the case expected for this column.}
  property CharCase: TIB_CharCase read FCharCase write SetCharCase;
{: Used by the DisplayText property to display data in a more appropriate format
in bound controls when the dataset is not in editing mode.}
  property DisplayFormat: string read FDisplayFormat write SetDisplayFormat;
{: Text used as a display label for this column. }
  property DisplayLabel: string read GetDisplayLabel write SetDisplayLabel;
{: Text used as a display label for this column in a grid instead of
DisplayLabel.}
  property GridDisplayLabel: string read GetGridDisplayLabel
                                    write SetGridDisplayLabel;
{: Text used as a hint for the title header in a grid for this column. }
  property GridTitleHint: string read FGridTitleHint
                                 write SetGridTitleHint;
{: Width in pixels for this column when displayed in an IB_Grid column.}
  property DisplayWidth: integer read GetDisplayWidth write SetDisplayWidth;
{: Used in masked data entry controls for cleaner data entry.}
  property EditMask: string read GetEditMask write SetEditMask;
{: Numeric reference to the position of the TIB_Column in the TList of columns.
<br><br>
This is used by the TIB_Grid to determine the left-to-right display order of the
columns.
<br><br>
Do not confuse this property with the FieldNo property , which represents the
native SQL ordering of the column buffer within the row buffer.}
  property Index: integer read GetIndex write SetIndex;
{: This property controls how CHAR and VARCHAR columns are trimmed and gives a
few extra nice options.<ul>
<li>NONE (ctNone) emulates the VCL behavior and leaves the string exactly as it
comes over from the server
<li>ALL (ctAll) trims leading and trailing blank characters (#32) and also
removes any that occur within the string.  Use with caution!  blanks are
sometimes used by developers to overrule the standard ASCII sequencing of number
strings.  This setting will defeat such a system.
<li>BOTH (ctBoth) trims leading and trailing blank characters.
<li>LEFT  (ctLeft) trims leading blanks only.
<li>RIGHT (ctRight) trims trailing blanks only.
<li>SENTENCE (ctSentence) trims leading and trailing blanks.  It also searches
the string for occurrences of two or more contiguous blank spaces and reduces
the size of these whitespace sequences to one blank space.
</ul>
<br><br>
Preface the entry with a C and it will also check the length of the string.
.}
  property Trimming: TIB_ColumnTrimming read FTrimming write SetTrimming;
{: Used by the TIB_Grid to determine whether it should display this column.  A
column will always be visible unless its VISIBLE attribute is set False
explicitly.}
  property Visible: boolean read FVisible write SetVisible;

{ Events }

{: Event triggered immediately after a column has been modified.}
  property OnAfterModify: TIB_ColumnEvent read FOnAfterModify
                                          write FOnAfterModify;
{: Event triggered immediately before a column is modified.}
  property OnBeforeModify: TIB_ColumnEvent read FOnBeforeModify
                                           write FOnBeforeModify;
{: This event is used in conjunction with the AsString property. It is
triggered any time the column is referenced with AsString.}
  property OnGetText: TIB_ColumnEventAs read FOnGetText write FOnGetText;
{:This event is used in conjunction with the AsString property. It is
triggered any time something is assigned to the column using AsString.}
  property OnSetText: TIB_ColumnEventAs read FOnSetText write FOnSetText;
{: Event to validate the new value written to the field.
<br><br>
If the new value is invalid then raise an exception to cause it to be rejected.}
  property OnValidate: TIB_ColumnEvent read FOnValidate write FOnValidate;

end;

{: Column type sub-class.}
TIB_ColumnNumBase = class( TIB_Column )
private
  FCheckMinVal,
  FCheckMaxVal: boolean;
  FMinVal,
  FMaxVal: double;
protected
  procedure SysLayoutChanged; override;
public
  property CheckMinVal: boolean read FCheckMinVal;
  property CheckMaxVal: boolean read FCheckMaxVal;
  property MinVal: double read FMinVal;
  property MaxVal: double read FMaxVal;
  constructor Create( ARow: TIB_Row;
                      PSQLVAR: PXSQLVAR;
                      AFieldNo: smallint ); override;
end;

{: Column type sub-class.}
TIB_ColumnNumeric = class( TIB_ColumnNumBase )
private
  FNoRoundErr: boolean;
  FFmtStr: string;
protected
  function GetAsCurrency: currency; override;
  procedure SetAsCurrency( const NewValue: currency ); override;
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string ); override;
  function GetAsExtended: extended; override;
  procedure SetAsExtended( const NewValue: extended ); override;
  function GetValue: extended; //~virtual;
  procedure SetValue( const NewValue: extended ); //~virtual;
  function GetAsInteger: integer; override; 
  procedure SetAsInteger( const NewValue: integer ); override;
public
  constructor Create( ARow: TIB_Row;
                      PSQLVAR: PXSQLVAR;
                      AFieldNo: smallint ); override;
  property Value: extended read GetValue write SetValue;
  property NoRoundErr: boolean read FNoRoundErr;
end;

{: Column type sub-class.}
TIB_ColumnFloat = class( TIB_ColumnNumBase )
protected
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string); override;
  function GetAsFloat: double; override;
  procedure SetAsFloat( const NewValue: double); override;
  function GetValue: single; //~virtual;
  procedure SetValue( const NewValue: single ); //~virtual;
public
  property Value: single read GetValue write SetValue;
end;

{: Column type sub-class.}
TIB_ColumnDouble = class( TIB_ColumnNumBase )
protected
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string); override;
  function GetAsDouble: Double; override;
  procedure SetAsDouble( const NewValue: Double); override;
  function GetValue: double; //~virtual;
  procedure SetValue( const NewValue: double ); //~virtual;
public
  property Value: double read GetValue write SetValue;
end;

{: Column type sub-class.}
TIB_ColumnBoolean = class( TIB_ColumnNumBase )
protected
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string ); override;
  function GetValue: boolean; 
  procedure SetValue( const NewValue: boolean );
public
  property Value: boolean read GetValue write SetValue;
end;

{: Column type sub-class.}
TIB_ColumnSmallInt = class( TIB_ColumnNumBase )
protected
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string ); override;
  function GetAsSmallint: smallint; override;
  procedure SetAsSmallint( const NewValue: smallint); override;
  function GetValue: smallint; //~virtual;
  procedure SetValue( const NewValue: smallint ); //~virtual;
public
  property Value: smallint read GetValue write SetValue;
end;

{: Column type sub-class.}
TIB_ColumnInteger = class( TIB_ColumnNumBase )
protected
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string ); override;
  function GetAsInteger: integer; override;
  procedure SetAsInteger( const NewValue: integer ); override;
  function GetValue: integer; //~virtual;
  procedure SetValue( const NewValue: integer ); //~virtual;
public
  property Value: integer read GetValue write SetValue;
end;

{: Column type sub-class.}
TIB_ColumnInt64 = class( TIB_ColumnNumBase )
protected
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string ); override;
  function GetAsInteger: integer; override;
  procedure SetAsInteger( const NewValue: integer ); override;
  function GetValue: ISC_INT64; //~virtual;
  procedure SetValue( const NewValue: ISC_INT64 ); //~virtual;
public
  property Value: ISC_INT64 read GetValue write SetValue;
end;

{: Column type sub-class.}
TIB_ColumnQuad = class( TIB_ColumnNumBase )
protected
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string ); override;
  function GetAsInteger: integer; override;
  procedure SetAsInteger( const NewValue: integer ); override;
  function GetValue: ISC_INT64; //~virtual;
  procedure SetValue( const NewValue: ISC_INT64 ); //~virtual;
public
  property Value: ISC_INT64 read GetValue write SetValue;
end;

{: Column type sub-class.}
TIB_ColumnDateTime = class( TIB_Column )
protected
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string ); override;
  function GetAsDate: TDateTime; override;
  procedure SetAsDate( const NewValue: TDateTime ); override;
  function GetAsDateTime: TDateTime; override;
  procedure SetAsDateTime( const NewValue: TDateTime ); override;
  function GetAsDateTimeEncodeString: string; override;
  procedure SetAsDateTimeEncodeString( const NewValue: string ); override;
  function GetValue: TDateTime; //~virtual;
  procedure SetValue( const NewValue: TDateTime ); //~virtual;
public
  constructor Create( ARow: TIB_Row;
                      PSQLVAR: PXSQLVAR;
                      AFieldNo: smallint ); override;
  property Value: TDateTime read GetValue write SetValue;
end;

{: Column type sub-class.}
TIB_ColumnDate = class( TIB_ColumnDateTime );
{: Column type sub-class.}
TIB_ColumnTime = class( TIB_ColumnDateTime );
{: Column type sub-class.}
TIB_ColumnTimeStamp = class( TIB_ColumnDateTime );

{: Column type sub-class.}
TIB_ColumnText = class( TIB_Column )
protected
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string ); override;
{$IFDEF IBO_VCL30_OR_GREATER}
  function GetAsWideString: widestring; override;
  procedure SetAsWideString( const NewValue: widestring ); override;
{$ENDIF}
  function GetValue: string; virtual;
  procedure SetValue( const NewValue: string ); virtual;
public
  constructor Create( ARow: TIB_Row;
                      PSQLVAR: PXSQLVAR;
                      AFieldNo: smallint ); override;
  property Value: string read GetValue write SetValue;
end;

{: Column type sub-class.}
TIB_ColumnVarText = class( TIB_ColumnText )
protected
{$IFDEF IBO_VCL30_OR_GREATER}
  function GetAsWideString: widestring; override;
  procedure SetAsWideString( const NewValue: widestring ); override;
{$ENDIF}
  function GetValue: string; override;
  procedure SetValue( const NewValue: string ); override;
end;

{: Column type sub-class.}
TIB_ColumnDB_KEY = class( TIB_Column )
protected
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string ); override;
  function GetDefaultWidth: integer; override;
  function GetValue: string; //~virtual;
  procedure SetValue( const NewValue: string ); //~virtual;
public
  constructor Create( ARow: TIB_Row;
                      PSQLVAR: PXSQLVAR;
                      AFieldNo: smallint ); override;
  property Value: string read GetValue write SetValue;
end;

// IBA_ColumnArray.INT
// IBA_ColumnArray.IMP
// IBA_ColumnBlob.INT
// IBA_ColumnBlob.IMP
// IBA_Column.IMP

{                                                                              }
{ TIB_ColumnBlob                                                               }
{                                                                              }

// I had to grab this out of DBTables.PAS from the VCL.
// I modified it so that there wouldn't be a conflict.
TIB_GraphicHeader = record
  Count: word;             { Fixed at 1 }
  HType: word;             { Fixed at $0100 }
  Size:  longint;          { Size not including header }
end;

{: This class is used to handle BLOB column types.
<br><br>
See TIB_Statement.CreateBlobStream() method for more information.}
TIB_ColumnBlob = class(TIB_Column)
private
{ General storage }
  FBlobNode: PIB_BlobNode;
  function GetLoadedBlobNode: PIB_BlobNode;
protected
{ Inherited Property Access methods }
  function GetBlobSize: longint; override;
  function GetOldAsString: string; override;
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string); override;
{ Property Access methods }
  function GetBlobID: isc_quad; //~virtual;
  function GetOldBlobID: isc_quad; //~virtual;
{ Inherited methods }
  function GetDisplayText: string; override;
  function GetIsLoaded: boolean; override;
  function GetIsModified: boolean; override;
  function SysInternalChanged: boolean; override;
  procedure SysSetIsNull( const AValue: boolean ); override;
{ Utility methods }
  procedure LoadFromBlob( const ABlob: TIB_ColumnBlob );
  procedure LoadFromGraphic( const AGraphic: TGraphic );
  procedure LoadFromStrings( Const AStrings: TStrings );
  procedure LoadFromStream( const AStream: TStream );
  procedure SaveToBlob( const ABlob: TIB_ColumnBlob );
  procedure SaveToStream( const AStream: TStream );
  procedure SaveToGraphic( const AGraphic: TGraphic );
  procedure SaveToStrings( const AStrings: TStrings );
public
  constructor Create( ARow: TIB_Row;
                      PSQLVAR: PXSQLVAR;
                      AFieldNo: smallint ); override;
{ Inherited Methods }
{: Empties the contents of the internal buffer.  If the column is nullable, it
sets the column to NULL;  if not, it sets the contents of the BLOB to zero
bytes.}
  procedure Clear; override;
{: Stream the contents of the BLOB from one of the following types:
<br><br>
TStream, TStrings, TPicture, TGraphic or TIB_ColumnBlob.
<br><br>
NOTE: I cannot provide built-in streaming to the TBlobField without forcing the
VCL/BDE code to be included.  That would defeat the purpose of using IB Objects
to avoid them!}
  procedure Assign( Source: TObject ); override;
{: Stream the contents of the BLOB to one of the following types:
<br><br>
TStream, TStrings, TPicture, TGraphic or TIB_ColumnBlob.
<br><br>
NOTE: I cannot provide built-in streaming to the TBlobField without forcing the
VCL/BDE code to be included.}
  procedure AssignTo( Dest: TObject ); override;
{: Method indicating whether a BLOB is equal to another BLOB.}
  function IsEqualTo( ABlob: TIB_ColumnBlob ): boolean;
{: Method to transfer data from a raw buffer to the contents of the column.}
  procedure SetBlobData( ABuffer: Pointer; ASize: integer ); override;
{: Load BLOB contents from a file.}
  procedure LoadFromFile( const AFileName: string ); override;
{: Save BLOB contents to a file.}
  procedure SaveToFile( const AFileName: string ); override;
{ Properties }

{: Permanent BLOB ID obtained by fetching data from a SELECT statement.}
  property BlobID: isc_quad read GetBlobID;
{: }
  property OldBlobID: isc_quad read GetOldBlobID;
{: Reference to the internal blob storage buffer.}
  property BlobNode: PIB_BlobNode read FBlobNode;
{: Reference to the blob node that contains the current data or nil.}
  property LoadedBlobNode: PIB_BlobNode read GetLoadedBlobNode;
end;

{                                                                              }
{ TIB_ColumnBinary                                                             }
{                                                                              }

{: This component implements the TIB_ColumnBlob class that is used for all blob
columns that are not text.}
TIB_ColumnBinary = class( TIB_ColumnBlob )
end;

{                                                                              }
{ TIB_ColumnMemo                                                               }
{                                                                              }

{: This component implements the TIB_ColumnBlob class that is used for all blob
columns that are text.}
TIB_ColumnMemo = class( TIB_ColumnBlob )
protected 
  function GetAsWideString: WideString; override;
  procedure SetAsWideString( const NewValue: WideString ); override;
public
  constructor Create( ARow: TIB_Row;
                      PSQLVAR: PXSQLVAR;
                      AFieldNo: smallint ); override;
end;

TIB_ColumnBLR = class( TIB_ColumnBlob )
public
  constructor Create( ARow: TIB_Row;
                      PSQLVAR: PXSQLVAR;
                      AFieldNo: smallint ); override;
end;

// IBA_ColumnBlob.IMP

{                                                                              }
{ TIB_ColumnArray                                                              }
{                                                                              }

{: This type is used for reading array slices or whole arrays into variant
arrays using streams as the intermediate vehicle.}
TIB_ArrayReader = procedure ( var Buffer; Count: longint ) of object;
{: This type is used in order to write slices or whole arrays into variant
arrays using streams as the intermediate vehicle.}
TIB_ArrayWriter = procedure ( const Buffer; Count: longint ) of object;

{: Foundation upon which a custom class of array handling columns can be based.
<br><br>
This is designed to allow a way to derive fully functional components that
will interact with the InterBase array column type.
<br><br>
Provison is made to get and put the whole array or just a slice. Variants can be
used, or calls can be made to operate on memory you have declared in your own
code to store the array. It is the developer's responsibility to provide the
buffer into and out of which the data is read and written when variants are not
used. This is very simple, as can be seen in this example, where an array column
is declared having bounds of 1 to 1024 of integer type:
<br>
<code>
procedure TForm1.btArrayClick(Sender: TObject);
var
  ii: word;
  ArrayValues: array [1..1024] of integer;
  ArraySize: ISC_LONG;
  tmpCol: TIB_ColumnArray;
begin
  if Sender = btWriteArray then begin
    for ii := 1 to 1024 do ArrayValues[ii] := ii;
  end else begin
    FillChar( ArrayValues, SizeOf( ArrayValues ), 0 );
  end;
  ArraySize := ISC_LONG( sizeof( ArrayValues ));
  tmpCol := QueryData.FieldByName( 'ARRAY_DATA' ) as TIB_ColumnArray;
  if Sender = btWriteArray then begin
    tmpCol.PutArray( @ArrayValues, @ArraySize );
    QueryData.Post;
  end else begin
    tmpCol.GetArray( @ArrayValues, @ArraySize );
  end;
  meArrayData.Lines.BeginUpdate;
  meArrayData.Lines.Clear;
  try
    for ii := 1 to 1024 do begin
      meArrayData.Lines.add( IntToStr( ArrayValues[ii] ));
    end;
  finally
    meArrayData.Lines.EndUpdate;
  end;
end;
</code>}
TIB_ColumnArray = class(TIB_Column)
private
  FArrayDesc: ISC_ARRAY_DESC;
protected
  function GetOldAsString: string; override;
  function GetAsString: string; override;
  procedure SetAsString( const NewValue: string); override;
  function GetSQLTypeSource( const BaseTypeOnly: boolean ): string; override;
  function GetAsVariant: variant; override;
  procedure SetAsVariant( const NewValue: variant ); override;
  function GetArrayID: isc_quad; //~virtual;
  function GetOldArrayID: isc_quad; //~virtual;
public
{$IFNDEF HELPSCAN}
  constructor Create( ARow: TIB_Row;
                      PSQLVAR: PXSQLVAR;
                      AFieldNo: smallint ); override;
  procedure Clear; override;
{$ENDIF}

{: Method to indicate whether an ARRAY is equal to another ARRAY.
<br><br>
For now, this is not properly implemented and simply returns False.}
  function IsEqualTo( AArray: TIB_ColumnArray ): boolean;
{: This method reads the whole array into a buffer that you have declared
directly via the API.}
  procedure GetArray( PArrayBuffer: pointer; PArraySize: pointer );
{: This method writes the whole array from a buffer that you have declared
directly via the API.}
  procedure PutArray( PArrayBuffer: pointer; PArraySize: pointer );
{: This method allows you to read the array or a slice of it into a buffer
that you have declared directly via the API.}
  procedure GetSlice( PArrayDesc: PISC_ARRAY_DESC;
                      PArrayBuffer: pointer;
                      PArraySize: pointer );
{: This method allows you to write the array or a slice of it from a buffer
that you have declared directly via the API.}
  procedure PutSlice( PArrayDesc: PISC_ARRAY_DESC;
                      PArrayBuffer: pointer;
                      PArraySize: pointer );
{: This method allows you to specify the dimensions of the array that you want
placed into a variant array.  An exception will be raised if you specify elements
that are out of the scope of the array.
<br><br>
This method will return the variant Unassigned if the whole column is NULL.}
  function GetVarSlice( Dimensions: array of integer ): variant;
{: This method allows you to alter certain elements of the array by passing in
a variant array holding what the new values should be. If the whole array is
used and the dimension bound is shifted by one, IBO will slide the bounds
automatically, thereby avoiding an exception.}
  procedure PutVarSlice( const Values: variant );
{: This is the description structure for the whole array. It can be used in the
calls to Put/GetSlice.}
  property ArrayDesc: ISC_ARRAY_DESC read FArrayDesc;
{: This value is needed for various API calls but, since IBO makes all the API
calls for you automatically, it should be unnecessary to use it.  It is here for
your convenience, in case you need to do some specialized things that IBO
doesn't handle.}
  property ArrayID: isc_quad read GetArrayID;
end;
//IBA_ColumnArray.IMP

const
{: This value is used as the default boolean true value for text columns.}
  BoolTrueChr: char = 'T';
{: This value is used as the default boolean false value for text columns.}
  BoolFalseChr: char = 'F';
{: This value is used as the default boolean true value for numeric columns.}
  BoolTrueNbr: char = '1';
{: This value is used as the default boolean false value for numeric columns.}
  BoolFalseNbr: char = '0';

{: These are the states when a transaction is in the process of closing up a
unit of work.}
  PendingTransactionStates: set of TIB_TransactionState = [
    tsSavePointPending,
    tsCommitRetainingPending,
    tsCommitPending,
    tsCommitRefreshPending,
    tsRollbackRetainingPending,
    tsRollbackPending,
    tsRollbackRefreshPending ];

procedure ClearConnectionPool;
function GetRecord( ANode: PIB_Node; ARow: TIB_Row ): word;
procedure PutRecord( ANode: PIB_Node; ARow: TIB_Row );

implementation

uses
  Consts, Registry, uRounding,

{$IFNDEF IBO_VCL60_OR_GREATER}
  FileCtrl,
{$ENDIF}

{$IFDEF IBO_VCL40_OR_GREATER}
  SyncObjs,
{$ENDIF}

  IB_Utils, IB_Parse, IB_Schema,
  IBD_Login, IBD_CancelQuery;

// IBA_StringList.INT

constructor TIB_StringList.Create;
begin
  inherited Create;
  FAllowBlankLines := true;
end;

constructor TIB_StringProperty.Create;
begin
  inherited Create;
  FAllowBlankLines := false;
end;

constructor TIB_StringCache.Create;
begin
  inherited Create;
  inherited Sorted := true;
end;

procedure TIB_StringList.SaveToStream(Stream: TStream);
var
  S: string;
begin
  if Assigned( Stream ) then
  begin
    S := GetTextStr;
    Stream.WriteBuffer(Pointer(S)^, Length(S));
  end;
end;

function TIB_StringList.Add(const S: string): Integer;
  function InternalAdd( const S: string):Integer;
  var
    APos: longint;
  begin
    if ( not Sorted ) then
      Result := inherited Add( S )
    else
    begin
      APos := getLitSafePos( '=', S, 1 );
      if APos = 0 then
        Result := inherited Add( S )
      else
      begin
        if FindIndex( Copy( S, 1, APos - 1 ), Result ) then
        begin
          case Duplicates of
            dupIgnore:
            begin
              Strings[ Result ] := S;
              Exit;
            end;
            dupError: raise Exception.Create( E_Duplicate_String_Error );
          end;
        end;
        Result := inherited Add( S )
      end;
    end;
  end;
begin
  if RemovingBlankLines or ( Length( Trim( S )) > 0 ) then
    Result := InternalAdd( S )
  else
  begin
// This makes it so that a blank line can be explicitely added.
// It is to allow the way the TStrings.SetValue() method works.
    FRemovingBlankLines := true;
    try
      Result := InternalAdd( S );
    finally
      FRemovingBlankLines := false;
    end;
  end;
end;

//ADDED// CW 2000-08-11
{
The normal implementation (at least for Delphi 3) of TStringList.Put()
does not support sorted lists.  This method override allows Put() to
work with a sorted TIB_StringList by reverting to the behaviour of
the TStrings.Put() method when the stringlist is sorted.
}
procedure TIB_StringList.Put(Index: Integer; const S: string);
var
  TempObject: TObject;
begin
  if ( not Sorted ) then
    inherited
  else
  begin
    TempObject := GetObject( Index );
    Delete( Index );
    AddObject( S, TempObject );
  end;
end;

function TIB_StringList.GetIndexName( Index: integer ): string;
var
  APos: longint;
begin
  Result := Get(Index);
  APos := getLitSafePos( '=', Result, 1 );
  if APos <> 0 then
    SetLength( Result, APos - 1 );
end;

function TIB_StringList.GetIndexNameRel( Index: integer ): string;
var
  tmpPos: integer;
begin
  Result := '';
  if ( Index >= 0 ) and ( Index < Count ) then
  begin
    Result := IndexNames[ Index ];
    tmpPos := getLitSafePos( '.', Result, 1 );
    if tmpPos > 0 then
      Result := Copy( Result, 1, tmpPos - 1 );
  end;
end;

function TIB_StringList.GetIndexNameCol( Index: integer ): string;
var
  tmpPos: integer;
begin
  if ( Index >= 0 ) and ( Index < Count ) then
  begin
    Result := IndexNames[ Index ];
    tmpPos := getLitSafePos( '.', Result, 1 );
    if tmpPos > 0 then
      Result := Copy( Result, tmpPos + 1, MaxInt );
  end
  else
    Result := '';
end;

procedure TIB_StringList.SetIndexName( Index: integer; AValue: string );
var
  tmpPos: longint;
  tmpStr: string;
begin
  if AValue = '' then
    Delete( Index )
  else
  begin
    tmpStr := Get(Index);
    tmpPos := getLitSafePos( '=', tmpStr, 1 );
    if tmpPos > 0 then
      tmpStr := AValue + Copy( tmpStr, tmpPos, MaxInt )
    else
      tmpStr := AValue;
    Put( Index, tmpStr );
  end;
end;

function TIB_StringList.GetIndexValue( Index: integer ): string;
var
  tmpPos: longint;
begin
  Result := Get( Index );
  tmpPos := getLitSafePos( '=', Result, 1 );
  if tmpPos > 0 then
    Result := Copy( Result, tmpPos + 1, MaxInt )
  else
    SetLength( Result, 0 );
end;

procedure TIB_StringList.SetIndexValue( Index: integer; AValue: string );
var
  tmpPos: longint;
  tmpStr: string;
begin
  tmpStr := Get(Index);
  tmpPos := getLitSafePos( '=', tmpStr, 1 );
  if tmpPos > 0 then
    tmpStr := Copy(tmpStr, 1, tmpPos) + AValue
  else
    tmpStr := tmpStr + '=' + AValue;
  Put(Index, tmpStr);
end;

function TIB_StringList.GetLinkIndex( ALink: string ): integer;
var
  tmpPos: longint;
  tmpStr: string;
  ii: integer;
begin
  if Sorted then
    FindIndex( ALink, Result )
  else
    Result := IndexOfName( ALink );
  if Result = -1 then
    Result := IndexOf( ALink );
  if Result = -1 then
  begin
    tmpPos := getLitSafePos( '.', ALink, 1 );
    if tmpPos <> 0 then
    begin
      ALink := Copy( ALink, tmpPos + 1, MaxInt );
      if Sorted then
        FindIndex( ALink, Result )
      else
        Result := IndexOfName( ALink );
      if Result = -1 then
        Result := IndexOf( ALink );
    end
    else
    begin
      for ii := 0 to Count - 1 do
      begin
        tmpStr := Names[ ii ];
        tmpPos := getLitSafePos( '.', tmpStr, 1 );
        if tmpPos > 0 then
        begin
          tmpStr := Copy( tmpStr, tmpPos + 1, MaxInt );
          if AnsiCompareText( ALink, tmpStr ) = 0 then
          begin
            Result := ii;
            Break;
          end;
        end;
      end;
    end;
  end;
end;

function TIB_StringList.GetLinkValue( ALink: string ): string;
var
  tmpPos: longint;
  ii: integer;
  tmpStr: string;
begin
  if Sorted then
    FindValue( ALink, Result )
  else
    Result := Values[ ALink ];
  if Result = '' then
  begin
    tmpPos := getLitSafePos( '.', ALink, 1 );
    if tmpPos <> 0 then
    begin
      ALink := Copy( ALink, tmpPos + 1, MaxInt );
      if Sorted then
        FindValue( ALink, Result )
      else
        Result := Values[ ALink ];
    end
    else
    begin
      for ii := 0 to Count - 1 do
      begin
        tmpStr := Names[ ii ];
        tmpPos := getLitSafePos( '.', tmpStr, 1 );
        if tmpPos > 0 then
        begin
          tmpStr := Copy( tmpStr, tmpPos + 1, MaxInt );
          if AnsiCompareText( ALink, tmpStr ) = 0 then
          begin
            Result := IndexValues[ ii ];
            Break;
            // Check for ambiguous settings?
          end;
        end;
      end;
    end;
  end;
end;

procedure TIB_StringList.SetLinkValue( ALink: string; AValue: string );
var
  tmpPos: longint;
  tmpStr: string;
  ii: integer;
  tmpInd: integer;
  tmpLink: string;
begin
  tmpLink := ALink;
  if Sorted then
    FindIndex( ALink, tmpInd )
  else
    tmpInd := IndexOfName( ALink );
  if tmpInd = -1 then
    tmpInd := IndexOf( ALink );
  if tmpInd = -1 then
  begin
    tmpPos := getLitSafePos( '.', ALink, 1 );
    if tmpPos <> 0 then
    begin
      ALink := Copy( ALink, tmpPos + 1, MaxInt );
      if Sorted then
        FindIndex( ALink, tmpInd )
      else
        tmpInd := IndexOfName( ALink );
      if tmpInd = -1 then
        tmpInd := IndexOf( ALink );
    end
    else
    begin
      for ii := 0 to Count - 1 do
      begin
        tmpStr := Names[ ii ];
        tmpPos := getLitSafePos( '.', tmpStr, 1 );
        if tmpPos > 0 then
        begin
          tmpStr := Copy( tmpStr, tmpPos + 1, MaxInt );
          if AnsiCompareText( ALink, tmpStr ) = 0 then
          begin
            tmpInd := ii;
            tmpStr := Get( tmpInd );
            Put( tmpInd, Copy( tmpStr, 1,
                               getLitSafePos( '=', tmpStr, 1 )) + AValue );
          end;
        end;
      end;
      if tmpInd <> -1 then
        Exit;
    end;
  end;
  if tmpInd = -1 then
    Add( tmpLink + '=' + AValue )
  else
  begin
    tmpStr := Get( tmpInd );
    Put( tmpInd, Copy( tmpStr, 1, getLitSafePos( '=', tmpStr, 1 )) + AValue );
  end;
end;

function TIB_StringList.GetIndexParamValue( Index: integer;
                                            AParam: string ): string;
var
  BeginPos, EndPos: longint;
begin
  ExtractListLinkParam( IndexValues[Index], AParam, BeginPos, EndPos, Result );
end;

procedure TIB_StringList.SetIndexParamValue( Index: integer;
                                             AParam: string; AValue: string );
var
  tmpStr: string;
begin
  tmpStr := IndexValues[ Index ];
  if SetListLinkParamValue( tmpStr, AParam, AValue ) then
    IndexValues[ Index ] := tmpStr;
end;

function TIB_StringList.GetLinkParamValueEx( ALink, AParam: string;
  var IsSet: boolean ): string;
var
  BeginPos, EndPos: longint;
begin
  ExtractListLinkParam( LinkValues[ ALink ], AParam, BeginPos, EndPos, Result );
  IsSet := BeginPos <> ParseLineInvalid;
end;

function TIB_StringList.GetLinkParamValue( ALink, AParam: string ): string;
var
  BeginPos, EndPos: longint;
begin
  ExtractListLinkParam( LinkValues[ ALink ], AParam, BeginPos, EndPos, Result );
end;

procedure TIB_StringList.SetLinkParamValue( ALink, AParam, AValue: string );
var
  tmpStr: string;
begin
  tmpStr := LinkValues[ ALink ];
  if SetListLinkParamValue( tmpStr, AParam, AValue ) then 
    LinkValues[ ALink ] := tmpStr;
end;

function TIB_StringList.GetLinkParamIsSet( ALink, AParam: string ): boolean;
var
  tmpStr: string;
  BeginPos, EndPos: longint;
begin
  ExtractListLinkParam( LinkValues[ ALink ], AParam, BeginPos, EndPos, tmpStr );
  Result := BeginPos <> ParseLineInvalid;
end;

procedure TIB_StringList.SetLinkParamIsSet( ALink, AParam: string;
                                            AValue: boolean );
var
  tmpStr: string;
begin
  tmpStr := LinkValues[ ALink ];
  if SetListLinkParamIsSet( tmpStr, AParam, AValue ) then begin
    LinkValues[ ALink ] := tmpStr;
  end;
end;

function TIB_StringList.GetIndexParamIsSet( AIndex: integer;
                                            AParam: string ): boolean;
var
  tmpStr: string;
  BeginPos, EndPos: longint;
begin
  ExtractListLinkParam( IndexValues[AIndex], AParam, BeginPos, EndPos, tmpStr );
  Result := BeginPos <> ParseLineInvalid;
end;

procedure TIB_StringList.SetIndexParamIsSet( AIndex: integer;
                                             AParam: string; AValue: boolean );
var
  tmpStr: string;
begin
  tmpStr := IndexValues[ AIndex ];
  if SetListLinkParamIsSet( tmpStr, AParam, AValue ) then begin
    IndexValues[ AIndex ] := tmpStr;
  end;
end;

procedure TIB_StringList.RemoveBlankLines;
var
  ii: integer;
begin
  if ( not RemovingBlankLines ) then
  begin
    FRemovingBlankLines := true;
    try
      ii := Count - 1;
      while ii >= 0 do
      begin
        if ii < Count then
        begin
          if Trim( Get( ii )) = '' then
          begin
            Delete( ii );
          end;
        end;
        Dec( ii );
      end;
    finally
      FRemovingBlankLines := false;
    end;
  end;
end;

procedure TIB_StringList.RemoveBlankEntries;
var
  ii: integer;
  tmpStr: string;
  tmpPos: integer;
begin
  if ( not RemovingBlankLines ) then
  begin
    FRemovingBlankLines := true;
    try
      ii := Count - 1;
      while ii >= 0 do
      begin
        if ii < Count then
        begin
          tmpStr := Trim( Get( ii ));
          tmpPos := getLitSafePos( '=', tmpStr, 1 );
          if tmpPos = Length( tmpStr ) then
          begin
            Delete( ii );
          end;
        end;
        Dec( ii );
      end;
    finally
      FRemovingBlankLines := false;
    end;
  end;
end;

procedure TIB_StringList.SetAllowBlankLines( AValue: boolean );
begin
  if AllowBlankLines <> AValue then
  begin
    FAllowBlankLines := AValue;
    if AllowBlankLines then
      RemoveBlankLines;
  end;
end;

procedure TIB_StringList.Changing;
begin
  if ( not RemovingBlankLines ) then
    inherited Changing;
end;

procedure TIB_StringList.Changed;
begin
  if ( not RemovingBlankLines ) and ( not UpdateState ) then
  begin
    if ( Count > 0 ) and ( not AllowBlankLines ) then
      RemoveBlankLines;
    inherited Changed;
  end;
end;

procedure TIB_StringList.SetUpdateState(Updating: Boolean);
begin
  FUpdateState := Updating;
  inherited SetUpdateState(Updating );
end;

function TIB_StringList.FindIndex( const AName: string;
                                     var AIndex: integer ): boolean;
var
  L, H, I, C: Integer;
begin
  Result := False;
  if Sorted then
  begin
    L := 0;
    H := Count - 1;
    while L <= H do
    begin
      I := (L + H) shr 1;
      C := AnsiCompareText( Copy( Get(I), 1, Length(AName) + 1 ), AName + '=' );
      if C < 0 then
      begin
        L := I + 1;
      end
      else
      begin
        H := I - 1;
        if C = 0 then
        begin
          Result := True;
          if Duplicates <> dupAccept then
          begin
            L := I;
          end;
        end;
      end;
    end;
    AIndex := L;
  end
  else
    raise Exception.Create( E_Stringlist_Not_Sorted );
  if ( not Result ) then
    AIndex := -1;
end;

function TIB_StringList.FindValue( const AName: string;
                                     var AValue: string ): boolean;
var
  tmpInd: integer;
begin
  AValue := '';
  Result := FindIndex( AName, tmpInd );
  if Result then
    AValue := IndexValues[ tmpInd ];
end;

function TIB_StringList.GetSafeCommaText: string;
var
  ii: integer;
begin
  if ( Count = 1 ) and ( Get(0) = '' ) then
    Result := '""'
  else
  begin
    Result := '';
    for ii := 0 to Count - 1 do
      Result := Result + Get( ii ) + ',';
    System.Delete( Result, Length( Result ), 1 );
  end;
end;

procedure TIB_StringList.SetSafeCommaText( AValue: string );
var
  ii: integer;
  tmpPos: integer;
begin
  tmpPos := 0;
  BeginUpdate;
  try
    Clear;
    if AValue <> '' then
      repeat
        Inc( tmpPos );
        ii := tmpPos;
        tmpPos := getLitSafePos( ',', AValue, tmpPos );
        if tmpPos > 0 then
          Add( Trim( Copy( AValue, ii, tmpPos - ii )))
        else
          Add( Trim( Copy( AValue, ii, MaxInt )));
      until tmpPos = 0;
  finally
    EndUpdate;
  end;
end;

function TIB_StringList.SysStringListParamSort( Index1: integer;
                                                Index2: string ): Integer;
var
  tmp1: string;
  tmp2: string;
begin
  if FQuickSortParam <> '' then
  begin
    tmp1 := IndexParamValue[ Index1, FQuickSortParam ];
    tmp2 := Index2;
  end
  else
  begin
    tmp1 := IndexValues[ Index1 ];
    tmp2 := Index2; 
  end;
  if FQuickSortNumeric then
  begin
    if StrToFloat( tmp1 ) = StrToFloat( tmp2 ) then
      Result := 0
    else
    if StrToFloat( tmp1 ) < StrToFloat( tmp2 ) then
      Result := -1
    else
      Result := 1;
  end
  else
    Result := AnsiCompareText( tmp1, tmp2 );
end;

procedure TIB_StringList.SysParamQuickSort( L, R: Integer );
var
  I, J: Integer;
  P: string;
begin
  repeat
    I := L;
    J := R;
    if FQuickSortParam <> '' then
      P := IndexParamValue[ ( L + R ) shr 1, FQuickSortParam ]
    else
      P := IndexValues[ ( L + R ) shr 1 ];
    repeat
      while SysStringListParamSort( I, P ) < 0 do
        Inc( I );
      while SysStringListParamSort( J, P ) > 0 do
        Dec( J );
      if I <= J then
      begin
        Exchange( I, J );
        Inc( I );
        Dec( J );
      end;
    until I > J;
    if L < J then
      SysParamQuickSort( L, J );
    L := I;
  until I >= R;
end;

procedure TIB_StringList.ParamQuickSort( AParam: string; IsNumeric: boolean );
begin
  FQuickSortParam := AParam;
  FQuickSortNumeric := IsNumeric;
  if Count > 1 then
    SysParamQuickSort( 0, Count - 1 );
end;


// IBA_Stream.INT
// IBBlobStream.inc

constructor TIB_BlobStream.Create( ARow: TIB_Row;
                                   ASQLVAR: XSQLVAR;
                                   AFieldNo: integer;
                                   ABlobNode: PIB_BlobNode;
                                   APBlobHead: PPIB_BlobNode;
                                   AMode: TIB_BlobStreamMode );
begin
  FRow := ARow;
  FPSQLVAR := ASQLVar;
  FFieldNo := AFieldNo; 
  FBlobNode := ABlobNode;
  FPBlobHead := APBlobHead;
  FMode := AMode;
  FPosition := 0;
  Initialize;
  inherited Create;
end;

constructor TIB_BlobStream.CreateForColumn( AColumn: TIB_Column;
                                            AMode: TIB_BlobStreamMode );
begin
  FColumn := AColumn;
  Create( AColumn.Row,
          AColumn.PSQLVar^,
          AColumn.FieldNo,
          AColumn.Row.GetBlobNode( AColumn.FieldNo ),
          AColumn.Row.PBlobHead,
          AMode );
end;

destructor TIB_BlobStream.Destroy;
begin
  if Assigned( FColumn ) and Modified then
    FColumn.SysAfterModify;
  inherited Destroy;
end;

procedure TIB_BlobStream.Initialize;
var
  ParentBlobRef: PIB_BlobNode;
  tmpBlobNode: PIB_BlobNode;
  tmpArrayDesc: PISC_ARRAY_DESC;
begin
  if Mode = bsmWrite then
  begin
    if ( not Assigned( BlobNode )) then
      raise EIB_Error.CreateWithSender( Row.Statement, E_Unassigned_Blob_Node );
    ClearBlobNodeData( BlobNode );
    SetModified;
    SQLVAR.SQLInd^ := IB_Null;
  end
  else
  if ( SQLVAR.SQLInd^ = IB_Null ) or ( not Assigned( PBlobHead )) then
  begin
    if Assigned( BlobNode ) then
      ClearBlobNodeData( BlobNode )
    else
    if Mode = bsmReadWrite then
      raise EIB_Error.CreateWithSender( Row.Statement, E_Unassigned_Blob_Node );
  end
  else
  if Assigned( BlobNode ) and
     ( BlobNode.BlobChanged or
      (( BlobNode.BlobSize > 0 ) and
         ( not isc_quad_is_zero( BlobNode.BlobID )) and
             isc_quad_equal( BlobNode.BlobID,
                             pisc_quad(SQLVAR.SQLData)^))) then
  else
  case Mode of
    bsmRead:
    begin
      tmpBlobNode := FindBlobNodeInList( PBlobHead,
                                         pisc_quad(SQLVAR.SQLData)^,
                                         FFieldNo,
                                         true,
                                         ParentBlobRef );
      if Assigned( tmpBlobNode ) then
        FBlobNode := tmpBlobNode
      else
      begin
        tmpBlobNode := AllocMem( SizeOf( TIB_BlobNode ));
        FBlobNode := tmpBlobNode;
        ClearBlobNodeData( BlobNode );
        BlobNode.BlobID := pisc_quad(SQLVAR.SQLData)^;
        try
          tmpArrayDesc := nil;
          case SQLVAR.SQLType of
            SQL_ARRAY,
            SQL_ARRAY_:
              tmpArrayDesc := @(FColumn as TIB_ColumnArray).ArrayDesc;
          end;
          Row.Statement.GetBlobNodeData( @SQLVAR, tmpArrayDesc, BlobNode );
        except
          if Assigned( tmpBlobNode ) then
          begin
            FreeBlobNodeData( tmpBlobNode );
            FreeMem( tmpBlobNode );
          end;
          raise;
        end;
        tmpBlobNode.Next := PBlobHead^;
        PBlobHead^ := tmpBlobNode;
      end;
    end;
    bsmReadWrite:
    begin
      if ( not GetBlobNodeFromList( PBlobHead, true, BlobNode )) then
        Row.Statement.GetBlobNodeData( @SQLVAR, nil, BlobNode );
    end;
  end;
end;

procedure TIB_BlobStream.SetModified;
begin
  if ( not FModified ) then
  begin
    if Assigned( FColumn ) then
      FColumn.SysBeforeModify;
    pisc_quad(SQLVAR.SQLData)^ := BlankQuad;
    BlobNode.BlobChanged := true;
    FModified := true;
  end;
end;

function TIB_BlobStream.Read( var Buffer; Count: longint ): longint;
begin
  if Assigned( BlobNode ) then
  begin
    if FPosition + Count > BlobNode.BlobSize then
      Result := BlobNode.BlobSize - FPosition
    else
      Result := Count;
  end
  else
    Result := 0;
  if Result > 0 then
  begin
    Move( pointer(longint(BlobNode.BlobBuffer) + FPosition)^, Buffer, Result );
    Inc( FPosition, Result );
  end;
end;

function TIB_BlobStream.Seek( Offset: longint; Origin: Word ): longint;
begin
  if Assigned( BlobNode ) then begin
    case Origin of
      0: FPosition := Offset;
      1: Inc( FPosition, Offset );
      2: FPosition := BlobNode.BlobSize + Offset;
    end;
    if FPosition > BlobNode.BlobSize then begin
      FPosition := BlobNode.BlobSize;
    end;
  end;
  if FPosition < 0 then begin
    FPosition := 0;
  end;
  Result := FPosition;
end;

function TIB_BlobStream.Write( const Buffer; Count: longint ): longint;
var
  NewSize: integer;
begin
  Result := 0;
  if FMode <> bsmRead then
  begin
    if ( not FModified ) then
      SetModified;
    SQLVAR.SQLInd^ := IB_NotNull;
    NewSize := FPosition + Count;
    if NewSize > BlobNode.BlobBufLen then
    begin
      IB_ReallocMem( BlobNode.BlobBuffer, BlobNode.BlobBufLen, NewSize );
      BlobNode.BlobBufLen := NewSize;
    end;
    if Count > 0 then
    begin
      Move( Buffer, pointer(longint(BlobNode.BlobBuffer) + FPosition)^, Count );
      Inc( FPosition, Count );
      if FPosition > BlobNode.BlobSize then
        BlobNode.BlobSize := FPosition;
      Result := Count;
    end;
  end;
end;

(*

type
  TPictureFiler = class(TFiler)
  public
    ReadData: TStreamProc;
    WriteData: TStreamProc;
    constructor Create; overload;
    procedure DefineProperty( const Name: string;
                                    ReadData: TReaderProc;
                                    WriteData: TWriterProc;
                                    HasData: boolean ); override;
    procedure DefineBinaryProperty( const Name: string;
                                          ReadData,
                                          WriteData: TStreamProc;
                                          HasData: boolean ); override;
    procedure FlushBuffer; override;
  end;

// Since I use TFiler only partially, the inherited constructor
// TFiler.Create is unnecessary, so I use this dummy

constructor TPictureFiler.Create;
begin
end;

//will be called by TPicture, handing over the private methods to
//read/write TPicture from/to Stream

procedure TPictureFiler.DefineBinaryProperty( const Name: string;
                                                    ReadData,
                                                    WriteData: TStreamProc;
                                                    HasData: boolean );
begin
  if Name = 'Data' then begin 
    Self.ReadData := ReadData;
    Self.WriteData := WriteData;
  end;
end;

procedure TPictureFiler.DefineProperty( const Name: string;
                                              ReadData: TReaderProc;
                                              WriteData: TWriterProc;
                                              HasData: boolean );
begin
  //at this time TPicture don't call this function
  //only implemented as precaution to (unlikely) changes in future delphi
  //versions
end;

procedure TPictureFiler.FlushBuffer;
begin
  //at this time TPicture don't call this function
  //only implemented as precaution to (unlikely) changes in future
  //delphi versions
end;

// Wrapper to call protected TPicture.DefineProperties
// must be in same unit as ReadWritePictureFromStream
type

TMyPicture = class( TPicture )
end;

procedure ReadWritePictureFromStream( Picture: TPicture;
                                      Stream: TStream;
                                      Read: boolean );
var
  Filer: TPictureFiler;
begin
  Filer := TPictureFiler.Create;
  try
    // TPicture.DefineProperties is protected, but TMyPicture is declared in
    // thisunit TMyPicture's protected members (also the inherited) are public
    // to this unit
    TMyPicture( Picture ).DefineProperties( Filer );
    //TPicture.DefineProperties calls Filer.DefineBinaryProperty
    if Read then
      Filer.ReadData( Stream ) //TPicture does the work
    else
      Filer.WriteData( Stream ); //TPicture does the work
  finally
    Filer.Free;
  end;
end;

//whatever TIcons actual image size, its LoadFromStream(Stream: TStream)
//reads just to the end of the stream
//if I have additional things after TIcon streamed, they are lost after
//TIcon.LoadFromStream
//so I store the actual size before in the stream
procedure WritePictureToStream( Picture: TPicture; Stream: TStream );
var
  MStream: TMemoryStream;
  iPictureSize: Integer;
begin
  MStream := TMemoryStream.Create;
  try
    //store TPicture data in TMemoryStream
    ReadWritePictureFromStream( Picture, MStream, False );
    iPictureSize := MStream.Size;
    //store size of TPicture data in TStream
    Stream.WriteBuffer( iPictureSize, sizeof( iPictureSize ));
    //store TMemoryStream(containing TPicture data) in TStream
    Stream.WriteBuffer( MStream.Memory^, iPictureSize );
  finally
    MStream.Free;
  end;
end;

procedure ReadPictureFromStream( Picture: TPicture; Stream: TStream );
var
  MStream: TMemoryStream;
  iPictureSize: Integer;
begin
  MStream := TMemoryStream.Create;
  try
    //read size of TPicture data
    Stream.ReadBuffer( iPictureSize, sizeof( iPictureSize ));
    //adjust buffer size
    MStream.SetSize( iPictureSize );
    //get TPicture data
    Stream.ReadBuffer( MStream.Memory^, iPictureSize );
    //why TMemoryStream ? See what I said above about TIcon
    //read TPicture data
    ReadWritePictureFromStream( Picture, MStream, True );
  finally
    MStream.Free;
  end;
end;

//Now WritePictureToStream and ReadPictureFromStream could be used to
//save/load any TPicture to/from any TStream.

//example (in pseudo code):

  AStream := ADataSet.CreateBlobStream( ABlobField, bmWrite );
  try
    WritePictureToStream( APicture, AStream );
  finally
    AStream.Free;
  end;

  AStream := ADataSet.CreateBlobStream( ABlobField, bmRead );
  try
    ReadPictureFromStream( APicture, AStream );
  finally
    AStream.Free;
  end;

*)

// IBA_Session.INT

{$IFDEF ALLOW_ALT_SESSION}
function GetAlternateSession: TIB_Session;
begin
// This allows a session outside of the threads environment be used as a
// default session. This is useful to make DLL files loaded merge into the
// main session of the EXE's instance. This way, everything will behave as
// if it were truly all a part of the same EXE.
  if hAlternateSession > 0 then
    Result := TIB_Session( TlsGetValue( hAlternateSession ))
  else
    Result := nil;
end;

class procedure TIB_Session.SetAlternateSession( ASession: TIB_Session );
begin
// Set an alternate component session to be used as the DefaultSession.
  if hAlternateSession > 0 then
    TlsSetValue( hAlternateSession, ASession );
end;
{$ENDIF}

{$IFDEF ALLOW_DEFAULT_SESSION}
function GetDefaultSession: TIB_Session;
begin
// This makes it so that for each individual thread there is a unique
// session instance used for the default session.
  if ( hDefaultSession > 0 ) then
    Result := TIB_Session( TlsGetValue( hDefaultSession ))
  else
    Result := nil;
  if ( not Assigned( Result )) then
  begin
    Result := TIB_Session.Create( nil );
    Result.FIsDefaultSession := true;
    Result.Name := 'IBO_DefaultSession';
    if hDefaultSession > 0 then
      TlsSetValue( hDefaultSession, Result );
  end;
end;

procedure FreeDefaultSession( var ASession: TIB_Session );
var
  tmpSession: TIB_Session;
begin
  if hDefaultSession > 0 then
    tmpSession := TIB_Session( TlsGetValue( hDefaultSession ))
  else
    tmpSession := nil;
// It is possible to be in another thread's environment now.
// Objects being created will persist beyond the thread's boundries in the
// same process. So, if a default session is created in one thread it may
// not be destroyed until after that thread has long terminated.
  if Assigned( tmpSession ) and
     ( tmpSession = ASession ) and
     ( hDefaultSession > 0 ) then
    TlsSetValue( hDefaultSession, nil );
  tmpSession := ASession;
  ASession := nil;
  tmpSession.Free;
end;

class function TIB_Session.DefaultSession: TIB_Session;
begin
// This will always return a valid session instance.
// It first looks for an alternate session and if there is not any
// it looks for an existing default session and if there is not any
// it will automatically generate one.
{$IFDEF ALLOW_ALT_SESSION}
  Result := GetAlternateSession;
  if ( not Assigned( Result )) then
{$ENDIF}
    Result := GetDefaultSession;
end;
{$ENDIF}

constructor TIB_Session.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  InitializeCriticalSection( FTimerCS );
  FTimerItems := TList.Create;
  FTimerItemIndex := 0;
  Session_Components := TList.Create;
  Session_Connections := TList.Create;
  Session_ConnectionLinks := TList.Create;
  Session_Transactions := TList.Create;
  Session_TransactionLinks := TList.Create;
  Session_Statements := TList.Create;
  Session_Datasets := TList.Create;
  Session_DataSources := TList.Create;
  Session_DataLinks := TList.Create;
  FBusyCursor := -17; //crSQLWait;
  FYieldCursor := -19; //crSQLYield;
  FBusyLevel := 0;
  FLockCursorLevel := 0;
  FYieldLevel := -1;
  FStoreActive := false;
  FAllowDefaultConnection := true;
  FAllowDefaultTransaction := true;
  FEditingColor := $00AAFFFF;   // clLemon  // old value: clYellow;
  FInsertingColor := $00C0FFC0; // clApple  // old value: clLime;
  FDeletingColor := $00C8C8FF;  // clPink   // old value: clRed;
  FSearchingColor := $00F0F0C8; // clLtAqua // old value: clAqua;
  FReadOnlyColor := clSilver;
  FSelectedColor := clBlue;
  FInvalidColor := clNone;
  FPreparedColor := clGrayText;
  FBrowsingColor := clWindow;
  FInCnt := 5;
  FInDa := AllocMem( XSQLDA_LENGTH( FInCnt ));
  FInDa.version := SQLDA_VERSION1;
  FInDa.sqln := FInCnt;
  FOutCnt := 30;
  FOutDa := AllocMem( XSQLDA_LENGTH( FOutCnt ));
  FOutDa.version := SQLDA_VERSION1;
  FOutDa.sqln := FOutCnt;
  FUseCursor := false;
  // The next section initialises FUseCursor to true and the TimerInterval
  // to 5000  **ONLY** in the main process thread.
  // Notes:
  //   * Using AttachCS CriticalSection here to ensure we have secure
  //     access to the Application.MainForm definition
  //   * It is NOT safe to use FUseCursor=true or the Timer on
  //     secondary threads.
  //   * This processing will only achieve the desired aim of initialising
  //     the session properly for the main thread IF there is a MainForm
  //     defined for the Application - at the time the session is needed.
  //     IF you need the session before the mainform is constructed, or
  //     are not providing a mainform then the application should define
  //     these defaults for itself - if required.
  // Setup this way (in replacement of the previous initialisation section of
  // IB_Components.pas) so that it is possible to avoid creating an IB_Session
  // instance when not required.
  EnterCriticalSection( AttachCS );
  try
    if Assigned( Application ) and Assigned( Application.MainForm ) and
       Application.MainForm.HandleAllocated then
    begin
      if GetWindowThreadProcessID( Application.MainForm.Handle, nil ) =
         GetCurrentThreadID then
      begin
        FUseCursor := true;
        {$IFNDEF CONSOLE}
        SetTimerInterval( 5000 );
        {$ENDIF}
      end;
    end;
  finally
    LeaveCriticalSection( AttachCS );
  end;
end;

destructor TIB_Session.Destroy;
begin
  SetTimerInterval( 0 );
  Destroying;
  while Session_Components.Count > 0 do
    TIB_Component(Session_Components.Items[ 0 ]).IB_Session := nil;
  inherited Destroy;
  if Assigned( FTimerItems ) then FTimerItems.Free;
  if Assigned( Session_Components ) then Session_Components.Free;
  if Assigned( Session_Connections ) then Session_Connections.Free;
  if Assigned( Session_ConnectionLinks ) then Session_ConnectionLinks.Free;
  if Assigned( Session_Transactions ) then Session_Transactions.Free;
  if Assigned( Session_TransactionLinks ) then Session_TransactionLinks.Free;
  if Assigned( Session_Statements ) then Session_Statements.Free;
  if Assigned( Session_Datasets ) then Session_Datasets.Free;
  if Assigned( Session_DataSources ) then Session_DataSources.Free;
  if Assigned( Session_DataLinks ) then Session_DataLinks.Free;
  FTimerItems := nil;
  Session_Components := nil;
  Session_Connections := nil;
  Session_ConnectionLinks := nil;
  Session_Transactions := nil;
  Session_TransactionLinks := nil;
  Session_Statements := nil;
  Session_Datasets := nil;
  Session_DataSources := nil;
  Session_DataLinks := nil;
  DeleteCriticalSection( FTimerCS );

//  try
    FreeMem( FInDa );
    FInCnt := 0;
//  except
  // There is a certain statement type that reveals a bug in the IB API.
  // This bug causes the buffer to get corrupted and an InvalidPointer
  // Operation error happens here when closing. So, I'm preventing it from
  // causing the flow to be interrupted.
//  end;
//  try
    FreeMem( FOutDa );
    FOutCnt := 0;
//  except
  // Same as above.
//  end;
end;

procedure TIB_Session.Loaded;
begin
  inherited Loaded;
  if ComponentIndex <> 0 then
    ComponentIndex := 0;
end;

procedure TIB_Session.Notification( AComponent: TComponent;
                                    Operation: TOperation);
var
  tmpIndex: integer;
begin
  inherited Notification( AComponent, Operation );
  if Operation = opRemove then
  begin
    tmpIndex := FTimerItems.IndexOf( AComponent );
    if tmpIndex >= 0 then
    begin
      FTimerItems.Delete( tmpIndex );
      if ( tmpIndex <= FTimerItemIndex ) and ( FTimerItemIndex > 0 ) then
        Dec( FTimerItemIndex );
    end;
  end;
end;

procedure TIB_Session.WndProc( var AMsg: TMessage );
var
  IsDone, IsWaiting, Terminated: boolean;
begin
  with AMsg do
    if Msg = WM_TIMER then
    begin
      IsDone := true;
      IsWaiting := false;
      Terminated := false;
      ProcessPassiveTasks( IsDone, IsWaiting, Terminated );
    end
    else
      Result := DefWindowProc( FTimerHandle, Msg, wParam, lParam );
end;

procedure TIB_Session.ResetTimerNotification( AComponent: TIB_Component );
var
  tmpBelongs: boolean;
  tmpIndex: integer;
begin
  tmpBelongs := ( not AComponent.ChildOfProcess ) and
                AComponent.NeedTimerNotifications;
  tmpIndex := FTimerItems.IndexOf( AComponent );
  if tmpIndex >= 0 then
  begin
    if ( not tmpBelongs ) then
    begin
      FTimerItems.Delete( tmpIndex );
      if ( tmpIndex <= FTimerItemIndex ) and ( FTimerItemIndex > 0 ) then
        Dec( FTimerItemIndex );
    end;
  end
  else
  if tmpBelongs then
    FTimerItems.Add( AComponent );
end;

procedure TIB_Session.BeginCallbackFreeze;
begin
  Inc( FCallbackFreezeLevel );
end;

procedure TIB_Session.EndCallbackFreeze;
begin
  if FCallbackFreezeLevel > 0 then
    Dec( FCallbackFreezeLevel );
end;

procedure TIB_Session.ProcessPassiveTasks( var IsDone,
                                               IsWaiting,
                                               Terminated: boolean );
var
  tmpIsDone: boolean;
  tmpIsWaiting: boolean;
  tmpTerminated: boolean;
begin
  IsDone := true;
  IsWaiting := false;
  Terminated := false;
  if ( not Assigned( Application )) or Application.Terminated then
  begin
    SetTimerInterval( 0 );
    Exit;
  end;
  if ( not TimerIsBusy ) then
  begin
    FTimerIsBusy := true;
    try
      if FTimerItemIndex = 0 then
      begin
        FTimerIsDone := true;
        FTimerIsWaiting := false;
        FTimerTerminated := false;
      end;
      tmpIsDone := FTimerIsDone;
      tmpIsWaiting := FTimerIsWaiting;
      tmpTerminated := FTimerTerminated;
      if FTimerItemIndex < FTimerItems.Count then
      begin
        TIB_Component(FTimerItems[FTimerItemIndex]).ProcessPassiveTasks(
          tmpIsDone,
          tmpIsWaiting,
          tmpTerminated );
        Inc( FTimerItemIndex );
      end
      else
      begin
        if Assigned( FOnSessionTimer ) then
          FOnSessionTimer( Self, tmpIsDone, tmpIsWaiting, tmpTerminated );
        FTimerItemIndex := 0;
      end;
      if FTimerIsDone then FTimerIsDone := tmpIsDone;
      if ( not FTimerIsWaiting  ) then FTimerIsWaiting  := tmpIsWaiting;
      if ( not FTimerTerminated ) then FTimerTerminated := tmpTerminated;
      IsDone := FTimerIsDone and ( FTimerItemIndex = 0 );
      IsWaiting := FTimerIsWaiting;
      Terminated := FTimerTerminated;
    except
      on E: Exception do
        Application.HandleException( E );
    end;
    FTimerisBusy := false;
  end;
end;

procedure TIB_Session.SetTimerInterval( AValue: cardinal );
begin
  if FTimerInterval <> AValue then
  begin
    if FTimerInterval > 0 then
    begin
      KillTimer( FTimerHandle, 1 );
{$IFDEF IBO_VCL60_OR_GREATER}
      Classes.DeallocateHWnd( FTimerHandle );
{$ELSE}
      DeallocateHWnd( FTimerHandle );
{$ENDIF}
    end;
    if AValue > 0 then
    begin
{$IFDEF IBO_VCL60_OR_GREATER}
      FTimerHandle := Classes.AllocateHWnd( WndProc );
{$ELSE}
      FTimerHandle := AllocateHWnd( WndProc );
{$ENDIF}
      if SetTimer( FTimerHandle, 1, AValue, nil ) = 0 then
      begin
        FTimerInterval := 0;
        raise EOutOfResources.Create( 'SNoTimers' );
      end;
    end;
    FTimerInterval := AValue;
  end;
end;

procedure TIB_Session.DoHandleError(        Sender: TObject;
                                      const errcode: longint;
                                            ErrorMessage,
                                            ErrorCodes: TStringList;
                                      const SQLCODE: longint;
                                            SQLMessage,
                                            SQL: TStringList;
                                        var RaiseException: boolean);
begin
  if Sender is TIB_Component then
    (Sender as TIB_Component).DoHandleError( Sender as TIB_Component,
                                             errcode,
                                             ErrorMessage,
                                             ErrorCodes,
                                             SQLCODE,
                                             SQLMessage,
                                             SQL,
                                             RaiseException );
  if RaiseException then
    inherited DoHandleError( Sender,
                             errcode,
                             ErrorMessage,
                             ErrorCodes,
                             SQLCODE,
                             SQLMessage,
                             SQL,
                             RaiseException );
end;

function TIB_Session.DoAppCallback: boolean;
begin
  Result := true;
  try
    Application.ProcessMessages;
  except
    on E: Exception do
    begin
      Result := false;
      Application.HandleException( E );
    end;
  end;
end;

function TIB_Session.GetConnectionByName(
  const ADBName: string ): TIB_Connection;
var
  ii: integer;
begin
  Result := nil;
  if Assigned( Session_Connections ) then
    for ii := 0 to Session_Connections.Count - 1 do
      with TIB_Connection( Session_Connections.Items[ ii ] ) do
        if CompareText( ADBName, DatabaseName ) = 0 then
        begin
          Result := Session_Connections.Items[ ii ];
          Break;
        end;
end;

procedure TIB_Session.AnnounceConnection;
var
  ii: integer;
begin
  if Session_ConnectionLinks <> nil then
    for ii := 0 to Session_ConnectionLinks.Count - 1 do
      with TIB_ConnectionLink( Session_ConnectionLinks.Items[ ii ] ) do
        if ReceiveFocus then
          DoReceiveFocus( FocusedConnection );
end;

procedure TIB_Session.AnnounceTransaction;
var
  ii: integer;
begin
  if Session_TransactionLinks <> nil then
    for ii := 0 to Session_TransactionLinks.Count - 1 do
      with TIB_TransactionLink( Session_TransactionLinks.Items[ ii ] ) do
        if ReceiveFocus then
          DoReceiveFocus( FocusedTransaction );
end;

procedure TIB_Session.AnnounceDataSource;
var
  ii: integer;
begin
  if Session_DataLinks <> nil then
    for ii := 0 to Session_DataLinks.Count - 1 do
      with TIB_DataLink( Session_DataLinks.Items[ ii ] ) do
        if ReceiveFocus then
          DoReceiveFocus( FocusedDataSource );
end;

{------------------------------------------------------------------------------}

procedure TIB_Session.SetFocusedConnection( C: TIB_Connection );
begin
  if Assigned( C ) and C.FHasNilAncestry then Exit;
  if FocusedConnection <> C then
  begin
    if FocusedConnection <> nil then
      FocusedConnection.DoLoseFocus;
    FFocusedConnection := C;
    if FocusedConnection <> nil then
    begin
      with FocusedConnection do
      begin
        if AnnounceFocus then
          AnnounceConnection;
        DoGainFocus;
      end;
    end
    else
      AnnounceConnection;
  end;
end;

procedure TIB_Session.SetFocusedTransaction( T: TIB_Transaction );
begin
  if Assigned( T ) and T.FHasNilAncestry then Exit;
  if FocusedTransaction <> T then
  begin
    if FocusedTransaction <> nil then
      FocusedTransaction.DoLoseFocus;
    FFocusedTransaction := T;
    if FocusedTransaction <> nil then
    begin
      with FocusedTransaction do
      begin
        if AnnounceFocus then
          AnnounceTransaction;
        DoGainFocus;
      end;
    end
    else
      AnnounceTransaction;
  end;
end;

procedure TIB_Session.SetFocusedDataset( D: TIB_Dataset );
begin
  if Assigned( D ) and D.FHasNilAncestry then Exit;
  if FocusedDataset <> D then
  begin
    FFocusedDataset := D;
    if FocusedDataset <> nil then
    begin
      with FocusedDataset do
      begin
        FocusedConnection := IB_Connection;
        FocusedTransaction := IB_Transaction;
      end;
    end
    else
    begin
      FocusedConnection := nil;
      FocusedTransaction := nil;
    end;
  end;
end;

procedure TIB_Session.SetFocusedDataSource( DS: TIB_DataSource );
begin
  if Assigned( DS ) and
    DS.FHasNilAncestry then Exit;
  if FocusedDataSource <> DS then
  begin
    if FocusedDataSource <> nil then
      FocusedDataSource.DoLoseFocus;
    FFocusedDataSource := DS;
    if FocusedDataSource <> nil then with FocusedDataSource do
    begin
      FocusedDataset := Dataset;
      if AnnounceFocus then
        AnnounceDataSource;
      DoGainFocus;
    end
    else
      AnnounceDataSource;
  end;
end;

procedure TIB_Session.BeginLockCursor;
begin Inc( FLockCursorLevel ); end;
procedure TIB_Session.EndLockCursor;
begin Dec( FLockCursorLevel ); end;
procedure TIB_Session.DoBeginBusy;
begin if Assigned( FOnBeginBusy ) then FOnBeginBusy( Self ); end;
procedure TIB_Session.DoEndBusy;
begin if Assigned( FOnEndBusy ) then FOnEndBusy( Self ); end;
procedure TIB_Session.DoBeginYield;
begin if Assigned( FOnBeginYield ) then FOnBeginYield( Self ); end;
procedure TIB_Session.DoEndYield;
begin if Assigned( FOnEndYield ) then FOnEndYield( Self ); end;
function TIB_Session.GetIsBusy: boolean;
begin Result := ( BusyLevel > 0 ) or TimerIsBusy; end;
function TIB_Session.GetIsYielding: boolean;
begin Result := YieldLevel <> -1; end;

function TIB_Session.GetWakeup: TNotifyEvent;
begin
  EnterCriticalSection( FTimerCS );
  try
    Result := FOnWakeUp;
  finally
    LeaveCriticalSection( FTimerCS );
  end;
end;

procedure TIB_Session.SetWakeup( AValue: TNotifyEvent );
begin
  EnterCriticalSection( FTimerCS );
  try
    FOnWakeUp := AValue;
  finally
    LeaveCriticalSection( FTimerCS );
  end;
end;

procedure TIB_Session.DoWakeUp( Sender: TObject );
begin
  EnterCriticalSection( FTimerCS );
  try
    if Assigned( FOnWakeUp ) then
      FOnWakeUp( Sender );
  finally
    LeaveCriticalSection( FTimerCS );
  end;
  if Assigned( Application ) then
    PostMessage( Application.Handle, WM_ENTERIDLE, 0, 0 );
end;

procedure TIB_Session.DoGetDataLinkColor(     DataLink: TIB_DataLink;
                                          var FieldColor: TColor );
begin
  if Assigned( OnGetDataLinkColor ) then
    OnGetDataLinkColor( Self, DataLink, FieldColor );
end;

{------------------------------------------------------------------------------}

procedure TIB_Session.BeginBusy( Yield: boolean );
begin
  if Yield and ( YieldLevel = -1 ) then
    FYieldLevel := BusyLevel;
  if BusyLevel = 0 then
  begin
    DoBeginBusy;
    if Yield then
      DoBeginYield;
    if UseCursor and ( not TimerIsBusy ) then
    begin
      if ( Screen.Cursor <> BusyCursor ) and
         ( Screen.Cursor <> YieldCursor ) then
        FOldCursor := Screen.Cursor
      else
        FOldCursor := 0{crDefault};
      if LockCursorLevel = 0 then
      begin
        if Yield then
          Screen.Cursor := YieldCursor
        else
          Screen.Cursor := BusyCursor;
      end;
    end
    else
      FOldCursor := -1;
  end
  else
  if BusyLevel = YieldLevel then
  begin
    DoBeginYield;
    if FOldCursor <> -1 then
      if LockCursorLevel = 0 then
        Screen.Cursor := YieldCursor;
  end;
  Inc( FBusyLevel );
end;

procedure TIB_Session.EndBusy;
begin
  if IsBusy then
    Dec( FBusyLevel );
  if YieldLevel = BusyLevel then
  begin
    FYieldLevel := -1;
    if ( FOldCursor <> -1 ) and ( BusyLevel > 0 ) then
      if LockCursorLevel = 0 then
        Screen.Cursor := BusyCursor;
    DoEndYield;
  end;
  if BusyLevel = 0 then
  begin
    if ( FOldCursor <> -1 ) and
       ( Screen.Cursor <> FOldCursor ) then
      Screen.Cursor := FOldCursor;
    DoEndBusy;
  end;
end;

procedure TIB_Session.GetAliasParams( const AliasName: string; List: TStrings );
begin
  GetBDEAliasInfo( 'ALIAS', AliasName, List );
end;
// IBA_Component.INT

constructor TIB_Component.CreateForSession( AOwner: TComponent;
                                            ASession: TIB_Session );
begin
  Create( AOwner );
  SetSession( ASession );
end;

constructor TIB_Component.Create( AOwner: TComponent );
  procedure OwnerIsProcess( const AComp: TComponent );
  begin
    if AComp is TIB_Component then
    begin
      if TIB_Component(AComp).FHasNilAncestry then
        FHasNilAncestry := true;
      if TIB_Component(AComp).FChildOfProcess or
         TIB_Component(AComp).FIsProcess then
        FChildOfProcess := true;
    end;
    if (( not FHasNilAncestry ) or ( not FChildOfProcess )) and
      Assigned( AComp.Owner ) then OwnerIsProcess( AComp.Owner );
  end;
begin
  FIsProcess := false;
  FHasNilAncestry := not Assigned( AOwner );
  FChildOfProcess := false;
  if Assigned( AOwner ) then
    OwnerIsProcess( AOwner );
  inherited Create( AOwner );
  ReserveSessionHookRef;
end;

destructor TIB_Component.Destroy;
begin
  FOnSessionTimer := nil;
  SetSession( nil );
  inherited Destroy;
  ReleaseSessionHookRef;
end;

procedure TIB_Component.Notification( AComponent: TComponent;
                                      Operation: TOperation);
begin
  inherited Notification( AComponent, Operation );
  if ( Operation = opRemove ) and ( AComponent = FIB_Session ) then
    SetSession( nil );
end;

function TIB_Component.GetSession: TIB_Session;
begin
  if not Assigned( FIB_Session ) then
    CheckSession( true );
  Result := FIB_Session;
end;

procedure TIB_Component.SetOnSessionTimer( AValue: TIB_SessionTimerEvent );
begin
  FOnSessionTimer := AValue;
  IB_Session.ResetTimerNotification( Self );
end;

function TIB_Component.NeedTimerNotifications: boolean;
begin
  Result := Assigned( FOnSessionTimer );
end;

procedure TIB_Component.ProcessPassiveTasks( var IsDone,
                                                 IsWaiting,
                                                 Terminate: boolean );
begin
  if Assigned( OnSessionTimer ) then
    OnSessionTimer( Self, IsDone, IsWaiting, Terminate );
end;

procedure TIB_Component.DoWakeUp( Sender: TObject );
begin
  if Assigned( IB_Session ) then
    IB_Session.DoWakeUp( Sender );
end;

function TIB_Component.CheckSession( AllowDefault: boolean ): boolean;
var
  tmpSession: TIB_Session;
begin
  if ( not Assigned( FIB_Session )) and ( not FCheckingSession ) then
    try
      FCheckingSession := true;
      tmpSession := nil;
      FindSession( tmpSession, AllowDefault );
      if Assigned( tmpSession ) then
        SetSession( tmpSession )
      else
      if AllowDefault then
{$IFDEF ALLOW_DEFAULT_SESSION}
        SetSession( TIB_Session.DefaultSession );
{$ELSE}
        raise EIB_Error.CreateWithSender( Self,
                                        E_Default_Session_Capability_Disabled );
{$ENDIF}
    finally
      FCheckingSession := false;
    end;
  Result := Assigned( FIB_Session );
end;

procedure TIB_Component.FindSession( var ASession: TIB_Session;
                                         AllowDefault: boolean );
  procedure GrabSession( const ACmp: TComponent );
  begin
    if ACmp is TIB_Session then
      ASession := TIB_Session( ACmp )
    else
    if ( ACmp <> Self ) and
       ( ACmp is TIB_Component ) then
    begin
      TIB_Component( ACmp ).CheckSession( AllowDefault );
      ASession := TIB_Component( ACmp ).FIB_Session;
    end;
  end;
var
  ii: integer;
  tmpComponent: TComponent;
begin
  tmpComponent := Self;
  while ( not Assigned( ASession )) and Assigned( tmpComponent.Owner ) do
  begin
    GrabSession( tmpComponent.Owner );
    if ( not Assigned( ASession )) then
      for ii := 0 to tmpComponent.Owner.ComponentCount - 1 do
        if tmpComponent.Owner.Components[ ii ] <> tmpComponent then
        begin
          GrabSession( tmpComponent.Owner.Components[ ii ] );
          if Assigned( ASession ) then
            Break;
        end;
    tmpComponent := tmpComponent.Owner;
  end;
end;

procedure TIB_Component.SetSession( ASession: TIB_Session );
var
  tmpBusyLevel: integer;
begin
  if FIB_Session <> ASession then
  begin
    tmpBusyLevel := BusyLevel;
    if Assigned( FIB_Session ) then
    begin
      while BusyLevel > 0 do EndBusy;
      if ( not FHasNilAncestry ) then
        FIB_Session.Session_Components.Remove( Self );
      if InterlockedDecrement( FIB_Session.cntComponents ) = 0 then
        if FIB_Session.FIsDefaultSession then
{$IFDEF ALLOW_DEFAULT_SESSION}
          FreeDefaultSession( FIB_Session );
{$ELSE}
          raise EIB_Error.CreateWithSender( Self,
                                        E_Default_Session_Capability_Disabled );
{$ENDIF}
    end;
    FIB_Session := ASession;
    if Assigned( FIB_Session ) then
    begin
      FIB_Session.FreeNotification( Self );
      InterlockedIncrement( FIB_Session.cntComponents );
      if ( not FHasNilAncestry ) then
        FIB_Session.Session_Components.Add( Self );
      if ( not FIB_Session.FIsDefaultSession ) then
        FIB_Session.FreeNotification( Self );
      while BusyLevel < tmpBusyLevel do BeginBusy( true );
    end;
  end;
end;

procedure TIB_Connection.SetSession( ASession: TIB_Session );
begin
  if FIB_Session <> ASession then
  begin
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        with FIB_Session do
        begin
          if FocusedConnection = Self then
            FocusedConnection := nil;
          if DefaultConnection = Self then
            DefaultConnection := nil;
          Session_Connections.Remove( Self );
        end;
    inherited SetSession( ASession );
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        with FIB_Session do
        begin
          Session_Connections.Add( Self );
          if DefaultConnection = nil then
            DefaultConnection := Self;
        end;
  end;
end;

procedure TIB_ConnectionLink.SetSession( ASession: TIB_Session );
begin
  if FIB_Session <> ASession then
  begin
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        FIB_Session.Session_ConnectionLinks.Remove( Self );
    inherited SetSession( ASession );
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        with FIB_Session do
        begin
          Session_ConnectionLinks.Add( Self );
          if ( not ( csReading in ComponentState )) then
            DoReceiveFocus( IB_Session.FocusedConnection );
        end;
  end;
end;

procedure TIB_Transaction.SetSession( ASession: TIB_Session );
begin
  if FIB_Session <> ASession then
  begin
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        with FIB_Session do
        begin
          if FocusedTransaction = Self then
            FocusedTransaction := nil;
          Session_Transactions.Remove( Self );
        end;
    inherited SetSession( ASession );
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        FIB_Session.Session_Transactions.Add( Self );
  end;
end;

procedure TIB_TransactionLink.SetSession( ASession: TIB_Session );
begin
  if FIB_Session <> ASession then
  begin
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        FIB_Session.Session_TransactionLinks.Remove( Self );
    inherited SetSession( ASession );
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        with FIB_Session do
        begin
          Session_TransactionLinks.Add( Self );
          if not ( csReading in ComponentState ) then
            DoReceiveFocus( FIB_Session.FocusedTransaction );
        end;
  end;
end;

procedure TIB_Statement.SetSession( ASession: TIB_Session );
begin
  if FIB_Session <> ASession then
  begin
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        FIB_Session.Session_Statements.Remove( Self );
    inherited SetSession( ASession );
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        FIB_Session.Session_Statements.Add( Self );
  end;
end;

procedure TIB_Dataset.SetSession( ASession: TIB_Session );
begin
  if FIB_Session <> ASession then
  begin
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        with FIB_Session do
        begin
          if FocusedDataset = Self then
            FocusedDataset := nil;
          Session_Datasets.Remove( Self );
        end;
    inherited SetSession( ASession );
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        FIB_Session.Session_Datasets.Add( Self );
  end;
end;

procedure TIB_DataSource.SetSession( ASession: TIB_Session );
begin
  if FIB_Session <> ASession then
  begin
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        with FIB_Session do
        begin
          if FocusedDataSource = Self then
            FocusedDataSource := nil;
          Session_DataSources.Remove( Self );
        end;
    inherited SetSession( ASession );
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
        FIB_Session.Session_DataSources.Add( Self );
  end;
end;

procedure TIB_DataLink.SetSession( ASession: TIB_Session );
begin
  if FIB_Session <> ASession then
  begin
    if Assigned( FIB_Session ) then
      if ( not FHasNilAncestry ) then
      begin
        UnlockSessionCursor;
        with FIB_Session do
        begin
          if Session_DataLinks <> nil then
          begin
            Session_DataLinks.Remove( Self );
            if Session_DataLinks.Count = 0 then
            begin
              Session_DataLinks.Free;
              Session_DataLinks := nil;
            end;
          end;
        end;
      end;
    inherited SetSession( ASession );
    if Assigned( FIB_Session ) then
    begin
      if ( not FHasNilAncestry ) then
        with FIB_Session do
        begin
          if Session_DataLinks = nil then
            Session_DataLinks := TList.Create;
          Session_DataLinks.Add( Self );
          if ( not ( csReading in ComponentState )) then
            Self.DoReceiveFocus( FocusedDataSource );
        end;
    end;
  end;
end;

function TIB_Component.IsSessionStored: boolean;
begin
  Result := Assigned( FIB_Session ) and ( not FIB_Session.FIsDefaultSession );  
end;

procedure TIB_Component.BeginBusy( Yield: boolean );
begin
  CheckSession( true );
  if Assigned( FIB_Session ) and ( not FHasNilAncestry ) then
    FIB_Session.BeginBusy( Yield );
  Inc( FBusyLevel );
end;

procedure TIB_Component.EndBusy;
begin
  if Assigned( FIB_Session ) and ( not FHasNilAncestry ) then
    FIB_Session.EndBusy;
  if FBusyLevel > 0 then
    Dec( FBusyLevel );
end;

function TIB_Component.GetVersion: string;
begin
  Result := IBO_VERSION_MAJOR + '.' +
            IBO_VERSION_MINOR + '.' +
            IBO_VERSION_BUILD +
            IBO_VERSION_SUB_RELEASE;
end;

procedure TIB_Component.SetVersion( const AValue: string );
begin
end;

procedure TIB_Component.SetOnError( AValue: TIB_ErrorEvent );
begin
  FOnError := AValue;
end;

procedure TIB_Component.DoHandleError(       Sender: TObject;
                                       const errcode: isc_long;
                                             ErrorMessage,
                                             ErrorCodes: TStringList;
                                       const SQLCODE: isc_long;
                                             SQLMessage,
                                             SQL: TStringList;
                                         var RaiseException: boolean);
begin
  if Assigned( OnError ) then
    FOnError( Self,
              errcode,
              ErrorMessage,
              ErrorCodes,
              SQLCODE,
              SQLMessage,
              SQL,
              RaiseException );
end;

// IBA_SchemaCache.INT
// IB_Schema

constructor TIB_SchemaCache.Create( AConnection: TIB_Connection );
begin
  inherited Create;
  FTransaction := TIB_TransactionInternal.Create( AConnection );
  FTransaction.IB_Connection := AConnection;
  FValidInfoFlags := [];
end;

destructor TIB_SchemaCache.Destroy;
var
  ii: TIB_SchemaCacheItems;
begin
  FreeResources;
  for ii := low( TIB_SchemaCacheItems ) to high( TIB_SchemaCacheItems ) do
    if Assigned( FValueLists[ ii ] ) then
      FValueLists[ ii ].Free;
  FTransaction.Free;
  FTransaction := nil;
  inherited Destroy;
end;

procedure TIB_SchemaCache.CheckTransactionEnded;
begin
  if Assigned( Transaction ) then
    if Transaction.IB_Session.TimerInterval = 0 then
      if Transaction.Started then
        Transaction.Commit;
end;

procedure TIB_SchemaCache.FreeResources;
begin
  if Assigned( FDomainNameCursor ) then
  begin
    FDomainNameCursor.Free;
    FDomainNameCursor := nil;
  end;
  if Assigned( FTableKeyCursor ) then
  begin
    FTableKeyCursor.Free;
    FTableKeyCursor := nil;
  end;
  if Assigned( FTableFieldsCursor ) then
  begin
    FTableFieldsCursor.Free;
    FTableFieldsCursor := nil;
  end;
  if Assigned( FDomainSourceCursor ) then
  begin
    FDomainSourceCursor.Free;
    FDomainSourceCursor := nil;
  end;
  if Assigned( FFieldSourceCursor ) then
  begin
    FFieldSourceCursor.Free;
    FFieldSourceCursor := nil;
  end;
  if Assigned( FProcedureParamsCursor ) then
  begin
    FProcedureParamsCursor.Free;
    FProcedureParamsCursor := nil;
  end;
  if Assigned( FProcParamsAttrCursor ) then
  begin
    FProcParamsAttrCursor.Free;
    FProcParamsAttrCursor := nil;
  end;
  if Transaction.Started then
    Transaction.Rollback;
end;

function TIB_SchemaCache.GetConnection: TIB_Connection;
begin
  Result := FTransaction.IB_Connection;
end;

procedure TIB_SchemaCache.CheckSchemaVersionTable;
var
  TmpItem: TIB_SchemaCacheItems;
begin
  with TIB_DSQL.Create( Connection ) do
  try
    RetrieveDomainNames := false;
    ParamCheck := false;
    IB_Connection := Connection;
    try
      if CheckConnection( true ) and CheckTransaction( true ) then
      begin
        // Note: Use ExecuteImmediate() here to avoid IBO's automatic
        //       detection of DDL statement execution.
        ExecuteImmediate( 'CREATE TABLE IBO$SCHEMA_VERSION ('#13#10 +
                          '  SCHEMA_ITEM VARCHAR(31) NOT NULL,'#13#10 +
                          '  VERSION_NUMBER INTEGER NOT NULL,'#13#10 +
                          '  PRIMARY KEY (SCHEMA_ITEM) )', nil );
        IB_Transaction.Activate;
        ExecuteImmediate( 'GRANT ALL ON IBO$SCHEMA_VERSION TO PUBLIC', nil );
        IB_Transaction.Activate;
      end;
    except
      // Ignore an exception here.
    end;
    SQL.Clear;
    SQL.Add( 'INSERT INTO IBO$SCHEMA_VERSION( SCHEMA_ITEM, VERSION_NUMBER )' );
    SQL.Add( 'VALUES (' );
    SQL.Add( '' );
    SQL.Add( ', 0)' );
    for TmpItem := Low(TIB_SchemaCacheItems) to High(TIB_SchemaCacheItems) do
    begin
      if TmpItem in [ sciRelationsByID,
                      sciDatabaseSchemaVer,
                      sciClientSchemaVer ] then
        Continue;  // Skip these items (not under version control).
      SQL.Strings[2] := '''' + GetItemName( TmpItem ) + '''';
      try
        Execute;
      except
        // Ignore an exception here.
      end;
    end;
  finally
    Free;
  end;
  CheckTransactionEnded;
end;

procedure TIB_SchemaCache.DeleteLocalFiles;
var
  TmpItem: TIB_SchemaCacheItems;
begin
  if LocalDir <> '' then
    for TmpItem := Low(TIB_SchemaCacheItems) to High(TIB_SchemaCacheItems) do
      SysUtils.DeleteFile( GetLocalFileName( TmpItem ));
  CheckTransactionEnded;
end;

procedure TIB_SchemaCache.SetLocalDir( const AValue: string );
var
  ii: integer;
  Done: boolean;
begin
  FLocalDir := Trim( AValue );
  repeat
    Done := true;
    ii := Length( FLocalDir );
    if ( ii > 0 ) and ( FLocalDir[ ii ] = '\' ) then begin
      Delete( FLocalDir, ii, 1 );
      Done := false;
    end;
  until Done;
end;

function TIB_SchemaCache.GetGDBFilename: string;
var
  tmpPos: integer;
  ii: Integer;
begin
  Result := '';
  if Assigned( Connection ) then
  begin
    ii := LastDelimiter( '/\:', Connection.Path );
    Result := Copy( Connection.Path, ii + 1, MaxInt );
    tmpPos := Pos( '.', Result );
    if tmpPos > 0 then
      Result := Copy( Result, 1, tmpPos - 1 );
  end;
end;

function TIB_SchemaCache.GetItemName( ALst: TIB_SchemaCacheItems ): string;
begin
  case ALst of
    sciDomainNames:       Result := 'DomainNames';
    sciGeneratorNames:    Result := 'GeneratorNames';
    sciTableNames:        Result := 'TableNames';
    sciViewNames:         Result := 'ViewNames';
    sciProcedureNames:    Result := 'ProcedureNames';
    sciConstraintNames:   Result := 'ConstraintNames';
    sciTriggerNames:      Result := 'TriggerNames';
    sciIndexDefs:         Result := 'IndexDefs';
    sciIndexNames:        Result := 'IndexNames';
    sciPrimaryKeys:       Result := 'PrimaryKeys';
    sciForeignKeys:       Result := 'ForeignKeys';
    sciRequired:          Result := 'Required';
    sciDefaults:          Result := 'Defaults';
    sciComputed:          Result := 'Computed';
    sciFieldDomains:      Result := 'FieldDomains';
    sciTableKeys:         Result := 'TableKeys';
    sciTableFields:       Result := 'TableFields';
    sciProcedureParams:   Result := 'ProcedureParams';
    sciRelationsByID:     Result := 'RelationsByID';
    sciDatabaseSchemaVer: Result := 'DatabaseSchemaVer';
    sciClientSchemaVer:   Result := 'ClientSchemaVer';
    else                  Result := '';
  end;
end;

function TIB_SchemaCache.GetLocalFileName( ALst: TIB_SchemaCacheItems ): string;
begin
  ForceDirectories( LocalDir + '\' + GDBFilename );
  Result := LocalDir + '\' + GDBFilename + '\' + GetItemName( ALst ) + '.IBO';
end;

function TIB_SchemaCache.GetSchemaCacheList( AList: integer ): TIB_StringList;
var
  ALst: TIB_SchemaCacheItems absolute AList;
  function SchemaVersionIsCurrent: boolean;
  var
    TmpItemName: string;
    DatabaseVersion, ClientVersion: string;
  begin
    Result := false;
    // Note: Always load sciRelationsByID directly from the database, as the
    //       RDB$RELATION_ID values could change due to backup/restore, etc.
    if ALst in [ sciProcedureParams,
                 sciRelationsByID,
                 sciDatabaseSchemaVer ] then
    begin
      Exit;  // Above items are never current, always reload from database.
    end;
    if FileExists( GetLocalFileName( ALst )) then
    begin
      if ALst = sciClientSchemaVer then
        Result := true  // Always current (special file to hold version info).
      else
      with DatabaseSchemaVer do
      begin
        if Count > 0 then
        begin
          TmpItemName := GetItemName( ALst );
          // Note: When an item is not found in DatabaseSchemaVer, return
          //       failsafe Result=false to ensure that the missing item does
          //       not prevent loading of new or modified metadata.
          if FindValue( TmpItemName, DatabaseVersion )
              and ClientSchemaVer.FindValue( TmpItemName, ClientVersion ) then
            Result := AnsiCompareStr( DatabaseVersion, ClientVersion ) = 0;
        end
        else
          // Note: No version info in database, so assume version is current.
          //       This behaviour is the default when the IBO$SCHEMA_VERSION
          //       table is not present in the database, or has no records.
          Result := true;
      end;
    end;
  end;
  function GetLocalFile( AStrings: TStrings ): boolean;
  begin
    Result := SchemaVersionIsCurrent;
    if Result then
      AStrings.LoadFromFile( GetLocalFileName( ALst ));
  end;
  procedure SetLocalFile( AStrings: TStrings );
  var
    TmpItemName: string;
    TmpVersion: string;
    TmpIndex: integer;
  begin
    if ALst in [ sciProcedureParams,
                 sciRelationsByID,
                 sciDatabaseSchemaVer,
                 sciClientSchemaVer ] then
    begin
      Exit;  // Don't save item to file here.
    end;
    try
      {Save current list to a local file...}
      AStrings.SaveToFile( GetLocalFileName( ALst ));
      {Keep track of the schema version info...}
      TmpVersion := '';
      TmpItemName := GetItemName( ALst );
      with DatabaseSchemaVer do
      begin
        if Count > 0 then
          FindValue( TmpItemName, TmpVersion );
      end;
      with ClientSchemaVer do
      begin
        if FindIndex( TmpItemName, TmpIndex ) then
          IndexValues[TmpIndex] := TmpVersion
        else
          Add( TmpItemName + '=' + TmpVersion );
        SaveToFile( GetLocalFileName( sciClientSchemaVer ));
      end;
    except
      // Ignore an exception here.
    end;
  end;
begin
  if ( not Assigned( FValueLists[ ALst ] )) then
  begin
    if ALst in [ sciFieldDomains,
                 sciTableKeys,
                 sciTableFields,
                 sciProcedureParams ] then
    begin
      FValueLists[ ALst ] := TIB_StringList.Create;
      FValueLists[ ALst ].Sorted := true;
      FValueLists[ ALst ].Duplicates := dupIgnore;
    end
    else
      FValueLists[ ALst ] := TIB_StringCache.Create;
  end;
  Result := FValueLists[ ALst ];
  if ( not ( ALst in FValidInfoFlags )) then
  begin
    // Avoid an infinite loop with profile info via SQL Monitor.  When profile
    // info is being gathered by the SQL Monitor, sciRelationsByID is accessed
    // recursively due to API calls triggering further profiling.  Setting the
    // sciRelationsByID list to valid here allows an empty list to be returned
    // until the original call grabs the list contents.  This means that
    // TIB_Profiler.TableDescription will return default output 'RELATION_ID='
    // until the list data is loaded by the original call.
    if ALst = sciRelationsByID then
      FValidInfoFlags := FValidInfoFlags + [ sciRelationsByID ];
    Result.BeginUpdate;
    try
      Result.Clear;
      if Connection.Connected then
      begin
        if ( LocalDir = '' ) or ( not GetLocalFile( Result )) then
        begin
          case ALst of
            sciDomainNames:
              SchemaDomainNames( Connection, nil, false, Result );
            sciGeneratorNames:
              SchemaGeneratorNames( Connection, nil, false, Result );
            sciTableNames:
              SchemaRelationNames( Connection, nil, false, true, false, Result);
            sciViewNames:
              SchemaRelationNames( Connection, nil, false, false, true, Result);
            sciProcedureNames:
              SchemaProcedureInfo( Connection, nil, Result );
            sciConstraintNames:
              SchemaConstraintInfo( Connection, nil, false, Result, '' );
            sciTriggerNames:
              SchemaTriggerInfo( Connection, nil, false, Result, '' );
            sciIndexDefs:
              SchemaIndexDefinitions( Connection, nil, false, Result );
            sciIndexNames:
              SchemaIndexInfo( Connection, nil, false, Result, '' );
            sciPrimaryKeys:
              SchemaPrimaryKeyInfo( Connection, nil, '', Result );
            sciForeignKeys:
              SchemaForeignKeyInfo( Connection, nil, '', false, Result );
            sciRequired:
              SchemaRequiredCols( Connection, nil, false, '', Result );
            sciDefaults:
              SchemaDefaultedCols( Connection, nil, false, true, '', Result );
            sciComputed:
              SchemaComputedCols( Connection, nil, false, '', Result );
            sciFieldDomains:
              SchemaFieldDomains( Connection, nil, Result );
            sciTableKeys:
              SchemaPrimaryKeyInfo( Connection, nil, '<BY TABLE>', Result );
            sciTableFields:
              SchemaFieldNames( Connection, nil, Result, '<BY TABLE>' );
            sciProcedureParams: {not loaded here};
            sciRelationsByID:
              SchemaRelationsByID( Connection, nil, true, Result );
            sciDatabaseSchemaVer:
              SchemaVersionInfo( Connection, nil, Result );
            sciClientSchemaVer: {never loaded from database};
          end;
          if LocalDir <> '' then
            SetLocalFile( Result );
        end;
        FValidInfoFlags := FValidInfoFlags + [ ALst ];
      end;
    finally
      Result.EndUpdate;
    end;
  end;
  CheckTransactionEnded;
end;

procedure TIB_SchemaCache.LoadRelationsByIDInfo;
begin
  GetSchemaCacheList( integer( sciRelationsByID ));
end;

{------------------------------------------------------------------------------}

procedure TIB_SchemaCache.InvalidateAllItems;
begin
  FValidInfoFlags := [];
end;

{------------------------------------------------------------------------------}

function TIB_SchemaCache.GetDomainName( const ARelName,
                                              ASQLName: string ): string;
begin
  if ( not FieldDomains.FindValue( Trim( ARelName ) + '.' +
                                   Trim( ASQLName ), Result )) then
    if LocalDir = '' then
      if ProcedureNames.IndexOf( ARelName ) = -1 then
        with DomainNameCursor do
        begin
          Params[0].AsString := ARelName;
          Params[1].AsString := ASQLName;
          First;
          Result := Trim( Fields[0].AsString );
          FieldDomains.Add( Trim( ARelName ) + '.' +
                            Trim( ASQLName ) + '=' + Result );
          Close;
        end;
  CheckTransactionEnded;
end;

procedure TIB_SchemaCache.GetTableKeys( const TableName: string;
                                        const AKeys: TStrings );
var
  tmpStr: string;
  ii: integer;
  ts: TIB_StringList;
begin
  AKeys.Clear;
  if TableKeys.FindValue( TableName, tmpStr ) then
  begin
    ts := TIB_StringList.Create;
    try
      ts.SafeCommaText := tmpStr;
      AKeys.Assign( ts );
    finally
      ts.Free;
    end;
    for ii := 0 to AKeys.Count - 1 do
      AKeys[ii] := TableName + '.' + AKeys[ii];
  end
  else
  if ( LocalDir = '' ) and ( ProcedureNames.LinkIndex[ TableName ] = -1 ) then
  begin
    tmpStr := '';
    with TableKeyCursor do
    begin
      Params[0].AsString := TableName;
      First;
      while ( not Eof ) do
      begin
        AKeys.Add( IB_Connection.mkIdent( TrimRight( Fields[0].AsString )));
        if Length( tmpStr ) > 0 then
          tmpStr := tmpStr + ',';
        tmpStr := tmpStr + IB_Connection.mkIdent(
                                               TrimRight( Fields[0].AsString ));
        Next;
      end;
      Close;
    end;
    TableKeys.Add( TableName + '=' + tmpStr );
  end;
  CheckTransactionEnded;
end;

procedure TIB_SchemaCache.GetTableFields( const TableName: string;
                                          const AFields: TStrings );
var
  tmpStr: string;
  ts: TIB_StringList;
begin
  AFields.BeginUpdate;
  try
    AFields.Clear;
    if TableFields.FindValue( TableName, tmpStr ) then
    begin
      ts := TIB_StringList.Create;
      try
        ts.SafeCommaText := tmpStr;
        AFields.Assign( ts );
      finally
        ts.Free;
      end;
    end
    else
    if ( LocalDir = '' ) and
       ( ProcedureNames.IndexOf( TableName ) = -1 ) then
    begin
      tmpStr := '';
      with TableFieldsCursor do
      begin
        Params[0].AsString := TableName;
        First;
        while ( not Eof ) do
        begin
          AFields.Add( IB_Connection.mkIdent( TrimRight( Fields[0].AsString )));
          if Length( tmpStr ) > 0 then
            tmpStr := tmpStr + ',';
          tmpStr := tmpStr + IB_Connection.mkIdent(
                                               TrimRight( Fields[0].AsString ));
          Next;
        end;
        Close;
        TableFields.Add( IB_Connection.mkIdent(
                                       TrimRight( TableName )) + '=' + tmpStr );
      end;
    end;
  finally
    AFields.EndUpdate;
  end;
  CheckTransactionEnded;
end;

function TIB_SchemaCache.GetDomainNameCursor: TIB_Dataset;
begin
  if ( not Assigned( FDomainNameCursor )) then
  begin
    FDomainNameCursor := TIB_Dataset.Create( Connection );
    FDomainNameCursor.FRetrieveDomainNames := false;  // Avoid recursion.
    with FDomainNameCursor do
    begin
      IB_Connection := Connection;
      IB_Transaction := Transaction;
      OnError := Self.DoHandleError;
      SQL.Add( 'SELECT RDB$FIELD_SOURCE' );
      SQL.Add( 'FROM RDB$RELATION_FIELDS' );
      SQL.Add( 'WHERE RDB$RELATION_NAME = ?RDB$RELATION_NAME' );
      SQL.Add( '  AND RDB$FIELD_NAME = ?RDB$FIELD_NAME' );
    end;
  end;
  FDomainNameCursor.Prepared := true;
  Result := FDomainNameCursor;
  CheckTransactionEnded;
end;

function TIB_SchemaCache.GetTableKeyCursor: TIB_Dataset;
begin
  if ( not Assigned( FTableKeyCursor )) then
  begin
    FTableKeyCursor := TIB_Dataset.Create( Connection );
    with FTableKeyCursor do
    begin
      IB_Connection := Connection;
      IB_Transaction := Transaction;
      OnError := Self.DoHandleError;
      SQL.Add( 'SELECT S.RDB$FIELD_NAME' );
      SQL.Add( 'FROM RDB$RELATION_CONSTRAINTS C' );
      SQL.Add( '   , RDB$INDICES I' );
      SQL.Add( '   , RDB$INDEX_SEGMENTS S' );
      SQL.Add( 'WHERE C.RDB$CONSTRAINT_TYPE = ''PRIMARY KEY''' );
      SQL.Add( '  AND C.RDB$RELATION_NAME = I.RDB$RELATION_NAME' );
      SQL.Add( '  AND C.RDB$INDEX_NAME = I.RDB$INDEX_NAME' );
      SQL.Add( '  AND I.RDB$INDEX_NAME = S.RDB$INDEX_NAME' );
      SQL.Add( '  AND I.RDB$RELATION_NAME = ?RDB$RELATION_NAME' );
      SQL.Add( 'ORDER BY S.RDB$FIELD_POSITION' );
    end;
  end;
  FTableKeyCursor.Prepared := true;
  Result := FTableKeyCursor;
  CheckTransactionEnded;
end;

function TIB_SchemaCache.GetTableFieldsCursor: TIB_Dataset;
begin
  if ( not Assigned( FTableFieldsCursor )) then
  begin
    FTableFieldsCursor := TIB_Dataset.Create( Connection );
    with FTableFieldsCursor do
    begin
      IB_Connection := Connection;
      IB_Transaction := Transaction;
      OnError := Self.DoHandleError;
      SQL.Add( 'SELECT RDB$FIELD_NAME' );
      SQL.Add( 'FROM RDB$RELATION_FIELDS' );
      SQL.Add( 'WHERE RDB$RELATION_NAME = ?RDB$RELATION_NAME' );
    end;
  end;
  FTableFieldsCursor.Prepared := true;
  Result := FTableFieldsCursor;
  CheckTransactionEnded;
end;

function TIB_SchemaCache.GetFieldSourceCursor: TIB_Dataset;
begin
  if ( not Assigned( FFieldSourceCursor )) then
  begin
    FFieldSourceCursor := TIB_Dataset.Create( Connection );
    with FFieldSourceCursor do begin
      IB_Connection := Connection;
      IB_Transaction := Transaction;
      OnError := Self.DoHandleError;
      SQL.Add( 'SELECT RDB$DEFAULT_SOURCE' );
      SQL.Add( 'FROM RDB$RELATION_FIELDS' );
      SQL.Add( 'WHERE RDB$DEFAULT_SOURCE IS NOT NULL' );
      SQL.Add( '  AND RDB$RELATION_NAME = ?RDB$RELATION_NAME' );
      SQL.Add( '  AND RDB$FIELD_NAME = ?RDB$FIELD_NAME' );
    end;
  end;
  FFieldSourceCursor.Prepared := true;
  Result := FFieldSourceCursor;
  CheckTransactionEnded;
end;

function TIB_SchemaCache.GetDomainSourceCursor: TIB_Dataset;
begin
  if ( not Assigned( FDomainSourceCursor )) then
  begin
    FDomainSourceCursor := TIB_Dataset.Create( Connection );
    with FDomainSourceCursor do
    begin
      IB_Connection := Connection;
      IB_Transaction := Transaction;
      OnError := Self.DoHandleError;
      SQL.Add( 'SELECT F.RDB$DEFAULT_SOURCE' );
      SQL.Add( 'FROM RDB$FIELDS F' );
      SQL.Add( 'JOIN RDB$RELATION_FIELDS R' );
      SQL.Add( '  ON F.RDB$FIELD_NAME = R.RDB$FIELD_SOURCE' );
      SQL.Add( 'WHERE R.RDB$RELATION_NAME = ?RDB$RELATION_NAME' );
      SQL.Add( '  AND R.RDB$FIELD_NAME = ?RDB$FIELD_NAME' );
      SQL.Add( '  AND F.RDB$DEFAULT_SOURCE IS NOT NULL' );
    end;
  end;
  FDomainSourceCursor.Prepared := true;
  Result := FDomainSourceCursor;
  CheckTransactionEnded;
end;

procedure TIB_SchemaCache.DoHandleError( Sender: TObject;
  const ERRCODE: longint; ErrorMessage, ErrorCodes: TStringList;
  const SQLCODE: longint; SQLMessage, SQL: TStringList;
    var RaiseException: boolean );
begin
  RaiseException := false;
{$IFDEF IBO_VCL30_OR_GREATER}
  if ( Sender <> FDomainNameCursor ) and
     ( ERRCODE <> isc_network_error ) then
  begin
    Assert( false, 'Internal SchemaCache ERRCODE: ' + IntToStr( ERRCODE ) );
  end;
{$ENDIF}
end;

function TIB_SchemaCache.GetProcedureParamsCursor: TIB_Dataset;
begin
  if ( not Assigned( FProcedureParamsCursor )) then
  begin
    FProcedureParamsCursor := TIB_Dataset.Create( Connection );
    with FProcedureParamsCursor do
    begin
      IB_Connection := Connection;
      IB_Transaction := Transaction;
      OnError := Self.DoHandleError;
      SQL.Add( 'SELECT RDB$PARAMETER_NAME' );
      SQL.Add( 'FROM RDB$PROCEDURE_PARAMETERS' );
      SQL.Add( 'WHERE RDB$PARAMETER_TYPE = ?RDB$PARAMETER_TYPE' );
      SQL.Add( '  AND RDB$PROCEDURE_NAME = ?RDB$PROCEDURE_NAME' );
      SQL.Add( 'ORDER BY RDB$PARAMETER_NUMBER' );
    end;
  end;
  if Connection.Connected then
    FProcedureParamsCursor.Prepared := true;
  Result := FProcedureParamsCursor;
  CheckTransactionEnded;
end;

function TIB_SchemaCache.GetProcParamsAttrCursor: TIB_Dataset;
begin
  if ( not Assigned( FProcParamsAttrCursor )) then
  begin
    FProcParamsAttrCursor := TIB_Dataset.Create( Connection );
    with FProcParamsAttrCursor do
    begin
      IB_Connection := Connection;
      IB_Transaction := Transaction;
      OnError := Self.DoHandleError;
      SQL.Add( 'select f.rdb$default_source' );
      SQL.Add( 'from rdb$procedure_parameters pp' );
      SQL.Add( 'join rdb$fields f' );
      SQL.Add( '  on f.rdb$field_name = pp.rdb$field_source' );
      SQL.Add( 'where pp.rdb$procedure_name = ?procedure_name' );
      SQL.Add( '  and pp.rdb$parameter_name = ?parameter_name' );
    end;
  end;
  if Connection.Connected then
    FProcParamsAttrCursor.Prepared := true;
  Result := FProcParamsAttrCursor;
  CheckTransactionEnded;
end;

procedure TIB_SchemaCache.GetProcedureParamNames( const AProcName: string;
                                                  const AParamInput: boolean;
                                                        AStrings: TStrings );
var
  tmpProc: string;
  tmpStr: string;
  tmpCol: TIB_Column;
  ts: TIB_StringList;
begin
  ts := TIB_StringList.Create;
  try
    if IsLitCriteria( AProcName, '"' ) then
      tmpProc := AProcName
    else
      tmpProc := UpperCase( AProcName );
    if AParamInput then
      tmpProc := Trim( tmpProc ) + '%IN'
    else
      tmpProc := Trim( tmpProc ) + '%OUT';
    if ProcedureParams.FindValue( tmpProc, tmpStr ) then
      ts.SafeCommaText := tmpStr
    else
    if Connection.Connected then
      try
        with GetProcedureParamsCursor do
        begin
          tmpCol := FieldByName( 'RDB$PARAMETER_NAME' );
          if AParamInput then
            Params[0].AsSmallint := 0
          else
            Params[0].AsSmallint := 1;
          if IsLitCriteria( AProcName, '"' ) then
            Params[1].AsString := stLitCriteria( AProcName )
          else
            Params[1].AsString := AnsiUpperCase( stLitCriteria( AProcName ));
          First;
          while ( not Eof ) do
          begin
            ts.Add( IB_Connection.mkIdent( tmpCol.AsString ));
            Next;
          end;
          Close;
        end;
      finally
        ProcedureParams.Add( tmpProc + '=' + ts.SafeCommaText );
      end;
    AStrings.Assign( ts );
  finally
    ts.Free;
  end;
  CheckTransactionEnded;
end;

procedure TIB_SchemaCache.GetProcParamAttr( const AProcName,
                                                  AParamName: string;
                                                  AStrings: TStrings );
var
  tmpProc: string;
  tmpParam: string;
  ts: TIB_StringList;
begin
  ts := TIB_StringList.Create;
  try
    if IsLitCriteria( AProcName, '"' ) then
      tmpProc := AProcName
    else
      tmpProc := UpperCase( AProcName );
    if IsLitCriteria( AParamName, '"' ) then
      tmpParam := AParamName
    else
      tmpParam := UpperCase( AParamName );
    if Connection.Connected then
      with GetProcParamsAttrCursor do
      begin
        Close;
        Params[0].AsString := tmpProc;
        Params[1].AsString := tmpParam;
        First;
        ts.LinkValues['DEFAULT_SOURCE'] := Fields[0].AsString;
        Close;
      end;
    AStrings.Assign( ts );
  finally
    ts.Free;
  end;
  CheckTransactionEnded;
end;

// IBA_DMLCache.INT

constructor TIB_TransactionDMLCache.Create( ATransaction: TIB_Transaction );
begin
  inherited Create( TIB_DMLCacheItem );
  FTransaction := ATransaction;
end;

destructor TIB_TransactionDMLCache.Destroy;
begin
  inherited Destroy;
end;

function TIB_TransactionDMLCache.Add: TIB_DMLCacheItem;
begin
  Result := TIB_DMLCacheItem( inherited Add );
end;

function TIB_TransactionDMLCache.GetDMLItem( Index: integer ): TIB_DMLCacheItem;
begin
  Result := TIB_DMLCacheItem( inherited Items[Index] );
end;

procedure TIB_TransactionDMLCache.SetDMLItem( Index: Integer;
                                              Value: TIB_DMLCacheItem );
begin
  TIB_DMLCacheItem( inherited Items[Index] ).Assign( Value );
end;

procedure TIB_TransactionDMLCache.AddItem(
  AConnection: TIB_Connection;
  ADataset: TIB_Dataset;
  AKeyFieldNames: string;
  AKeyFieldValues: variant;
  ADMLCacheItemType: TIB_DMLCacheItemType );
var
  tmpItem: TIB_DMLCacheItem;
begin
  tmpItem := Add;
  with tmpItem do
  begin
    if Assigned( ADataset ) then
      FConnection := ADataset.IB_Connection
    else
      FConnection := AConnection;
    FKeyFieldNames := AKeyFieldNames;
    FKeyFieldValues := AKeyFieldValues;
    FDMLCacheItemType := ADMLCacheItemType;
  end;
  if ( not Assigned( ADataset )) then
  begin
  // This item is from an external source and should be processed immediatly.
    try
      tmpItem.AnnounceToConnection;
    except
      on E: Exception do
        Application.HandleException( E );
    end;
    tmpItem.Free;
  end
  else
  if Assigned( Transaction ) then
  begin
  // This item is from an internal source and should be processed immediatly
  // within its own transaction but cached until committed before being
  // processed for the rest of the datasets in the connection.
    tmpItem.AnnounceToTransaction( Transaction, ADataset );
  end;
end;

procedure TIB_TransactionDMLCache.ProcessItems( Announce: boolean );
begin
  while Count > 0 do
    if Announce then
      ProcessItem( Items[0] )
    else
      Items[0].Free;
end;

procedure TIB_TransactionDMLCache.ProcessItem( const AItem: TIB_DMLCacheItem );
var
  ii: integer;
  tmpDataset: TIB_Dataset;
begin
  with AItem do
  begin
    Collection := nil;
    if Assigned( Connection ) then
    try
      if Assigned( Transaction ) then
      begin
        if Transaction.GetConnectionIndex( Connection ) <> - 1 then
        begin
          for ii := 0 to Connection.DatasetCount - 1 do
          begin
            tmpDataset := Connection.Datasets[ ii ];
            if ( Assigned( tmpDataset )) and
               ( tmpDataset.Active ) and
               ( tmpDataset.IB_Transaction <> Transaction ) and
               ( tmpDataset.IB_Transaction.Isolation = tiCommitted ) then
              AnnounceToDataset( tmpDataset );
          end;
        end;
      end;
    except
      on E: Exception do
        Application.HandleException( E );
    end;
    Free;
  end;
end;

{---}

procedure TIB_DMLCacheItem.AnnounceToConnection;
var
  ii: integer;
  tmpDataset: TIB_Dataset;
begin
  if Assigned( Connection ) then with Connection do
  begin
    for ii := 0 to DatasetCount - 1 do
    begin
      tmpDataset := Datasets[ ii ];
      if ( Assigned( tmpDataset )) and
         ( tmpDataset.Active ) and
         ( tmpDataset.IB_Transaction.Isolation = tiCommitted ) then
        AnnounceToDataset( tmpDataset );
    end;
  end;
end;

procedure TIB_DMLCacheItem.AnnounceToDataset( const DS: TIB_Dataset );
begin
  if Assigned( DS ) and
     ( not Assigned( DS.FBindingCursor )) and
     ( DS.DMLCacheFlags <> [] ) then
  begin
    case DMLCacheItemType of
      ditEdit:   if not (dcfReceiveEdit   in DS.DMLCacheFlags) then Exit;
      ditInsert: if not (dcfReceiveInsert in DS.DMLCacheFlags) then Exit;
      ditDelete: if not (dcfReceiveDelete in DS.DMLCacheFlags) then Exit;
    end;
    DS.DoDMLCacheReceiveItem( Self );
  end;
end;

procedure TIB_DMLCacheItem.AnnounceToTransaction( const TS: TIB_Transaction;
                                                  const ExcludeDS: TIB_Dataset);
var
  ii: integer;
  tmpDataset: TIB_Dataset;
begin
  if Assigned( TS ) then with TS do begin
    for ii := 0 to DatasetCount - 1 do begin
      tmpDataset := Datasets[ ii ];
      if ( Assigned( tmpDataset )) and
         ( tmpDataset.Active ) and
         ( tmpDataset <> ExcludeDS ) then
        AnnounceToDataset( tmpDataset );
    end;
  end;
end;

//IBA_Connection.INT

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  27-Apr-2003                                                                 }
{     Added extract of Firebird specific version info to characteristics.      }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  09-Sep-2001                                                                 }
{     Added psKeyFromEnviron option to TIB_PasswordStorage type.               }
{                                                                              }
{  Wassim Haddad <lobolo2000@yahoo.com>                                        }
{  22-Aug-2001                                                                 }
{     Added support to import column defaults from the server upon request.    }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  10-Aug-2001                                                                 }
{     Implemented new PasswordStorage property to control if and how the       }
{     Password property is saved to the DFM (via the SavedPassword property).  }
{     See TIB_PasswordStorage type for detailed explanation.                   }
{     Deprecating PasswordRemembered - some special streaming implemented      }
{     so that it can still be read, but now it simply sets PasswordStorage     }
{     to psKeyFromUserReg.                                                     }
{     SavedPassword property set back to public (from published) and special   }
{     streaming code provided to read/write to/from the DFM.                   }
{                                                                              }
{  Norman Dunbar <norman@bountiful.demon.co.uk>                                }
{  13-Aug-2002                                                                 }
{     Change TIB_Connection.SysLogin to avoid an exception being raised if the }
{     user hits the CANCEL button on the login dialogue.                       }
{******************************************************************************}

type
  TIB_ConnectionPool = class
  private
    FPoolItems: TList;
    FPoolLock: TRTLCriticalSection;
  public
    constructor Create;
    destructor Destroy; override;
    procedure ClearHandles;
  end;

  TIB_ConnectionPoolItem = class
  private
    FdbHandle: ISC_DB_HANDLE;
    FDatabase: string;
    FUsername: string;
    FPassword: string;
    FSQLRolename: string;
  public
    destructor Destroy; override;
  end;

var
  ConnectionPool: TIB_ConnectionPool = nil;

procedure ClearConnectionPool;
begin
  if Assigned( ConnectionPool ) then
  begin
    ConnectionPool.ClearHandles;
    ConnectionPool.Free;
    ConnectionPool := nil;
  end;
end;

constructor TIB_Connection.Create( AOwner: TComponent );
begin
// Create this object.
  inherited Create( AOwner );
  FEscapeCharacter := '\';
// Create objects used by this object.
  FStatementList := TList.Create;
  FDatasetList := TList.Create;
  FTransactionList := TList.Create;
  FActiveTransactionList := TList.Create;
  FParams := TIB_StringList.Create;
  FPasswordStorage := psNone;
  FUsers := TIB_StringList.Create;
  FSchemaCache := TIB_SchemaCache.Create( Self );
  FColumnAttributes := TIB_StringProperty.Create;
  FColumnAttributes.Sorted := true;
  FDefaultValues := TIB_StringProperty.Create;
  FDefaultValues.Sorted := true;
  FFieldsAlignment := TIB_StringProperty.Create;
  FFieldsAlignment.Sorted := true;
  FFieldsCharCase := TIB_StringProperty.Create;
  FFieldsCharCase.Sorted := true;
  FFieldsDisplayLabel := TIB_StringProperty.Create;
  FFieldsDisplayLabel.Sorted := true;
  FFieldsGridLabel := TIB_StringProperty.Create;
  FFieldsGridLabel.Sorted := true;
  FFieldsGridTitleHint := TIB_StringProperty.Create;
  FFieldsGridTitleHint.Sorted := true;
  FFieldsDisplayFormat := TIB_StringProperty.Create;
  FFieldsDisplayFormat.Sorted := true;
  FFieldsDisplayWidth := TIB_StringProperty.Create;
  FFieldsDisplayWidth.Sorted := true;
  FFieldsEditMask := TIB_StringProperty.Create;
  FFieldsEditMask.Sorted := true;
  FFieldsReadOnly := TIB_StringProperty.Create;
  FFieldsReadOnly.Sorted := true;
  FFieldsTrimming := TIB_StringProperty.Create;
  FFieldsTrimming.Sorted := true;
  FFieldsVisible := TIB_StringProperty.Create;
  FFieldsVisible.Sorted := true;
  FGeneratorLinks := TIB_StringProperty.Create;
  FGeneratorLinks.Sorted := true;
// Initialize this object.
  FdbHandle := nil;
  FStmtHandleCache := nil;
  SysInvalidateCachedInformation;
  FCharacteristicsValid := false;
  FLoginAttempts := 3;
  FLoginAttemptsDone := 0;
  FLoginAbortedShowMessage := false;
  FCacheStatementHandles := true;
  FLastOpened := now;
  FLastClosed := now;
  FKeepConnection := true;
  FMacroBegin := '<';
  FMacroEnd := '>';
  FParameterOrder := poAuto;
  FDefaultNoLengthCheck := true;
end;

destructor TIB_Connection.Destroy;
begin
  CloseTransactions;
  Destroying;
  try
    SysDisconnect;
  except
  end;
  while TransactionCount > 0 do
    Transactions[ 0 ].SysRemoveConnection( Self );
  while ConnectionLinkCount > 0 do
    TIB_ConnectionLink( FConnectionLinkList.Items[ 0 ] ).IB_Connection := nil;
  FParams.Free;
  FParams := nil;
  FUsers.Free;
  FUsers := nil;
  FTransactionList.Free;
  FTransactionList := nil;
  FActiveTransactionList.Free;
  FActiveTransactionList := nil;
  FStatementList.Free;
  FStatementList := nil;
  FDatasetList.Free;
  FDatasetList := nil;
  FSchemaCache.Free;
  FSchemaCache := nil;
  FColumnAttributes.Free;
  FColumnAttributes := nil;
  FDefaultValues.Free;
  FDefaultValues := nil;
  FFieldsAlignment.Free;
  FFieldsCharCase.Free;
  FFieldsDisplayLabel.Free;
  FFieldsGridLabel.Free;
  FFieldsGridTitleHint.Free;
  FFieldsDisplayFormat.Free;
  FFieldsDisplayWidth.Free;
  FFieldsEditMask.Free;
  FFieldsReadOnly.Free;
  FFieldsTrimming.Free;
  FFieldsVisible.Free;
  FFieldsAlignment := nil;
  FFieldsCharCase := nil;
  FFieldsDisplayLabel := nil;
  FFieldsGridLabel := nil;
  FFieldsGridTitleHint := nil;
  FFieldsDisplayFormat := nil;
  FFieldsDisplayWidth := nil;
  FFieldsEditMask := nil;
  FFieldsReadOnly := nil;
  FFieldsTrimming := nil;
  FFieldsVisible := nil;
  FGeneratorLinks.Free;
  FGeneratorLinks := nil;
  if Assigned( FAliasParams ) then
  begin
    FAliasParams.Free;
    FAliasParams := nil;
  end;
  inherited Destroy;
end;

procedure TIB_Connection.GetSessionName( AReader: TReader );
begin
  AReader.ReadString;
end;

procedure TIB_Connection.LoadSavedPassword( AReader: TReader );
begin
  SavedPassword := AReader.ReadString;
end;

procedure TIB_Connection.StoreSavedPassword( AWriter: TWriter );
begin
  AWriter.WriteString( SavedPassword );
end;

// PasswordRemembered is defined here only to keep some backwards
// compatibility - it will automatically set the matching PasswordStorage
// option.  This function and its DefineProperty call below can be
// removed in a month or two when everyone should have upgraded.
//    Geoff Worboys 10-Aug-2001
procedure TIB_Connection.LoadPasswordRemembered( AReader: TReader );
var
  TmpVal: boolean;
begin
  TmpVal := AReader.ReadBoolean;
  if TmpVal then
    PasswordStorage := psKeyFromUserReg;
end;

procedure TIB_Connection.DefineProperties( AFiler: TFiler );
begin
  inherited DefineProperties( AFiler );
  AFiler.DefineProperty( 'SessionName', GetSessionName, nil, false );
  AFiler.DefineProperty( 'SavedPassword', LoadSavedPassword,
                         StoreSavedPassword, IsSavedPasswordStored );
  AFiler.DefineProperty( 'PasswordRemembered', LoadPasswordRemembered, nil, false );
end;

procedure TIB_Connection.Loaded;
begin
  inherited Loaded;
  SysConnectAfterLoad;
end;

procedure TIB_Connection.Notification( AComponent: TComponent;
                                       Operation: TOperation);
begin
  if ( Operation = opRemove ) and ( AComponent = FDefaultTransaction ) then
    FDefaultTransaction := nil;
  inherited Notification( AComponent, Operation );
end;

function TIB_Connection.GetSchemaCacheDir: string;
begin
  Result := SchemaCache.LocalDir;
end;

procedure TIB_Connection.SetSchemaCacheDir( const AValue: string );
begin
  SchemaCache.LocalDir := AValue;
end;

procedure TIB_Connection.SysConnectAfterLoad;
var
  ii: integer;
begin
  try
    if FConnectAfterLoad then begin
      if SysConnect( false ) then begin
        FConnectAfterLoad := false;
        for ii := 0 to StatementCount - 1 do begin
          Statements[ ii ].SysPrepareAfterLoad;
        end;
        for ii := 0 to DatasetCount - 1 do begin
          Datasets[ ii ].SysOpenAfterLoad;
        end;
      end;
    end;
  except
  // Throw away the exception here.
  end;
end;

procedure TIB_Connection.Connect;
begin
  if Connected then
    DoLinkEvent( cetConnectedChanged )
  else
    try
      BeginBusy( false );
      SysConnect( false );
    finally
      EndBusy;
    end;
end;

procedure TIB_Connection.Disconnect;
begin
  if not Connected then
    DoLinkEvent( cetConnectedChanged )
  else
    try
      BeginBusy( false );
      SysDisconnect;
    finally
      EndBusy;
    end;
end;

procedure TIB_Connection.ForceDisconnect;
var
  ii: integer;
begin
  ii := 1;
  while Connected and ( ii <= 3 ) do
    try
      Inc( ii );
      Disconnect;
    except
    end;
end;

procedure TIB_Connection.Open;
begin
  Connect;
end;

procedure TIB_Connection.Close;
begin
  Disconnect;
end;

procedure TIB_Connection.CreateDatabase;
begin
  if Connected then
    raise EIB_ConnectionError.CreateWithSender( Self, E_CONNECTED )
  else
  begin
    try
      BeginBusy( false );
      SysCreateDatabase;
    finally
      EndBusy;
    end;
  end;
end;

procedure TIB_Connection.DropDatabase;
begin
  if Connected then
  begin
    try
      BeginBusy( false );
      SysDropDatabase;
    finally
      EndBusy;
    end;
  end else begin
    raise EIB_ConnectionError.CreateWithSender( Self, E_NOT_CONNECTED );
  end;
end;

procedure TIB_Connection.AlterUser( Action: TIB_AlterUserAction;
                                    AUsername,
                                    AUserPass,
                                    AGroupName,
                                    AFirstName,
                                    AMiddleName,
                                    ALastName: string );
var
  tmpSec: USER_SEC_DATA;
  SaveCW: word;
begin
  tmpSec := IB_Session.GetUserSec( Action,
                                   Protocol,
                                   Server,
                                   Username,
                                   Password,
                                   AUsername,
                                   AUserPass,
                                   AGroupName,
                                   AFirstName,
                                   AMiddleName,
                                   ALastName );
  with IB_Session do begin
    asm fstcw [SaveCW] end;
    case Action of
      uaAddUser: errcode := dll_add_user( @status, @tmpSec );
      uaDeleteUser: errcode := dll_delete_user( @status, @tmpSec );
      uaModifyUser: errcode := dll_modify_user( @status, @tmpSec );
    end;
    asm fldcw [SaveCW] end;
    if errcode <> 0 then begin
      HandleException( Self );
    end;
  end;
end;

{  Property access routines                                                    }

function TIB_Connection.IsConnectedStored: boolean;
begin
  Result := IB_Session.StoreActive and Connected;
end;

procedure TIB_Connection.SetCacheStatementHandles( AValue: boolean );
begin
  FCacheStatementHandles := AValue;
  if not AValue then
    FreeStmtHandleCache( 0 );
end;

procedure TIB_Connection.SetColumnAttributes( AValue: TIB_StringProperty );
begin FColumnAttributes.Assign( AValue ); end;
procedure TIB_Connection.SetDefaultValues( AValue: TIB_StringProperty );
begin FDefaultValues.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsAlignment( AValue: TIB_StringProperty );
begin FFieldsAlignment.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsCharCase( AValue: TIB_StringProperty );
begin FFieldsCharCase.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsDisplayLabel( AValue: TIB_StringProperty );
begin FFieldsDisplayLabel.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsGridLabel( AValue: TIB_StringProperty );
begin FFieldsGridLabel.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsGridTitleHint( AValue: TIB_StringProperty );
begin FFieldsGridTitleHint.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsDisplayFormat( AValue: TIB_StringProperty );
begin FFieldsDisplayFormat.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsDisplayWidth( AValue: TIB_StringProperty );
begin FFieldsDisplayWidth.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsEditMask( AValue: TIB_StringProperty );
begin FFieldsEditMask.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsReadOnly( AValue: TIB_StringProperty );
begin FFieldsReadOnly.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsTrimming( AValue: TIB_StringProperty );
begin FFieldsTrimming.Assign( AValue ); end;
procedure TIB_Connection.SetFieldsVisible( AValue: TIB_StringProperty );
begin FFieldsVisible.Assign( AValue ); end;
procedure TIB_Connection.SetGeneratorLinks( AValue: TIB_StringProperty );
begin FGeneratorLinks.Assign( AValue ); end;

{------------------------------------------------------------------------------}

function TIB_Connection.GetLoginUsername: string;
var
  tmpPos: integer;
begin
  Result := Username;
  if ( Result <> '' ) and
     ( Result <> 'SYSDBA' ) and
     ( Result <> 'PUBLIC' ) then begin
    tmpPos := Pos( LoginUsernamePrefix, Result );
    if tmpPos > 0 then begin
      Inc( tmpPos, Length( LoginUsernamePrefix ));
      Result := Copy( Result, tmpPos, MaxInt );
    end;
  end;
end;

procedure TIB_Connection.SetLoginUsername( const AValue: string );
begin
  if ( AValue <> '' ) and
     ( AValue <> 'SYSDBA' ) and
     ( AValue <> 'PUBLIC' ) then
    Username := LoginUsernamePrefix + AValue
  else
    Username := AValue;
end;

function TIB_Connection.GetLoginSQLRole: string;
var
  tmpPos: integer;
begin
  Result := SQLRole;
  tmpPos := Pos( LoginSQLRolePrefix, Result );
  if tmpPos > 0 then
  begin
    Inc( tmpPos, Length( LoginSQLRolePrefix ));
    Result := Copy( Result, tmpPos, MaxInt );
  end;
end;

procedure TIB_Connection.SetLoginSQLRole( const AValue: string );
begin
  if AValue = '' then begin
    SQLRole := AValue;
  end else begin
    SQLRole := LoginSQLRolePrefix + AValue;
  end;
end;

function TIB_Connection.GetHasActiveTransaction: boolean;
var
  ii: integer;
begin
  Result := false;
  for ii := 0 to TransactionCount - 1 do begin
    if Transactions[ ii ].TransactionIsActive then begin
      Result := true;
      Break;
    end;
  end;
end;

function TIB_Connection.GetUsers: TStrings;
var
  Results: array [0..8191] of Char;
  Items: array [0..1] of Char;
  Item: byte;
  ItemLen: integer;
  ItemPtr: integer;
begin
  Result := FUsers;
  FUsers.Clear;
  if not Connected then
    Exit;
  Items[ 0 ] := char( isc_info_user_names );
  Items[ 1 ] := char( isc_info_end );
  FillChar( Results, SizeOf(Results), #0 );
  API_Database_Info( Items, Results );
  ItemPtr := 0;
  while byte( Results[ ItemPtr ] ) <> isc_info_end do
  begin
    Item := byte( Results[ ItemPtr ] );
    Inc( ItemPtr, 1 );
    ItemLen := isc_vax_integer( @Results[ ItemPtr ], 2 );
    Inc( ItemPtr, 2 );
    if Item = isc_info_user_names then
      FUsers.Add( Copy(pchar(@Results[ItemPtr+1]), 1, byte(Results[ItemPtr])));
    Inc( ItemPtr, ItemLen )
  end;
end;

procedure TIB_Connection.SetConnected( Value: boolean);
begin
  if Value <> Connected then
    if Value then
      SysConnect( false )
    else
      SysDisconnect;
end;

function TIB_Connection.GetConnected: Boolean;
begin
  Result := FdbHandle <> nil;
end;

procedure TIB_Connection.SetDefaultTransaction( AValue: TIB_Transaction );
begin
  FDefaultTransaction := AValue;
  if Assigned( AValue ) then
    AValue.FreeNotification( Self );
end;

procedure TIB_Connection.SetParams( Value: TIB_StringList );
var
  tmpStr: string;
begin
  FParams.Text := Value.Text;
  if csReading in ComponentState then
  begin
    tmpStr := FParams.Values[ IBO_SERVER_NAME ];
    if tmpStr <> '' then
    begin
      Database := tmpStr;
      FParams.Values[ IBO_SERVER_NAME ] := '';
    end;
    if PasswordStorage <> psParams then
      SetPrm( 2, '' ); // Make sure no value in password param
  end;
end;

function TIB_Connection.GetPrm( Index: integer ): string;
begin
  Result := '';
  case Index of
    1: Result := Params.Values[ IBO_USER_NAME ];
    2:
    begin
      if Protocol = cpLocal then
        Result := Params.Values[ IBO_LOCAL_PASSWORD ];
      if Result = '' then
        Result := Params.Values[ IBO_PASSWORD ];
    end;
    3: Result := Params.Values[ IBO_SERVER ];
    4:
    begin
      Result := Params.Values[ IBO_ROLE_NAME ];
      if Result = '' then
      begin
        Result := Params.Values[ IBO_ROLE_ ];
        if Result <> '' then
        begin
          Params.Values[ IBO_ROLE_NAME ] := Result;
          Params.Values[ IBO_ROLE_ ] := '';
        end;
      end;
      if Result = '' then
      begin
        Result := Params.Values[ IBO_SQL_ROLE_NAME_ ];
        if Result <> '' then
        begin
          Params.Values[ IBO_ROLE_NAME ] := Result;
          Params.Values[ IBO_SQL_ROLE_NAME_ ] := '';
        end;
      end;
    end;
    5: Result := Params.Values[ IBO_PATH ];
    6: Result := Params.Values[ IBO_LICENSE ];
    7: Result := Params.Values[ IBO_SYSDBA ];
    8: Result := Params.Values[ IBO_ENCRYPT_KEY ];
    9: Result := Params.Values[ IBO_CHARACTER_SET ];
   10: Result := Params.Values[ IBO_MESSAGE_FILE ];
  end;
end;

procedure TIB_Connection.SetPrm( Index: integer; const Value: string );
begin
  if csDesigning in ComponentState then
    Connected := false;
  if Connected then
    raise EIB_ConnectionError.CreateWithSender( Self, E_CANT_CHG_WITH_CONN )
  else
  case Index of
    1: Params.Values[ IBO_USER_NAME ] := Value;
    2: begin
      if ( Protocol = cpLocal ) and
         ( Params.Values[ IBO_LOCAL_PASSWORD ] <> '' ) then
        Params.Values[ IBO_LOCAL_PASSWORD ] := Value
      else
        Params.Values[ IBO_PASSWORD ] := Value;
    end;
    3: Params.Values[ IBO_SERVER ] := Value;
    4:
    begin
      Params.Values[ IBO_ROLE_NAME ] := Value;
      Params.Values[ IBO_ROLE_ ] := '';
      Params.Values[ IBO_SQL_ROLE_NAME_ ] := '';
    end;
    5: Params.Values[ IBO_PATH ] := Value;
    6: Params.Values[ IBO_LICENSE ] := Value;
    7: Params.Values[ IBO_SYSDBA ] := Value;
    8: Params.Values[ IBO_ENCRYPT_KEY ] := Value;
    9: Params.Values[ IBO_CHARACTER_SET ] := Value;
   10: Params.Values[ IBO_MESSAGE_FILE ] := Value;
  end;
end;

function TIB_Connection.GetPassword: string;
begin
  Result := FPassword;
  if ( Result = '' ) or ( PasswordStorage = psParams ) then
    Result := GetPrm( 2 );
end;

// Not protected, so can be cleared at runtime if desired.
procedure TIB_Connection.SetPassword( AValue: string );
begin
  FPassword := AValue;
  if PasswordStorage = psParams then
    SetPrm( 2, AValue )
  else
    SetPrm( 2, '' ); // Make sure no value in params property
end;

function TIB_Connection.GetSavedPassword: string;
var
  tmpKey: HKEY;
  tmpStr: string;
begin
  case PasswordStorage of
    psNone: Result := '';
    psParams: GetPrm( 2 );
    psKeyFromUserReg:
    begin
      // Dont step on other connections doing the same thing
      EnterCriticalSection( AttachCS );
      tmpKey := JumbleRegKey;
      try
        JumbleRegKey := HKEY_CURRENT_USER;
        Result := JumbleString( Password, GetJumbleKey );
      finally
        JumbleRegKey := tmpKey;
        LeaveCriticalSection( AttachCS );
      end;
    end;
    psKeyFromMachineReg:
    begin
      // Dont step on other connections doing the same thing
      EnterCriticalSection( AttachCS );
      tmpKey := JumbleRegKey;
      try
        JumbleRegKey := HKEY_LOCAL_MACHINE;
        Result := JumbleString( Password, GetJumbleKey );
      finally
        JumbleRegKey := tmpKey;
        LeaveCriticalSection( AttachCS );
      end;
    end;
    psNotSecure:
    begin
      // We use the JumblePrefix_v01 string from IB_Utils as the standard
      // encryption key.  Does not make much difference what we use since
      // this is inherently insecure.
      Result := JumbleString( Password, JumblePrefix_v01 );
    end;
    psKeyFromEnviron:
    begin
      tmpStr := GetEnvVariable( 'IBO_PASSKEY' );
      if tmpStr = '' then
        tmpStr := JumblePrefix_v01; // default to psNotSecure
      Result := JumbleString( Password, tmpStr );
    end;
  end;
end;

procedure TIB_Connection.SetSavedPassword( AValue: string );
var
  tmpKey: HKEY;
  tmpStr: string;
begin
  case PasswordStorage of
    psNone: Password := UnJumbleString( AValue, '' );
    psParams: SetPrm( 2, AValue );
    psKeyFromUserReg:
    begin
      // Dont step on other connections doing the same thing
      EnterCriticalSection( AttachCS );
      tmpKey := JumbleRegKey;
      try
        JumbleRegKey := HKEY_CURRENT_USER;
        Password := UnJumbleString( AValue, GetJumbleKey );
      finally
        JumbleRegKey := tmpKey;
        LeaveCriticalSection( AttachCS );
      end;
    end;
    psKeyFromMachineReg:
    begin
      // Dont step on other connections doing the same thing
      EnterCriticalSection( AttachCS );
      tmpKey := JumbleRegKey;
      try
        JumbleRegKey := HKEY_LOCAL_MACHINE;
        Password := UnJumbleString( AValue, GetJumbleKey );
      finally
        JumbleRegKey := tmpKey;
        LeaveCriticalSection( AttachCS );
      end;
    end;
    psNotSecure:
    begin
      // We use the JumblePrefix_v01 string from IB_Utils as the standard
      // encryption key.  Does not make much difference what we use since
      // this is inherently insecure.
      Password := UnJumbleString( AValue, JumblePrefix_v01 );
    end;
    psKeyFromEnviron:
    begin
      tmpStr := GetEnvVariable( 'IBO_PASSKEY' );
      if tmpStr = '' then
        tmpStr := JumblePrefix_v01; // default to psNotSecure
      Password := UnJumbleString( AValue, tmpStr );
    end;
  end;
end;

function TIB_Connection.IsSavedPasswordStored: boolean;
begin
  Result := ((PasswordStorage <> psNone) and
             (PasswordStorage <> psParams) and
             (Password <> ''));
end;

procedure TIB_Connection.SetPasswordStorage( AVal: TIB_PasswordStorage );
begin
  if AVal <> FPasswordStorage then
    FPasswordStorage := AVal;
end;

function TIB_Connection.GetForcedWrites: TIB_DPBFlag;
begin
  if Params.Values[ IBO_FORCED_WRITES ] = IBO_TRUE then
    Result := dpbTrue
  else
  if Params.Values[ IBO_FORCED_WRITES ] = IBO_FALSE then
    Result := dpbFalse
  else
    Result := dpbDefault;
end;

procedure TIB_Connection.SetForcedWrites( AValue: TIB_DPBFlag );
begin
  if ForcedWrites <> AValue then
  begin
    if csDesigning in ComponentState then
      Connected := false;
    if Connected then
      raise EIB_ConnectionError.CreateWithSender( Self, E_CANT_CHG_WITH_CONN )
    else
      case AValue of
        dpbTrue: Params.Values[ IBO_FORCED_WRITES ] := IBO_TRUE;
        dpbFalse: Params.Values[ IBO_FORCED_WRITES ] := IBO_FALSE;
        dpbDefault: Params.Values[ IBO_FORCED_WRITES ] := '';
      end;
  end;
end;

function TIB_Connection.GetDBKeyScope: TIB_DPBFlag;
begin
  if Params.Values[ IBO_DB_KEY_SCOPE ] = IBO_DB_KEY_SCOPE_CONN then
    Result := dpbTrue
  else
  if Params.Values[ IBO_DB_KEY_SCOPE ] = IBO_DB_KEY_SCOPE_TRAN then
    Result := dpbFalse
  else
    Result := dpbDefault;
end;

procedure TIB_Connection.SetDBKeyScope( AValue: TIB_DPBFlag );
begin
  if DBKeyScope <> AValue then
  begin
    if csDesigning in ComponentState then
      Connected := false;
    if Connected then
      raise EIB_ConnectionError.CreateWithSender( Self, E_CANT_CHG_WITH_CONN )
    else
    if AValue = dpbTrue then
      Params.Values[ IBO_DB_KEY_SCOPE ] := IBO_DB_KEY_SCOPE_CONN
    else
    if AValue = dpbFalse then
      Params.Values[ IBO_DB_KEY_SCOPE ] := IBO_DB_KEY_SCOPE_TRAN
    else
      Params.Values[ IBO_DB_KEY_SCOPE ] := '';
  end;
end;

function TIB_Connection.GetReservePageSpace: TIB_DPBFlag;
begin
  if Params.Values[ IBO_RESERVE_PAGE_SPACE ] = IBO_TRUE then
    Result := dpbTrue
  else
  if Params.Values[ IBO_RESERVE_PAGE_SPACE ] = IBO_FALSE then
    Result := dpbFalse
  else
    Result := dpbDefault;
end;

procedure TIB_Connection.SetReservePageSpace( AValue: TIB_DPBFlag );
begin
  if ReservePageSpace <> AValue then
  begin
    if csDesigning in ComponentState then
      Connected := false;
    if Connected then
      raise EIB_ConnectionError.CreateWithSender( Self, E_CANT_CHG_WITH_CONN )
    else
      case AValue of
        dpbTrue:    Params.Values[ IBO_RESERVE_PAGE_SPACE ] := IBO_TRUE;
        dpbFalse:   Params.Values[ IBO_RESERVE_PAGE_SPACE ] := IBO_FALSE;
        dpbDefault: Params.Values[ IBO_RESERVE_PAGE_SPACE ] := '';
      end;
  end;
end;

function TIB_Connection.GetPageSize: word;
begin
  if Connected then
    Result := Characteristics.dbPage_Size
  else
  begin
    if Params.Values[ IBO_PAGE_SIZE ] = '' then
      Result := 1024
    else
      Result := StrToInt( Params.Values[ IBO_PAGE_SIZE ] );
    if Result < 1024 then
      Result := 1024
    else
      Result := ( Result div 1024 ) * 1024;
  end;
end;

procedure TIB_Connection.SetPageSize( AValue: word );
begin
  if AValue < 1024 then
    AValue := 1024
  else
    AValue := ( AValue div 1024 ) * 1024;
  if ( Params.Values[ IBO_PAGE_SIZE ] = '' ) or
     ( StrToInt(Params.Values[ IBO_PAGE_SIZE ]) <> AValue ) then
  begin
    if AValue = 1024 then
      Params.Values[ IBO_PAGE_SIZE ] := ''
    else
      Params.Values[ IBO_PAGE_SIZE ] := IntToStr( AValue );
  end;
end;

function TIB_Connection.GetSQLDialect: smallint;
var
  tmpStr: string;
begin
  if Connected then
    Result := Characteristics.dbSQLDialect
  else
  begin
    tmpStr := Params.Values[ IBO_SQL_DIALECT ];
    if tmpStr = '' then
      Result := 3
    else
      Result := StrToInt( tmpStr );
  end;
  if Result < 1 then
    Result := 1;
end;

procedure TIB_Connection.SetSQLDialect( AValue: smallint );
begin
  if ( Params.Values[ IBO_SQL_DIALECT ] = EmptyStr ) or
     ( StrToInt(Params.Values[ IBO_SQL_DIALECT ]) <> AValue ) then
    Params.Values[ IBO_SQL_DIALECT ] := IntToStr( AValue );
end;

function TIB_Connection.GetSweepInterval: word;
begin
  if Connected then
    Result := Characteristics.dbSweep_Interval
  else
  if Params.Values[ IBO_SWEEP_INTERVAL ] = '' then
    Result := 0
  else
    Result := StrToInt( Params.Values[ IBO_SWEEP_INTERVAL ] );
end;

procedure TIB_Connection.SetSweepInterval( AValue: word );
begin
  if ( Params.Values[ IBO_SWEEP_INTERVAL ] = '' ) or
     ( StrToInt(Params.Values[ IBO_SWEEP_INTERVAL ]) <> AValue ) then
    if AValue = 0 then
      Params.Values[ IBO_SWEEP_INTERVAL ] := ''
    else
      Params.Values[ IBO_SWEEP_INTERVAL ] := IntToStr( AValue );
end;

function TIB_Connection.GetDatabase: string;
begin
  case Protocol of
    cpNetBEUI: Result := '\\' + Server + '\' + Path;
    cpTCP_IP:  Result := Server + ':' + Path;
    cpNovell:  Result := Server + '@' + Path;
    else       Result := Path;
  end;
end;

procedure TIB_Connection.SetDatabase( const AValue: string );
var
  tmpStr: string;
begin
  if ( Length( AValue ) > 2 ) and
     ( AValue[ 2 ] = ':' ) and
     ( Pos( ':', Copy( AValue, 3, maxint )) = 0 ) then
  begin
    Path     := AValue;
    Protocol := cpLocal;
  end
  else
  if Pos( '\\', AValue ) <> 0 then
  begin
    tmpStr := Copy( AValue, 3, MaxInt );
    Server   := Copy( tmpStr, 1, Pos( '\', tmpStr ) - 1);
    Path     := Copy( tmpStr, Pos( '\', tmpStr ) + 1, MaxInt );
    Protocol := cpNetBEUI;
  end
  else
  if Pos( '@', AValue ) <> 0 then
  begin
    Server   := Copy( AValue, 1, Pos( '@', AValue ) - 1);
    Path     := Copy( AValue, Pos( '@', AValue ) + 1, MaxInt );
    Protocol := cpNovell;
  end
  else
  if Pos( ':', AValue ) <> 0 then
  begin
    Server   := Copy( AValue, 1, Pos( ':', AValue ) - 1);
    Path     := Copy( AValue, Pos( ':', AValue ) + 1, MaxInt );
    Protocol := cpTCP_IP;
  end
  else
  begin
    Path     := AValue;
    Protocol := cpLocal;
  end;
end;

function TIB_Connection.GetDatabaseName: string;
begin
  if FDatabaseName <> '' then
    Result := FDatabaseName
  else
    Result := Database;
end;

procedure TIB_Connection.SetDatabaseName( const AValue: string );
var
  tmpCN: TIB_Connection;
  ii: integer;
begin
  if ( Pos( '.', AValue ) > 0 ) or
     ( Pos( '\', AValue ) > 0 ) or
     ( Pos( ':', AValue ) > 0 ) or
     ( Pos( '@', AValue ) > 0 ) then
  begin
    Database := AValue;
    FDatabaseName := '';
  end
  else
  if FDatabaseName <> AValue then
  begin
    if IsValidIdent( AValue ) then
    begin
      if Assigned( FIB_Session ) then
        tmpCN := IB_Session.GetConnectionByName( AValue )
      else
        tmpCN := nil;
      ii := 0;
      while ( tmpCN <> nil ) and ( tmpCN <> Self ) do
      begin
        Inc( ii );
        tmpCN := IB_Session.GetConnectionByName( AValue + '_' + IntToStr( ii ));
      end;
      if ii > 0 then
        FDatabaseName := AValue + '_' + IntToStr( ii )
      else
        FDatabaseName := AValue;
    end
    else
      raise EIB_ConnectionError.CreateWithSender( Self, E_INVALID_DB_NAME );
  end;
end;

function TIB_Connection.IsDatabaseNameStored: boolean;
begin
  Result := FDatabaseName <> '';
end;

function TIB_Connection.GetProtocol: TIB_Protocol;
var
  ProtocolString: string;
begin
  ProtocolString := Uppercase( Params.Values[ IBO_PROTOCOL ] );
  if ProtocolString = IBO_PROTOCOL_NETBEUI then Result := cpNetBEUI else
  if ProtocolString = IBO_PROTOCOL_TCP_IP  then Result := cpTCP_IP else
  if ProtocolString = IBO_PROTOCOL_NOVELL  then Result := cpNovell else
                                               Result := cpLocal;
end;

procedure TIB_Connection.SetProtocol( AValue: TIB_Protocol );
begin
  if Protocol <> AValue then
  begin
    if csDesigning in ComponentState then
      Connected := false;
    if Connected then
      raise EIB_ConnectionError.CreateWithSender( Self, E_CANT_CHG_WITH_CONN )
    else
      case AValue of
        cpNetBEUI: Params.Values[ IBO_PROTOCOL ] := IBO_PROTOCOL_NETBEUI;
        cpTCP_IP:  Params.Values[ IBO_PROTOCOL ] := IBO_PROTOCOL_TCP_IP;
        cpNovell:  Params.Values[ IBO_PROTOCOL ] := IBO_PROTOCOL_NOVELL;
        else       Params.Values[ IBO_PROTOCOL ] := '';
      end;
  end;
end;

function TIB_Connection.GetCharacteristics: TIB_ConnectCharacteristics;
begin
  if Connected then
  begin
    if not FCharacteristicsValid then
      SysUpdateCharacteristics;
  end
  else
    raise EIB_ConnectionError.CreateWithSender( Self, E_CANT_GET_CONN_CHR );
  Result := FCharacteristics;
end;

function TIB_Connection.GetTransactionCount: integer;
begin
  if FTransactionList <> nil then
    Result := FTransactionList.Count
  else
    Result := 0;
end;

function TIB_Connection.GetTransaction( Index: integer ): TIB_Transaction;
begin
  if (Index >= 0) and (Index <= TransactionCount - 1) then
    Result := TIB_Transaction( FTransactionList.Items[ Index ] )
  else
    Result := nil;
end;

function TIB_Connection.GetStatementCount: integer;
begin
  if FStatementList <> nil then
    Result := FStatementList.Count
  else
    Result := 0;
end;

function TIB_Connection.GetStatement( Index: integer ): TIB_Statement;
begin
  if (Index >= 0) and (Index <= StatementCount - 1) then
    Result := TIB_Statement( FStatementList.Items[ Index ] )
  else
    Result := nil;
end;

function TIB_Connection.GetDatasetCount: integer;
begin
  if FDatasetList <> nil then
    Result := FDatasetList.Count
  else
    Result := 0;
end;

function TIB_Connection.GetDataset( Index: integer ): TIB_Dataset;
begin
  if (Index >= 0) and (Index <= DatasetCount - 1) then
    Result := TIB_Dataset( FDatasetList.Items[ Index ] )
  else
    Result := nil;
end;

function TIB_Connection.GetPdbHandle: pisc_db_handle;
begin
  Result := @FdbHandle;
end;

function TIB_Connection.GetConnectionLinkCount: integer;
begin
  if FConnectionLinkList <> nil then
    Result := FConnectionLinkList.Count
  else
    Result := 0;
end;

function TIB_Connection.SysConnect( FromScript: boolean ): boolean;
begin
  if not Connected then
  begin
    FLoginWasUsed := false;
    if ( csReading in ComponentState ) then
    begin
      FConnectionStatus := csConnectPending;
      FConnectAfterLoad := true;
    end
    else
    begin
      if FromScript then
        FConnectionStatus := csForcedConnectPending;
      if ( not FromScript ) and LoginPrompt then
      begin
        SysLogin
      end
      else
      begin
        SysBeforeConnect;
        API_Connect;
      end;
      if Connected then
        SysAfterConnect;
    end;
  end;
  Result := Connected;
end;

procedure TIB_Connection.SysLogin;
begin
  FLoginAttemptsDone := 0;
  FLoginAborted := false;
  while ( not Connected ) and
        ( not LoginAborted ) and
        ((LoginAttempts = 0) or (LoginAttemptsDone < LoginAttempts)) do
  begin
    Inc( FLoginAttemptsDone );
    DoLogin;
    if not LoginAborted then
      try
        SysBeforeConnect;
        API_Connect;
      except
        on E: Exception do
          DoLoginFailure( E );
      end;
  end;
  if Connected then
    FLoginAborted := false
  else
    DoLoginFailure( nil );
end;

procedure TIB_Connection.SysDisconnect;
begin
  if Connected then
  begin
    SysBeforeDisconnect;
    API_Disconnect;
    SysAfterDisconnect;
  end;
end;

procedure TIB_Connection.SysCreateDatabase;
begin
  if not Connected then
  begin
    SysBeforeCreateDatabase;
    API_Connect;
    SysAfterCreateDatabase;
  end;
end;

procedure TIB_Connection.SysDropDatabase;
begin
  if Connected then
  begin
    SysBeforeDropDatabase;
    API_Disconnect;
    SysAfterDropDatabase;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Connection.SysBeforeConnect;
begin
  if FConnectionStatus <> csForcedConnectPending then
    FConnectionStatus := csConnectPending;
  try
    DoBeforeConnect;
    DoLinkEvent( cetBeforeConnect );
  except
    if Connected then
      FConnectionStatus := csConnected
    else
      FConnectionStatus := csDisconnected;
    raise;
  end;
end;

procedure TIB_Connection.SysBeforeDisconnect;
begin
  FConnectionStatus := csDisconnectPending;
  try
    DoBeforeDisconnect;
    CloseTransactions;
    DeallocateStatements;
    DoLinkEvent( cetBeforeDisconnect );
  except
    if Connected then
      FConnectionStatus := csConnected
    else
      FConnectionStatus := csDisconnected;
    raise;
  end;
end;

procedure TIB_Connection.SysAfterDisconnect;
begin
  FConnectionStatus := csDisconnected;
  SysInvalidateCachedInformation;
  DoLinkEvent( cetConnectedChanged );
  DoLinkEvent( cetAfterDisconnect );
  DoConnectedChanged;
  DoAfterDisconnect;
end;

procedure TIB_Connection.SysBeforeCreateDatabase;
begin
  SysBeforeConnect;
  FConnectionStatus := csCreatePending;
  try
    DoBeforeCreateDatabase;
    DoLinkEvent( cetBeforeCreateDatabase );
  except
    if Connected then
      FConnectionStatus := csConnected
    else
      FConnectionStatus := csDisconnected;
    raise;
  end;
end;

procedure TIB_Connection.SysBeforeDropDatabase;
begin
  SysBeforeDisconnect;
  FConnectionStatus := csDropPending;
  try
    DoBeforeDropDatabase;
    DoLinkEvent( cetBeforeDropDatabase );
  except
    if Connected then
      FConnectionStatus := csConnected
    else
      FConnectionStatus := csDisconnected;
    raise;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Connection.SysBeforeExecDDL;
begin
  DoBeforeExecDDL;
  DoLinkEvent( cetBeforeExecDDL );
end;

procedure TIB_Connection.SysAfterExecDDL;
begin
  SchemaCache.DeleteLocalFiles;
  SysInvalidateCachedInformation;
  DoLinkEvent( cetAfterExecDDL );
  DoAfterExecDDL;
end;

procedure TIB_Connection.SysAfterConnect;
var
  tmpStr: string;
begin
  if FLoginWasUsed then
  begin
    FLoginWasUsed := false;
    if LoginRegKey = '%DEFAULT%' then
    begin
      tmpStr := ExtractFileName( Application.EXEName );
      tmpStr := ChangeFileExt( tmpStr, '' );
      tmpStr := 'Software\Apps\' + tmpStr;
    end
    else
      tmpStr := LoginRegKey;
    with TRegIniFile.Create( tmpStr ) do
      try
        WriteString( 'Settings', 'Database', Database );
        WriteString( 'Settings', 'LastUser', Username );
        WriteString( 'Settings', 'LastRole', SQLRole );
      finally
        Free;
      end;
  end;
  FConnectionStatus := csConnected;
  FCharacteristicsValid := false;
  FOldParameterOrder := ParameterOrder = poOld;
  if ParameterOrder = poAuto then
    FOldParameterOrder := IB_Schema.GetOldParameterOrder( Self );
  SysInvalidateCachedInformation;
  DoLinkEvent( cetConnectedChanged );
  DoLinkEvent( cetAfterConnect );
  DoConnectedChanged;
  DoAfterConnect;
end;

procedure TIB_Connection.SysAfterCreateDatabase;
begin
  FConnectionStatus := csConnected;
  DoLinkEvent( cetAfterCreateDatabase );
  DoAfterCreateDatabase;
  SysAfterConnect;
end;

procedure TIB_Connection.SysAfterDropDatabase;
begin
  SysAfterDisconnect;
  DoLinkEvent( cetAfterDropDatabase );
  DoAfterDropDatabase;
end;

procedure TIB_Connection.SysInvalidateCachedInformation;
begin
  SchemaCache.InvalidateAllItems;
end;

{------------------------------------------------------------------------------}

procedure TIB_Connection.FlushSchemaCache;
begin
  SysAfterExecDDL;
  FreeStmtHandleCache( 0 );
end;

procedure TIB_Connection.CloseTransactions;
var
  ii: integer;
  tmpTrn: TIB_Transaction;
begin
  ii := 0;
  while ii < TransactionCount do
  begin
    tmpTrn := Transactions[ ii ];
    tmpTrn.Close;
    if ( tmpTrn <> Transactions[ ii ] ) then
      ii := 0
    else
      Inc( ii );
  end;
end;

procedure TIB_Connection.DeallocateStatements;
var
  ii: integer;
  tmpStmt: TIB_Statement;
begin
  ii := 0;
  while ii < StatementCount do
  begin
    tmpStmt := Statements[ ii ];
    tmpStmt.SysDeallocate( true );
    if ( tmpStmt <> Statements[ ii ] ) then
      ii := 0
    else
      Inc( ii );
  end;
  FreeStmtHandleCache( 0 );
end;

procedure TIB_Connection.SetMacroBegin(AValue: string);
begin
  if Trim( AValue ) = '' then
    FMacroBegin := '<'
  else
    FMacroBegin := AValue;
end;

procedure TIB_Connection.SetMacroEnd(AValue: string);
begin
  if Trim( AValue ) = '' then
    FMacroEnd := '>'
  else
    FMacroEnd := AValue;
end;

function TIB_Connection.IsMacroBeginStored: boolean;
begin
  Result := FMacroBegin <> '<';
end;

function TIB_Connection.IsMacroEndStored: boolean;
begin
  Result := FMacroEnd <> '>';
end;

{  Event dispatch routines                                                     }

procedure TIB_Connection.DoBeforeExecDDL;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( BeforeExecDDL ) then
      BeforeExecDDL( Self );
end;

procedure TIB_Connection.DoAfterExecDDL;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( AfterExecDDL ) then
      AfterExecDDL( Self );
end;

procedure TIB_Connection.DoLogin;
var
  tmpUser: string;
  tmpRole: string;
  tmpDB: string;
  tmpStr: string;
begin
  if LoginRegKey <> '' then
  begin
    tmpStr := ExtractFileName( Application.EXEName );
    tmpStr := ChangeFileExt( tmpStr, '' );
    if ( AnsiCompareText( tmpStr, 'Delphi32' ) <> 0 ) and
       ( AnsiCompareText( tmpStr, 'BCB' ) <> 0 ) then
    begin
      if LoginRegKey = '%DEFAULT%' then
        tmpStr := 'Software\Apps\' + tmpStr
      else
        tmpStr := LoginRegKey;
      with TRegIniFile.Create( tmpStr ) do
        try
          tmpUser := ReadString( 'Settings', 'LastUser', '' );
          tmpRole := ReadString( 'Settings', 'LastRole', '' );
          tmpDB := ReadString( 'Settings', 'Database', '<empty>' );
          if tmpDB = '<empty>' then
          begin
            tmpDB := Database;
            WriteString( 'Settings', 'Database', Database );
          end
          else
            Database := tmpDB;
          if tmpUser <> '' then
            Username := tmpUser;
          if tmpRole <> '' then
            SQLRole := tmpRole;
          FLoginWasUsed := true;
        finally
          Free;
        end;
    end;
  end;
  if Assigned( OnLogin ) then
    OnLogin( Self, FLoginAborted )
  else
    ShowLoginForm;
end;

procedure TIB_Connection.ShowLoginForm;
begin
  with TdlgLogin.Create( Application ) do
    try
      if LoginCaption <> '' then
        Caption := LoginCaption;
      DatabaseReadOnly := LoginDBReadOnly;
      edDatabase.Text := Database;
      edUsername.Text := LoginUsername;
      cbSQLRole.Text := LoginSQLRole;
      edPassword.Text := Password;
      if LoginHelpContext <> 0 then
      begin
        HelpBtn.HelpContext := LoginHelpContext;
        HelpBtn.Visible := true;
      end;
      ExtractFieldsIntoList( LoginSQLRoleList, cbSQLRole.Items );
      if ShowModal = idcancel then
        FLoginAborted := true
      else
      begin
        Database := edDatabase.Text;
        LoginUsername := edUsername.Text;
        Password := edPassword.Text;
        LoginSQLRole := cbSQLRole.Text;
      end;
    finally
      Free;
    end;
end;

procedure TIB_Connection.DoLoginFailure( E: Exception );
begin
  if Assigned( E ) then
    Application.HandleException( E )
  else
  begin
	  if LoginAborted and
	     ( not LoginAbortedShowMessage ) then
	    Abort
	  else
	  if Assigned( OnLoginFailure ) then
	    OnLoginFailure( Self );
	  raise EIB_ConnectionError.CreateWithSender( Self, E_LOGIN_FAILED );
  end;
end;

procedure TIB_Connection.DoBeforeConnect;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( BeforeConnect ) then
      BeforeConnect( Self );
end;

procedure TIB_Connection.DoAfterConnect;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( AfterConnect ) then
      AfterConnect( Self );
end;

procedure TIB_Connection.DoBeforeCreateDatabase;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( BeforeCreateDatabase ) then
      BeforeCreateDatabase( Self );
end;

procedure TIB_Connection.DoAfterCreateDatabase;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( AfterCreateDatabase ) then
      AfterCreateDatabase( Self );
end;

procedure TIB_Connection.DoBeforeDisconnect;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( BeforeDisconnect ) then
      BeforeDisconnect( Self );
end;

procedure TIB_Connection.DoAfterDisconnect;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( AfterDisconnect ) then
      AfterDisconnect( Self );
end;

procedure TIB_Connection.DoBeforeDropDatabase;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( BeforeDropDatabase ) then
      BeforeDropDatabase( Self );
end;

procedure TIB_Connection.DoAfterDropDatabase;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( AfterDropDatabase ) then
      AfterDropDatabase( Self );
end;

procedure TIB_Connection.DoConnectedChanged;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( OnConnectedChanged ) then
      OnConnectedChanged( Self );
end;

procedure TIB_Connection.DoProcessSearchBuffer(     IB_Field: TIB_Column;
                                                var SearchBuffer: string;
                                                    WhereClause,
                                                    Parameters,
                                                    Macros: TStrings;
                                                var DefaultHandling: boolean );
begin
  if Assigned( OnProcessSearchBuffer ) then
    OnProcessSearchBuffer( Self,
                           IB_Field,
                           SearchBuffer,
                           WhereClause,
                           Parameters,
                           Macros,
                           DefaultHandling );
end;

function TIB_Connection.GetSoundEx( SourceStr: string ): string;
begin
  DoSoundExParse( SourceStr, Result );
end;

function TIB_Connection.GetSoundExMax( SourceStr: string ): string;
begin
  DoSoundExMaxParse( SourceStr, Result );
end;

procedure TIB_Connection.DoSoundExParse( const SourceStr: string;
                                           var ResultStr: string );
begin
  if Assigned( OnSoundExParse ) then
    OnSoundExParse( Self,
                    SourceStr,
                    ResultStr )
  else
    ResultStr := IntToStr( TC_SoundEx( SourceStr ) );
end;

procedure TIB_Connection.DoSoundExMaxParse( const SourceStr: string;
                                              var ResultStr: string );
begin
  if Assigned( OnSoundExMaxParse ) then
    OnSoundExMaxParse( Self,
                       SourceStr,
                       ResultStr )
  else
  if Assigned( OnSoundExParse ) then
    OnSoundExParse( Self,
                    SourceStr,
                    ResultStr )
  else
    ResultStr := IntToStr( TC_SoundExMax( SourceStr ) );
end;

{------------------------------------------------------------------------------}

procedure TIB_Connection.AddConnectionLink( NewLink: TObject );
begin
  if FConnectionLinkList = nil then
    FConnectionLinkList := TList.Create;
  FConnectionLinkList.Add( NewLink );
end;

procedure TIB_Connection.RemoveConnectionLink( OldLink: TObject );
begin
  if FConnectionLinkList <> nil then
  begin
    FConnectionLinkList.Remove( OldLink );
    if FConnectionLinkList.Count = 0 then
    begin
      FConnectionLinkList.Free;
      FConnectionLinkList := nil;
    end;
  end;
end;

{  API Methods                                                                 }

procedure TIB_Connection.BuildDPB( var BufPtr: integer;
                                   var Buffer: array of char;
                                       Item: byte;
                                       Contents: string);
var
  Dest: Pointer;                                 
begin
  if Length( Contents ) > 255 then
    raise EIB_ConnectionError.CreateWithSender( Self, E_Invalid_DPB_Over_255 );
  if Length( Contents ) = 0 then
    Exit;
  buffer[ bufptr ] := Char(Item);
  Inc( bufptr );
  buffer[ bufptr ] := Char(Length(Contents));
  Inc( bufptr );
  Dest := @buffer[ bufptr ];
  Move(Contents[1], Dest^, Length(Contents));
  Inc( bufptr, Length( Contents ));
end;
{
  PasswordInEnv := GetEnvVariable( 'ISC_PASSWORD', nil, 0 ) > 0;
  UsernameInEnv := GetEnvVariable( 'ISC_USER',     nil, 0 ) > 0;
  DatabaseInEnv := GetEnvVariable( 'ISC_DATABASE', nil, 0 ) > 0;
}
procedure TIB_Connection.API_Connect;
var
  bufptr: integer;
  buffer: array[0..1023] of char;
  SaveCW: word;
  tmpPos: integer;
  ii: integer;
  tmpPI: TIB_ConnectionPoolItem;
begin
  if Connected then
    Exit;
// Cause the alias to be re-read each time an attempt to connect is made.
  if Assigned( FAliasParams ) then
  begin
    FAliasParams.Free;
    FAliasParams := nil;
  end;
  FConnectedDataBase := FParams.Values[ 'FORCED_DATABASE' ];
  if FConnectedDatabase = '' then
    FConnectedDatabase := Database;
  if Trim( FConnectedDatabase ) = '' then
  begin
    FConnectedDatabase := FParams.Values[ IBO_SERVER_NAME ];
    if FConnectedDatabase = '' then
    begin
      if ( AliasName <> '' ) then
        FConnectedDatabase := Trim( AliasParams.Values[ BDE_SERVER_NAME ] );
    end
    else
    begin
      Database := FConnectedDatabase;
      FParams.Values[ IBO_SERVER_NAME ] := '';
    end;
  end;
  FConnectedUsername := Trim( Username );
  if FConnectedUsername = '' then
    if ( AliasName <> '' ) then
      FConnectedUsername := Trim( AliasParams.Values[ BDE_USER_NAME ] );
  FConnectedPassword := Password;
  if FConnectedPassword = '' then
    if ( AliasName <> '' ) then
      FConnectedPassword := Trim( AliasParams.Values[ BDE_PASSWORD ] );
  FConnectedSQLRoleName := SQLRole;
  if FConnectedSQLRoleName = '' then
    if ( AliasName <> '' ) then
      FConnectedSQLRoleName := Trim( AliasParams.Values[ BDE_ROLE_NAME ] );

  if FConnectedUsername = '' then
    FConnectedUsername := IB_Utils.GetEnvVariable( 'ISC_USER' );

  if FConnectedPassword = '' then
    FConnectedPassword := IB_Utils.GetEnvVariable( 'ISC_PASSWORD' );

  if FConnectedSQLRoleName = '' then
    FConnectedSQLRoleName := IB_Utils.GetEnvVariable( 'ISC_ROLE' );

  if FConnectedDatabase = '' then
    FConnectedDatabase := IB_Utils.GetEnvVariable( 'ISC_DATABASE' );

  if FConnectedDatabase = '' then
    raise EIB_ConnectionError.CreateWithSender( Self, E_DATABASE_BLANK );

  tmpPos := getLitSafePos( ':', Path, 1 );
  if tmpPos > 0 then
    if ( Length( Path ) > tmpPos ) and
       ( not ( Path[ tmpPos + 1 ] in ['\','/'] )) then
      raise EIB_ConnectionError.CreateWithSender( Self, E_Invalid_conn_path );
  if Assigned( FAliasParams ) then
  begin
    FAliasParams.Free;
    FAliasParams := nil;
  end;
  FillChar( buffer, SizeOf( buffer ), #0 );
  bufptr := 0;
  buffer[bufptr] := Char( isc_dpb_version1 );
  Inc( bufptr );
  BuildDPB( bufptr, buffer, isc_dpb_user_name, FConnectedUsername );
  BuildDPB( bufptr, buffer, isc_dpb_password, FConnectedPassword );
  if FConnectedSQLRoleName <> '' then
    BuildDPB( bufptr, buffer, isc_dpb_sql_role_name, FConnectedSQLRoleName );
  if FParams.Values[ IBO_BUFFERS ] <> '' then
    BuildDPB( bufptr, buffer, isc_dpb_num_buffers,
              FParams.Values[ IBO_BUFFERS ] );
  if ConnectionStatus = csCreatePending then
    IB_Session.CreateDatabase( FConnectedDatabase,
                               FConnectedUsername,
                               FConnectedPassword,
                               PageSize,
                               CharSet,
                               SQLDialect )
  else
  if Assigned( ConnectionPool ) then
  begin
    with ConnectionPool do
    begin
      EnterCriticalSection( FPoolLock );
      try
        ii := 0;
        while ii < FPoolItems.Count do
        begin
          tmpPI := TIB_ConnectionPoolItem( FPoolItems[ii] );
          if ( FConnectedDatabase = tmpPi.FDatabase ) and
             ( FConnectedUsername = tmpPi.FUsername ) and
             ( FConnectedPassword = tmpPi.FPassword ) and
             ( FConnectedSQLRoleName = tmpPi.FSQLRolename ) then
          begin
            FdbHandle := tmpPI.FdbHandle;
            tmpPI.FdbHandle := nil;
            FPoolItems.Delete( ii );
            tmpPI.Free;
            if VerifyConnection then
            begin
              FLastConnected := now;
              Break;
            end
            else
              FdbHandle := nil;
          end
          else
            Inc( ii );
        end;
      finally
        LeaveCriticalSection( FPoolLock );
      end;
    end;
  end;
  if not Assigned( FdbHandle ) then
  begin
    case DBKeyScope of
      dpbTrue:  BuildDPB( bufptr, buffer, isc_dpb_dbkey_scope, #1 );
      dpbFalse: BuildDPB( bufptr, buffer, isc_dpb_dbkey_scope, #0 );
    end;
    case ForcedWrites of
      dpbTrue:  BuildDPB( bufptr, buffer, isc_dpb_force_write, #1 );// Synch.
      dpbFalse: BuildDPB( bufptr, buffer, isc_dpb_force_write, #0 );// Async.
    end;
    case ReservePageSpace of
      dpbTrue:  BuildDPB( bufptr, buffer, isc_dpb_no_reserve, #0 );
      dpbFalse: BuildDPB( bufptr, buffer, isc_dpb_no_reserve, #1 );
    end;
    if CharSet <> '' then
      BuildDPB( bufptr, buffer, isc_dpb_lc_ctype, CharSet );
    if SysDBA <> '' then
      BuildDPB( bufptr, buffer, isc_dpb_sys_user_name, SysDBA );
    if License <> '' then
      BuildDPB( bufptr, buffer, isc_dpb_license, License );
    if MessageFile <> '' then
      BuildDPB( bufptr, buffer, isc_dpb_lc_messages, MessageFile );
    //isc_dpb_set_page_buffers
    DoCustomizeDPB( bufptr, buffer );
    with IB_Session do
    begin
      if not Assigned( isc_attach_database ) then
        RevertToOriginalHooks; // does an acquire of hooks if necessary
      if Assigned( isc_attach_database ) then
      begin
        EnterCriticalSection( AttachCS );
        try
          asm fstcw [SaveCW] end;
          errCode := isc_attach_database( @Status,
                                          null_terminated,
                                          PChar( FConnectedDatabase ),
                                          PdbHandle,
                                          bufptr,
                                          @buffer );
          asm fldcw [SaveCW] end;
        finally
          LeaveCriticalSection( AttachCS );
        end;
      end
      else
        errcode := -1;
      if errCode = 0 then
        FLastConnected := now
      else
        HandleException( Self );
    end;
  end;
  FIsHandleShared := false;
  FRequestReconnect := false;
  FLastOpened := now;
  FConnectionWasLost := false;
  IB_Session.ResetTimerNotification( Self );
end;

function TIB_Connection.NeedTimerNotifications: boolean;
begin
  Result := ( Assigned( FdbHandle ) and ( FdbHandle <> FakePointer )) or
            inherited NeedTimerNotifications;
end;

procedure TIB_Connection.DoCustomizeDPB( var BufPtr: integer;
                                         var Buffer: array of char );
begin
  if Assigned( FOnCustomizeDPB ) then
    FOnCustomizeDPB( Self, BufPtr, Buffer );
end;

procedure TIB_Connection.API_Disconnect;
var
  SaveCW: word;
begin
  if not IsHandleShared then
    if not FConnectionWasLost then
      with IB_Session do
      begin
        if ConnectionStatus = csDropPending then
        begin
          asm fstcw [SaveCW] end;
          errCode := isc_drop_database( @Status, PdbHandle );
          asm fldcw [SaveCW] end;
          if errCode <> 0 then HandleException( Self );
        end
        else
        begin
          asm fstcw [SaveCW] end;
          errCode := isc_detach_database( @Status, PdbHandle );
          asm fldcw [SaveCW] end;
          if ( errCode = 0 ) and FRequestReconnect then
          begin
            API_Connect;
            asm fstcw [SaveCW] end;
            errCode := isc_detach_database( @Status, PdbHandle );
            asm fldcw [SaveCW] end;
          end;
        end;
        ErrCode := 0;
      end;
  FdbHandle := nil;
  FIsHandleShared := false;
  FLastClosed := now;
end;

procedure TIB_Connection.API_Database_Info( var Items:  array of Char;
                                            var Buffer: array of Char );
var
  SaveCW: word;
begin
  with IB_Session do
  begin
    if not Assigned( isc_attach_database ) then
      RevertToOriginalHooks; // does an acquire of hooks if necessary
    asm fstcw [SaveCW] end;
    errcode := isc_database_info( @status,
                                  PdbHandle,
                                  SizeOf( Items ),
                                  @Items,
                                  SizeOf( Buffer ),
                                  @Buffer );
    asm fldcw [SaveCW] end;
    if errcode <> 0 then
      HandleException( Self );
  end;
end;

procedure TIB_Connection.SysUpdateCharacteristics;
var
  Results: array [0..4095] of Char;
  Items: array [0..255] of Char;
  Item: byte;
  ItemLen: integer;
  ItemPtr: integer;
  procedure AddInfoItem( C: byte ); begin Items[ItemPtr] := char(C);
                                          Inc( ItemPtr ); end;
  procedure ExtractVersionInfo;
  var
    i, len: integer;
    VerString, tmpstr: string;
  begin
    with FCharacteristics do
    begin
      dbPlatform := '';
      dbServer_Major_Version := 0;
      dbServer_Minor_Version := 0;
      dbServer_Release := 0;
      dbServer_Build := 0;

      //FBuildType := '';
      //FBuildInfo := '';
      if length(dbFBVersion) > 0 then
        VerString := dbFBVersion
      else
        VerString := dbFBVersion;
      len := length(VerString);
      i := 1;
      // for ex. WI-V2.0.0.12724 Firebird 2.0 Release Candidate 4
      // collect platform details (until '-' character)
      while ((i <= len) and (VerString[i] <> '-')) do
      begin
        dbPlatform := dbPlatform + VerString[i];
        Inc(i);
      end;
      Inc(i);
      // collect build type details (until first numeric)
      while ((i <= len) and not(VerString[i] in ['0'..'9'])) do
      begin
        dbBuildType := dbBuildType + VerString[i];
        Inc(i);
      end;
      // try for the major version
      tmpstr := '';
      while ((i <= len) and (VerString[i] in ['0'..'9'])) do
      begin
        tmpstr := tmpstr + VerString[i];
        Inc(i);
      end;
      if length(tmpstr) > 0 then
        dbServer_Major_Version := StrToInt(tmpstr);
      // try for the minor version
      if ((i <= len) and (VerString[i] = '.')) then
      begin
        Inc(i);
        tmpstr := '';
        while ((i <= len) and (VerString[i] in ['0'..'9'])) do
        begin
          tmpstr := tmpstr + VerString[i];
          Inc(i);
        end;
        if length(tmpstr) > 0 then
          dbServer_Minor_Version := StrToInt(tmpstr);
      end;
      // try for the release
      if ((i <= len) and (VerString[i] = '.')) then
      begin
        Inc(i);
        tmpstr := '';
        while ((i <= len) and (VerString[i] in ['0'..'9'])) do
        begin
          tmpstr := tmpstr + VerString[i];
          Inc(i);
        end;
        if length(tmpstr) > 0 then
          dbServer_Release := StrToInt(tmpstr);
      end;
      // try for the build
      if ((i <= len) and (VerString[i] = '.')) then
      begin
        Inc(i);
        tmpstr := '';
        while ((i <= len) and (VerString[i] in ['0'..'9'])) do
        begin
          tmpstr := tmpstr + VerString[i];
          Inc(i);
        end;
        if length(tmpstr) > 0 then
          dbServer_Build := StrToInt(tmpstr);
      end
      else
      begin
        // some versions did not show a release value, only a build (I think)
        dbServer_Build := dbServer_Release;
        dbServer_Release := 0;
      end;
      // skip any blanks
      while ((i <= len) and (VerString[i] = ' ')) do
        Inc(i);
      // and everything else into the build info
      while (i <= len) do
      begin
        dbBuildInfo := dbBuildInfo + VerString[i];
        Inc(i);
      end;
    end;
  end;

begin
  FillChar( Results, SizeOf(Results), #0 );
  FillChar( Items, SizeOf(Items), #0 );
  ItemPtr := 0;
  AddInfoItem( isc_info_allocation );
  AddInfoItem( isc_info_base_level );
  AddInfoItem( isc_info_db_id );
  AddInfoItem( isc_info_implementation );
  AddInfoItem( isc_info_no_reserve );
  AddInfoItem( isc_info_forced_writes );
  AddInfoItem( isc_info_ods_minor_version );
  AddInfoItem( isc_info_ods_version );
  AddInfoItem( isc_info_page_size );
  AddInfoItem( isc_info_version ); // this is the isc/ib verrsion string
  AddInfoItem( isc_info_firebird_version ); // this is the FB specific ver str
  AddInfoItem( isc_info_sweep_interval );
  AddInfoItem( isc_info_set_page_buffers );
  AddInfoItem( isc_info_db_SQL_Dialect ); FCharacteristics.dbSQLDialect := 0;
  AddInfoItem( isc_info_db_read_only ); FCharacteristics.dbReadOnly := false;
  AddInfoItem( isc_info_db_size_in_pages ); FCharacteristics.dbSizeInPages := 0;
  AddInfoItem( isc_info_end );
  API_Database_Info( Items, Results );
  ItemPtr := 0;
  // reset the FB stuff specifically, we may not get any values
  FCharacteristics.dbFBVersionPrefix := 0;
  FCharacteristics.dbFBVersion := '';
  while ( byte( Results[ ItemPtr ] ) <> isc_info_end ) and
        ( byte( Results[ ItemPtr ] ) <> isc_info_truncated ) do
  begin
    Item := byte( Results[ ItemPtr ] );
    Inc( ItemPtr, 1 );
    ItemLen := {IB_Session.}isc_vax_integer( @Results[ ItemPtr ], 2 );
    Inc( ItemPtr, 2 );
    with IB_Session, FCharacteristics do case Item of
      isc_info_allocation:
        dbAllocation := isc_vax_integer( @Results[ ItemPtr ], ItemLen );
      isc_info_base_level:
      begin
        dbLevelPrefix := byte( Results[ ItemPtr ] );
        dbBase_Level := byte( Results[ ItemPtr + 1 ] );
      end;
      isc_info_db_id:
      begin
        dbFilePrefix := byte(Results[ItemPtr]);
        dbFile := Copy(pchar(@Results[ItemPtr+2]), 1,
                       byte( Results[ItemPtr+1]) );
        dbSite := Copy(pchar(@Results[ItemPtr+
                       byte( Results[ItemPtr+1])+3]),1,
                       byte( Results[ItemPtr+
                       byte( Results[ItemPtr+1])+2]));
      end;
      isc_info_implementation:
      begin
        dbImplementationPrefix := byte( Results[ ItemPtr + 0 ] );
        dbImplementation       := byte( Results[ ItemPtr + 1 ] );
        dbClass                := byte( Results[ ItemPtr + 2 ] );
      end;
      isc_info_no_reserve:
        dbNo_Reserve := isc_vax_integer( @Results[ ItemPtr ], ItemLen );
      isc_info_forced_writes:
        dbForced_Writes := isc_vax_integer( @Results[ ItemPtr ], ItemLen );
      isc_info_ods_minor_version:
        dbODS_Minor_Version := isc_vax_integer( @Results[ ItemPtr ], ItemLen );
      isc_info_ods_version:
        dbODS_Version := isc_vax_integer( @Results[ ItemPtr ], ItemLen );
      isc_info_page_size:
        dbPage_Size := isc_vax_integer( @Results[ ItemPtr ], ItemLen );
      isc_info_version:
      begin
        dbVersionPrefix := byte(Results[ItemPtr]);
        dbVersion := Copy(pchar(@Results[ItemPtr+2]), 1,
                          byte( Results[ItemPtr+1]) );
      end;
      isc_info_firebird_version:
      begin
        dbFBVersionPrefix := byte(Results[ItemPtr]);
        dbFBVersion := Copy(pchar(@Results[ItemPtr+2]), 1,
                          byte( Results[ItemPtr+1]) );
        ExtractVersionInfo;
      end;
      isc_info_sweep_interval:
        dbSweep_Interval := isc_vax_integer( @Results[ ItemPtr ], ItemLen );
      isc_info_set_page_buffers:
        dbPage_Buffers := isc_vax_integer( @Results[ ItemPtr ], ItemLen );
      isc_info_db_SQL_Dialect:
        dbSQLDialect := isc_vax_integer( @Results[ ItemPtr ], ItemLen );
      isc_info_db_read_only:
        dbReadOnly := boolean( isc_vax_integer( @Results[ ItemPtr ], ItemLen ));
      isc_info_db_size_in_pages:
        dbSizeInPages := isc_vax_integer( @Results[ ItemPtr ], ItemLen );
    end;
    Inc( ItemPtr, ItemLen )
  end;
  if byte( Results[ ItemPtr ] ) = isc_info_truncated then
    raise EIB_ConnectionError.CreateWithSender( Self, E_DB_INFO_REC_TRUNCATED );
  FCharacteristicsValid := true;
end;

procedure TIB_Connection.AllocateStmtHandle( const PstHandle: pisc_stmt_handle);
var
  SaveCW: word;
  tmpPtr: pisc_stmt_handle;
  newSize: integer;
begin
  newSize := FStmtHandleCapacity;
  if Assigned( PstHandle ) and (( PstHandle^ = nil ) or
                                ( PstHandle^ = FakePointer )) then
  begin
    PstHandle^ := nil;
    if FStmtHandleCount = 0 then
    begin
      newSize := 0;
      with IB_Session do
      begin
        asm fstcw [SaveCW] end;
        errcode := isc_dsql_allocate_statement(
//        errcode := isc_dsql_alloc_statement2(
                                                @status,
                                                PdbHandle,
                                                PstHandle );
        asm fldcw [SaveCW] end;
        if errcode <> 0 then HandleException( Self );
      end;
    end
    else
    begin
      Dec( FStmtHandleCount );
      tmpPtr := pisc_stmt_handle( longint( FStmtHandleCache ) +
                                  FStmtHandleCount * SizeOf( isc_stmt_handle ));
      PstHandle^ := tmpPtr^;
      tmpPtr^ := nil;
      if FStmtHandleCapacity > FStmtHandleCount + 10 then
        newSize := FStmtHandleCapacity - 10;
      if ( newSize < 0 ) or ( FStmtHandleCount = 0 ) then
        newSize := 0;
    end;
  end;
  if newSize <> FStmtHandleCapacity then
  begin
    IB_ReallocMem( FStmtHandleCache,
                   FStmtHandleCapacity * SizeOf(isc_stmt_handle),
                   newSize             * SizeOf(isc_stmt_handle));
    FStmtHandleCapacity := newSize;
  end;
end;

procedure TIB_Connection.DeallocateStmtHandle(const PstHandle:pisc_stmt_handle);
var
  SaveCW: word;
begin
  if Assigned( PstHandle ) then
  begin
    if ( PstHandle^ <> nil ) and
       ( PstHandle^ <> FakePointer ) then
    begin
      if CacheStatementHandles then
      begin
        Inc( FStmtHandleCount );
        if ( FStmtHandleCount >= FStmtHandleCapacity ) then
        begin
          IB_ReallocMem( FStmtHandleCache,
                        ( FStmtHandleCapacity      ) * SizeOf(isc_stmt_handle),
                        ( FStmtHandleCapacity + 10 ) * SizeOf(isc_stmt_handle));
          Inc( FStmtHandleCapacity, 10 );
        end;
        pisc_stmt_handle( longint( FStmtHandleCache ) +
              (FStmtHandleCount - 1) * SizeOf(isc_stmt_handle))^ := PstHandle^;
      end
      else
      with IB_Session do
      begin
        asm fstcw [SaveCW] end;
        errcode := isc_dsql_free_statement( @status, PstHandle, DSQL_DROP );
        asm fldcw [SaveCW] end;
      end;
    end;
    PstHandle^ := nil;
  end;
end;

function TIB_Connection.IsSQLBased: boolean;
begin
  Result := true;
end;

procedure TIB_Connection.SetLostConnection;
var
  ii: integer;
begin
  FConnectionWasLost := true;
  for ii := 0 to TransactionCount - 1 do
    Transactions[ii].FConnectionWasLost := true;
end;

procedure TIB_Connection.FreeStmtHandleCache( MaxHandles: integer );
var
  ii: integer;
  tmpPtr: pisc_stmt_handle;
  SaveCW: word;
begin
  if MaxHandles < 0 then
    MaxHandles := 0;
  if MaxHandles = 0 then
    SchemaCache.FreeResources;
  if FStmtHandleCount > MaxHandles then
  begin
    for ii := FStmtHandleCount - 1 downto MaxHandles do
    begin
      tmpPtr := pisc_stmt_handle( longint( FStmtHandleCache ) +
                                  ii * SizeOf( isc_stmt_handle ));
      if Assigned( tmpPtr^ ) then
      begin
        try
          with IB_Session do
          begin
            asm fstcw [SaveCW] end;
            errcode := isc_dsql_free_statement( @status, tmpPtr, DSQL_DROP );
            asm fldcw [SaveCW] end;
            if ( errcode <> 0 ) and ( errcode <> isc_network_error ) then
              raise EIB_ConnectionError.CreateWithSender( Self,
                                               'This is a potential problem: ' +
                                                IntToStr( errcode ));
          end;
          tmpPtr^ := nil;
        except
        end;
      end;
    end;
    FStmtHandleCount := MaxHandles;
  end;
  if FStmtHandleCapacity > FStmtHandleCount then
  begin
    IB_ReallocMem( FStmtHandleCache,
                   FStmtHandleCapacity * SizeOf(isc_stmt_handle),
                   FStmtHandleCount    * SizeOf(isc_stmt_handle));
    FStmtHandleCapacity := FStmtHandleCount;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Connection.RequestReconnect;
begin
  FRequestReconnect := true;
end;

procedure TIB_Connection.ProcessDMLCacheItem(
  var AKeyFieldNames: string;
  var AKeyFieldValues: variant;
  var ADMLCacheItemType: TIB_DMLCacheItemType);
var
  Accept: boolean;
begin
  Accept := true;
  if Assigned( OnReceiveDMLCache ) then
    OnReceiveDMLCache( Self,
                       AKeyFieldNames,
                       AKeyFieldValues,
                       ADMLCacheItemType,
                       Accept );
  if Accept then
    SchemaCache.Transaction.AddDMLCacheItem( Self,
                                             nil,
                                             AKeyFieldNames,
                                             AKeyFieldValues,
                                             ADMLCacheItemType );
end;

function TIB_Connection.VerifyConnection: boolean;
var
  Results: array [0..1023] of Char;
  Items: array [0..1] of Char;
begin
  Result := true;
  if not Connected then Exit;
  FillChar( Results, SizeOf(Results), #0 );
  Items[ 0 ] := char( isc_info_forced_writes );
  Items[ 1 ] := char( isc_info_end );
  with IB_Session do
  begin
    if not Assigned( isc_attach_database ) then
      RevertToOriginalHooks; // does an acquire of hooks if necessary
    errcode := isc_database_info( @status,
                                  PdbHandle,
                                  SizeOf( Items ),
                                  @Items,
                                  SizeOf( Results ),
                                  @Results );

    Result := errcode = 0;
    if ConnectionLostErrCode( errcode ) then
      SetLostConnection;
  end;
end;

procedure TIB_Connection.SetFocus;
begin
  IB_Session.FocusedConnection := Self;
end;

procedure TIB_Connection.DoGainFocus;
begin
  if ( not ( csReading    in ComponentState )) and
     ( not ( csDestroying in ComponentState )) then
    if Assigned( OnGainFocus ) then
      OnGainFocus( Self );
end;

procedure TIB_Connection.DoLoseFocus;
begin
  if ( not ( csReading    in ComponentState )) and
     ( not ( csDestroying in ComponentState )) then
    if Assigned( OnLoseFocus ) then
      OnLoseFocus( Self );
end;

procedure TIB_Connection.SetdbHandleShared( AValue: isc_db_handle );
begin
  if dbHandle <> AValue then
  begin
    if Assigned( AValue ) then
    begin
      with IB_Session do
      begin
        if ( not Assigned( isc_dsql_execute_immediate )) then
          RevertToOriginalHooks; // does an acquire of hooks if necessary
      end;
      dbHandle := AValue;
      FIsHandleShared := true;
    end
    else
      dbHandle := AValue;
  end;
end;

procedure TIB_Connection.SetdbHandle( AValue: isc_db_handle );
var
  ii: integer;
begin
  if FdbHandle = AValue then Exit;
  if Assigned( AValue ) and Connected and ( not IsHandleShared ) then
    for ii := 0 to IB_Session.Session_Connections.Count - 1 do
      with TIB_Connection( IB_Session.Session_Connections.Items[ ii ] ) do
        if AValue = dbHandle then
        begin
          Self.FIsHandleShared := true;
          Break;
        end;
  Connected := false; //  A nil value being passed in forces a disconnect.
  if ( AValue <> nil ) and ( not ( ConnectionStatus = csConnectPending )) then
  begin
    FConnectionStatus := csForcedConnectPending;
    SysBeforeConnect;
  end;
  FdbHandle := AValue;
  if Connected then
  begin
    with IB_Session do
    begin
      if not Assigned( isc_dsql_execute_immediate ) then
        RevertToOriginalHooks; // does an acquire of hooks if necessary
    end;
  // Get the database information associated to this new handle and put it
  // in the TIB_Connection instance to preserve integrity.
    SysUpdateCharacteristics;
    Params.Values[ IBO_SERVER ] := Characteristics.dbSite;
    Params.Values[ IBO_PATH ] := Characteristics.dbFile;
    Params.Values[ IBO_RESERVE_PAGE_SPACE ] := '';
    Params.Values[ IBO_FORCED_WRITES ] := '';
    Params.Values[ IBO_PAGE_SIZE ] := '';
  // I cannot get the protocol from a raw handle!
  // Default it to TCP/IP if necessary.
    if Characteristics.dbFilePrefix <> 2 then // Is a remote connection!
    begin
      if ( Params.Values[ IBO_PROTOCOL ] = EmptyStr          ) or
         ( Params.Values[ IBO_PROTOCOL ] = IBO_PROTOCOL_LOCAL ) then
        Params.Values[ IBO_PROTOCOL ] := IBO_PROTOCOL_TCP_IP;
    end;
    FLoginWasUsed := false;
    FLastConnected := now;
    if ( AValue <> nil ) and ( not ( ConnectionStatus = csConnectPending )) then
      SysAfterConnect;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Connection.DoLinkEvent( AEvent: TIB_ConnectionEventType );
var
  ii: integer;
begin
  for ii := ConnectionLinkCount - 1 downto 0 do
    with TIB_ConnectionLink( FConnectionLinkList.Items[ ii ] ) do
      ProcessEvent( AEvent );
end;

procedure TIB_Connection.SetAnnounceFocus( AValue: boolean );
begin
  if AnnounceFocus <> AValue then
  begin
    FAnnounceFocus := AValue;
    if AnnounceFocus then
      IB_Session.FocusedConnection := Self;
  end;
end;

procedure TIB_Connection.SetAliasName( const AValue: string );
begin
  if AliasName <> AValue then
  begin
    FAliasName := AValue;
    if Assigned( FAliasParams ) then
    begin
      FAliasParams.Free;
      FAliasParams := nil;
    end;
  end;
end;

function TIB_Connection.GetAliasParams: TIB_StringList;
begin
  if not Assigned( FAliasParams ) then
  begin
    FAliasParams := TIB_StringList.Create;
    if AliasName <> '' then
      GetBDEAliasInfo( 'ALIAS', AliasName, FAliasParams );
  end;
  Result := FAliasParams;
end;

procedure TIB_Connection.SetKeepConnection( AValue: boolean );
begin
// Do nothing for now here. Needs to stay true.
end;

procedure TIB_Connection.ProcessPassiveTasks( var IsDone,
                                                  IsWaiting,
                                                  Terminate: boolean );
var
  ii: integer;
  SaveCW: word;
begin
  IsDone := true;
  Exit;
// Be sure to be sensitive to events and shared connection handles.
//IBO4!!!
  if Connected and ( not IsHandleShared ) and
     ( FStartedTransactionCount = 0 ) then
    if ( now - FLastOpened ) > ( 10 / 88400 ) { 10 seconds for testing} then
    begin
      for ii := 0 to StatementCount - 1 do
        with Statements[ ii ] do
        begin
          if Assigned( PstHandle^ ) and ( PstHandle^ <> FakePointer ) then
          begin
            with IB_Session do
            begin
              asm fstcw [SaveCW] end;
              errcode := isc_dsql_free_statement(@status, PstHandle, DSQL_DROP);
              asm fldcw [SaveCW] end;
            end;
            PstHandle^ := FakePointer;
            InvalidateSQL;
          end;
        end;
      FreeStmtHandleCache( 0 );
      API_Disconnect;
      DoLinkEvent( cetConnectedChanged )
    end;
end;

function TIB_Connection.Gen_ID( const AGenerator: string;
                                      Increment: integer ): ISC_INT64;
begin
  if not GetGeneratorValue( Self,
                            Self.SchemaCache.Transaction,
                            mkIdent( AGenerator ),
                            Increment,
                            Result ) then
    raise EIB_StatementError.CreateWithSender( Self, E_GeneratorFailed );
end;

function TIB_Connection.mkIdent( const AString: string ): string;
begin
  Result := IB_Parse.mkIdent( SQLDialect,
                              Characteristics.dbODS_Version,
                              Characteristics.dbODS_Minor_Version,
                              AString );

end;

function TIB_Connection.mkFldLst( const AString: string ): string;
begin
  Result := IB_Parse.mkFldLst( SQLDialect,
                               Characteristics.dbODS_Version,
                               Characteristics.dbODS_Minor_Version,
                               AString );
end;

function TIB_Connection.mkVarIdent( const AString: string ): string;
begin
  Result := IB_Parse.mkVarIdent( SQLDialect,
                                 Characteristics.dbODS_Version,
                                 Characteristics.dbODS_Minor_Version,
                                 AString );
end;

procedure TIB_Connection.ImportServerDefaults;
var
  ii, jj: integer;
  IB_Query: TIB_Query;
  ConnectedState: boolean;
begin
  ConnectedState := Connected;
  if not Connected then
    Connect;
  DefaultValues.Clear;
  IB_Query := TIB_Query.Create( Self );
  try
    BeginBusy( true );
    for ii := 0 to SchemaCache.TableNames.Count - 1 do
    begin
      IB_Query.SQL.Clear;
      IB_Query.SQL.Add( 'SELECT * FROM ' + SchemaCache.TableNames[ ii ] );
      IB_Query.DefaultValues.Clear;
      IB_Query.Prepare;
      IB_Query.ImportServerDefaults;
      for jj := 0 to IB_Query.DefaultValues.Count-1 do
        DefaultValues.Add( IB_Query.DefaultValues[ jj ] );
      IB_Query.Unprepare;
    end;
  finally
    IB_Query.Free;
    EndBusy;
  end;
  if not ConnectedState then
    Connected := False;
end;

procedure TIB_Connection.DisconnectToPool;
var
  tmpPI: TIB_ConnectionPoolItem;
begin
  if not Assigned( ConnectionPool ) then
    ConnectionPool := TIB_ConnectionPool.Create;
  if Connected and not IsHandleShared then
  begin
    with ConnectionPool do
    begin
      tmpPI := TIB_ConnectionPoolItem.Create;
      tmpPI.FdbHandle := dbHandle;
      tmpPI.FDatabase := FConnectedDatabase;
      tmpPI.FUsername := FConnectedUsername;
      tmpPI.FPassword := FConnectedPassword;
      tmpPI.FSQLRoleName := FConnectedSQLRoleName;
      FIsHandleShared := true;
      try
        Disconnect;
        EnterCriticalSection( FPoolLock );
        try
          FPoolItems.Add( tmpPI );
        finally
          LeaveCriticalSection( FPoolLock );
        end;
      except
        tmpPI.Free;
        raise;
      end;
    end;
  end
  else
    Disconnect;
end;

destructor TIB_ConnectionPoolItem.Destroy;
var
  status: status_vector;
begin
  if Assigned( FdbHandle ) then
    dll_detach_database( @status, @FdbHandle );
  inherited Destroy;
end;

constructor TIB_ConnectionPool.Create;
begin
  inherited Create;
  ReserveSessionHookRef;
  FPoolItems := TList.Create;
  InitializeCriticalSection( FPoolLock );
end;

destructor TIB_ConnectionPool.Destroy;
begin
  try
    ClearHandles;
  except
  // Ignore
  end;
  ReleaseSessionHookRef;
  FPoolItems.Free;
  DeleteCriticalSection( FPoolLock );
  inherited Destroy;
end;

procedure TIB_ConnectionPool.ClearHandles;
var
  tmpPI: TIB_ConnectionPoolItem;
begin
  EnterCriticalSection( FPoolLock );
  try
    while FPoolItems.Count > 0 do
    begin
      tmpPI := TIB_ConnectionPoolItem( FPoolItems[0] );
      tmpPI.Free;
      FPoolItems.Delete( 0 );
    end;
  finally
    LeaveCriticalSection( FPoolLock );
  end;
end;

// IBA_ConnectionLink.INT

destructor TIB_ConnectionLink.Destroy;
begin
  IB_Connection := nil;
  inherited Destroy;
end;

procedure TIB_ConnectionLink.Loaded;
begin
  inherited Loaded;
  if Assigned( IB_Session ) then
    DoReceiveFocus( IB_Session.FocusedConnection );
end;

function TIB_ConnectionLink.GetConnected: boolean;
begin
  Result := Assigned( IB_Connection ) and IB_Connection.Connected
end;

function TIB_ConnectionLink.GetConnectionStatus: TIB_ConnectionStatus;
begin
  if IB_Connection <> nil then
    Result := IB_Connection.ConnectionStatus
  else
    Result := csDisconnected;
end;

procedure TIB_ConnectionLink.SetConnection( AValue: TIB_Connection );
begin
  if AValue <> IB_Connection then
  begin
    ProcessEvent( cetBeforeAssignment );
    if IB_Connection <> nil then
      IB_Connection.RemoveConnectionLink( Self );
    FIB_Connection := AValue;
    if IB_Connection <> nil then
      IB_Connection.AddConnectionLink( Self );
    ProcessEvent( cetAfterAssignment );
  end;
end;

procedure TIB_ConnectionLink.SetReceiveFocus( AValue: boolean );
begin
  if ReceiveFocus <> AValue then
  begin
    FReceiveFocus := AValue;
    DoReceiveFocus( IB_Session.FocusedConnection );
  end;
end;

procedure TIB_ConnectionLink.DoReceiveFocus( C: TIB_Connection );
begin
  if ReceiveFocus and ( IB_Connection <> C ) then
  begin
    if Assigned( OnReceiveFocus ) then
      OnReceiveFocus( Self, C )
    else
      IB_Connection := C;
  end;
end;

procedure TIB_ConnectionLink.ProcessEvent( AEvent: TIB_ConnectionEventType );
begin
  if Assigned( OnProcessEvent ) then OnProcessEvent( Self, AEvent );
end;


// IBA_Transaction.INT

constructor TIB_Transaction.Create(AOwner : TComponent);
begin
  inherited Create( AOwner );
  FStatementList := TList.Create;
  FDatasetList := TList.Create;
  FConnectionLinkList := TList.Create;
  FTransactionLinkList := TList.Create;
  FConfirmCancelPrompt := TIB_StringList.Create;
  FConfirmClosePrompt := TIB_StringList.Create;
  FDMLCache := TIB_TransactionDMLCache.Create( Self );
  FtrHandle := nil;
  FRecVersion := true;
  FTPBLength := 1024;
  FStartedDateTime := now;
  FLastStarted := now;
  FLastStopped := now;
  FPaused := 0;
  FPauseDisabled := 0;
  FResumeFromCommit := false;
  FIsolation := tiCommitted;
  FTimeoutProps := TIB_TimeoutProps.Create;
  FTimeoutProps.FAllowCheckOAT := 120;
  FTimeoutProps.FNextAttempt := 900;
  FTimeoutProps.FAttempt := 1200;
  FTimeoutProps.FAttemptMaxRows := 5000;
  FTimeoutProps.FAttemptTicks := 150;
  FTimeoutProps.FAttemptRetry := 5;
  FTimeoutProps.FForceClosed := 0;
  FTimeoutProps.FNextPromptUser := 0;
  FTimeoutProps.FPromptUser := 0;
  FTimeoutProps.FPromptUserDuration := 15;
  FTimeoutProps.FPromptUserRetry := 60;
end;

destructor TIB_Transaction.Destroy;
begin
  while IsPaused do
    Resume( false );
  if Started or InTransaction or TransactionIsActive then
    Close;
  while TransactionLinkCount > 0 do
    TIB_TransactionLink(FTransactionLinkList.Items[ 0 ]).IB_Transaction := nil;
  SysRemoveAllConnections;
  inherited Destroy;
  FTransactionLinkList.Free;
  FConnectionLinkList.Free;
  FStatementList.Free;
  FDatasetList.Free;
  FConfirmCancelPrompt.Free;
  FConfirmClosePrompt.Free;
  FDMLCache.Free;
  FDMLCache := nil;
  FTimeoutProps.Free;
  FTimeoutProps := nil;
end;

{  Public methods                                                              }

procedure TIB_Transaction.ApplyUpdates(
                                       const ADatasets: array of TIB_BDataset );
var
  ii: integer;
  tmpDataset: TIB_BDataset;
begin
  if TransactionState = tsActive then
    raise EIB_TransactionError.CreateWithSender( Self, E_Transaction_Is_Active );
  try
    BeginBusy( true );
    StartTransaction;
    try
      if ( High( ADatasets ) < 0 ) or
         ( ADatasets[ Low( ADatasets ) ] = nil ) then
      begin
        for ii := 0 to DatasetCount - 1 do
        begin
          if Datasets[ ii ] is TIB_BDataset then
            if Assigned( TIB_BDataset(Datasets[ ii ]).MasterSource ) then
              TIB_BDataset(Datasets[ ii ]).ApplyUpdates;
        end;
        for ii := 0 to DatasetCount - 1 do
        begin
          if Datasets[ ii ] is TIB_BDataset then
            if not Assigned( TIB_BDataset(Datasets[ ii ]).MasterSource ) then
              TIB_BDataset(Datasets[ ii ]).ApplyUpdates;
        end;
      end
      else
        for ii := Low( ADatasets ) to High( ADatasets ) do
        begin
          tmpDataset := ADatasets[ii];
          if tmpDataset.IB_Transaction <> Self then
            raise EIB_TransactionError.CreateWithSender( Self,
                                                       Format( E_UpdateWrongTrn,
                                                      [tmpDataset.Name, Name]));
          ADatasets[ii].ApplyUpdates;
        end;
      Commit;
    except
      Rollback;
      raise;
    end;
    if ( High( ADatasets ) < 0 ) or
       ( ADatasets[ Low( ADatasets ) ] = nil ) then
    begin
      for ii := 0 to DatasetCount - 1 do
      begin
        if Datasets[ ii ] is TIB_BDataset then
          TIB_BDataset(Datasets[ ii ]).CommitUpdates;
      end;
    end
    else
      for ii := Low( ADatasets ) to High( ADatasets ) do
        ADatasets[ii].CommitUpdates;
  finally
    EndBusy;
  end;
end;

procedure TIB_Transaction.CancelUpdates(
                                       const ADatasets: array of TIB_BDataset );
var
  ii: Integer;
  tmpDataset: TIB_BDataset;
begin
  if ( High( ADatasets ) < 0 ) or
     ( ADatasets[ Low( ADatasets ) ] = nil ) then
  begin
    for ii := 0 to DatasetCount - 1 do
    begin
      if Datasets[ ii ] is TIB_BDataset then
        TIB_BDataset(Datasets[ ii ]).CancelUpdates;
    end;
  end
  else
    for ii := 0 to High( ADatasets ) do
    begin
      tmpDataset := ADatasets[ii];
      if tmpDataset.IB_Transaction <> Self then
        raise EIB_TransactionError.CreateWithSender( Self,
                                                     Format( E_UpdateWrongTrn,
                                                   [ tmpDataset.Name, Name ]));
      ADatasets[ii].CancelUpdates;
    end;
end;

procedure TIB_Transaction.AddDMLCacheItem( AConnection: TIB_Connection;
                                           ADataset: TIB_Dataset;
                                           const AKeyFieldNames: string;
                                           AKeyFieldValues: variant;
                                           ADMLItemType: TIB_DMLCacheItemType );
begin
  FDMLCache.AddItem( AConnection,
                     ADataset,
                     AKeyFieldNames,
                     AKeyFieldValues,
                     ADMLItemType );
end;

procedure TIB_Transaction.ExecuteImmediate( const ACommand: string );
begin
  Started := true;
  if Started then
  begin
    API_ExecuteImmediate( ACommand, nil );
    if Started then
      Activate;
  end
  else
    raise EIB_TransactionError.CreateWithSender( Self, E_NO_TRANS_STARTED );
end;

procedure TIB_Transaction.API_ExecuteImmediate( const ACommand: string;
                                                      AParam: PXSQLDA );
var
  ii: integer;
  WasStarted: boolean;
  SaveCW: word;
begin
  WasStarted := Started;
  try
    with IB_Session do
    begin
      if not Assigned( isc_dsql_execute_immediate ) then
        RevertToOriginalHooks; // does an acquire of hooks if necessary
      for ii := 0 to ConnectionCount - 1 do
      begin
        with Connections[ ii ] do
        begin
          asm fstcw [SaveCW] end;
          errcode := isc_dsql_execute_immediate( @status,
                                                 PdbHandle,
                                                 PtrHandle,
                                                 Length(ACommand),
                                                 PChar(ACommand),
                                                 SQLDialect,
                                                 AParam );
          asm fldcw [SaveCW] end;
          if errcode <> 0 then
            HandleException( Self );
        end;
      end;
    end;
  finally
    if WasStarted and ( not Started ) then
    begin
      for ii := 0 to ConnectionCount - 1 do
        Dec( Connections[ ii ].FStartedTransactionCount );
      UpdateStatus;
      IB_Session.ResetTimerNotification( Self );
    end;
  end;
end;

procedure TIB_Transaction.StartTransaction;
begin
  if InTransaction then
    raise EIB_TransactionError.CreateWithSender( Self, E_TRANS_STARTED )
  else
  if ServerAutoCommit then
    raise EIB_TransactionError.CreateWithSender( Self, E_SVRAUTOCMT_NOEXP )
  else
    FInTransaction := true;
  UpdateStatus;
end;

procedure TIB_Transaction.Commit;
begin
  if Started or InTransaction or TransactionIsActive then
    try
      BeginBusy( false );
      SysCommit( false );
    finally
      EndBusy;
    end
  else
    UpdateStatus;
end;

procedure TIB_Transaction.CommitRetaining;
begin
  if Started or InTransaction or TransactionIsActive then
    try
      BeginBusy( false );
      SysCommitRetaining( false );
    finally
      EndBusy;
    end
  else
    UpdateStatus;
end;

procedure TIB_Transaction.SavePoint;
begin
  if Started then
    try
      BeginBusy( false );
      SysCommitRetaining( true );
    finally
      EndBusy;
    end
  else
    UpdateStatus;
end;

procedure TIB_Transaction.LosePoint;
begin
  if Started then
    try
      BeginBusy( false );
      SysRollbackRetaining( true );
    finally
      EndBusy;
    end
  else
    UpdateStatus;
end;

procedure TIB_Transaction.Rollback;
begin
  if Started or InTransaction or TransactionIsActive then
    try
      BeginBusy( false );
      SysRollback( false );
    finally
      EndBusy;
    end
  else
    UpdateStatus;
end;

procedure TIB_Transaction.RollbackRetaining;
begin
  if Started or InTransaction or TransactionIsActive then
    try
      BeginBusy( false );
      SysRollbackRetaining( false );
    finally
      EndBusy;
    end
  else
    UpdateStatus;
end;

procedure TIB_Transaction.Refresh( CommitChanges: boolean );
begin
  try
    BeginBusy( false );
    if CommitChanges then
      SysCommit( true )
    else
      SysRollback( true );
  finally
    EndBusy;
  end;
end;

procedure TIB_Transaction.ChangeIsolation( NewIsolation: TIB_Isolation;
                                           CommitChanges: boolean );
begin
  if Isolation <> NewIsolation then
  begin
    if Started or InTransaction or TransactionIsActive then
      try
        BeginBusy( false );
        if CommitChanges then
        begin
          if SysCommitBegin( true ) then
          begin
            Isolation := NewIsolation;
            SysCommitEnd( true );
          end;
        end
        else
        if SysRollbackBegin( true ) then
        begin
          Isolation := NewIsolation;
          SysRollbackEnd( true );
        end;
      finally
        EndBusy;
      end
    else
      Isolation := NewIsolation;
  end;
end;

procedure TIB_Transaction.PostAll;
begin
  if Started or InTransaction or TransactionIsActive then
    try
      BeginBusy( false );
      SysPostAll( true );
    finally
      EndBusy;
    end;
end;

procedure TIB_Transaction.CancelAll;
begin
  if Started or InTransaction or TransactionIsActive then
    try
      BeginBusy( false );
      SysCancelAll( true );
    finally
      EndBusy;
    end;
end;

procedure TIB_Transaction.Close;
begin
  if Started or InTransaction or TransactionIsActive then try
    BeginBusy( false );
    if not FClosePending then
    try
      try
        FClosePending := true;
        if AutoCommit and ( not FConnectionWasLost ) and
                          ( not InTransaction ) then
          try
    //!! Check for a lost connection.
            SysCommit( false );
          except
            if Started or InTransaction or TransactionIsActive then
              SysRollback( false )
            else
            if not ( csDestroying in ComponentState ) then
              raise;
          end
        else
          SysRollback( false );
      except
        if not ( csDestroying in ComponentState ) then
          raise;
      end;
    finally
      FClosePending := false;
    end;
    if FInTransaction then
      FInTransaction := false;
  finally
    EndBusy;
  end;
end;

function TIB_Transaction.CloseWithConfirm: integer;
var
  AMessage: string;
begin
  Result := idOk; //mrOk;
  try
    if TransactionIsActive then
    begin
      if ConfirmClosePrompt.Count = 0 then
        AMessage := M_Save_Changes
      else
        AMessage := ConfirmClosePrompt.Text;
      case MessageDlg( AMessage, mtConfirmation, mbYesNoCancel, 0 ) of
        idYes: //mrYes:
          if CachedUpdatePendingCount > 0 then
            ApplyUpdates( [nil] )
          else
            Commit;
        idNo: //mrNo:
          if CachedUpdatePendingCount > 0 then
            CancelUpdates( [nil] )
          else
            Rollback;
        idCancel: //mrCancel:
          Result := idCancel; //mrCancel;
      end;
    end
    else
    if Started or InTransaction then
      try
        Commit;
      except
        Rollback;
        raise;
      end;
  except
    on E: Exception do
    begin
      Result := idAbort; //mrAbort;
      Application.HandleException( E );
    end;
  end;
end;

function TIB_Transaction.EndWithConfirm: integer;
var
  AMessage: string;
begin
  Result := idOk; //mrOk;
  try
    if TransactionIsActive then
    begin
      if ConfirmClosePrompt.Count = 0 then
        AMessage := M_Save_Changes
      else
        AMessage := ConfirmClosePrompt.Text;
      case MessageDlg( AMessage, mtConfirmation, mbYesNoCancel, 0 ) of
        idYes: //mrYes:
          if CachedUpdatePendingCount > 0 then
            ApplyUpdates( [nil] )
          else
            CommitRetaining;
        idNo: //mrNo:
          if CachedUpdatePendingCount > 0 then
            CancelUpdates( [nil] )
          else
            RollbackRetaining;
        idCancel: //mrCancel:
          Result := idCancel; //mrCancel;
      end;
    end
    else
    if InTransaction then
      CommitRetaining;
  except
    on E: Exception do
    begin
      Result := idAbort; //mrAbort;
      Application.HandleException( E );
    end;
  end;
end;

procedure TIB_Transaction.SetConfirmCancelPrompt( AValue: TStrings );
begin
  FConfirmCancelPrompt.Assign( AValue );
end;

procedure TIB_Transaction.SetConfirmClosePrompt( AValue: TStrings );
begin
  FConfirmClosePrompt.Assign( AValue );
end;

procedure TIB_Transaction.SysAdjustPendingCount;
begin
  if ( PostPendingCount = 0 ) and ( CachedUpdatePendingCount = 0 ) then
  begin
    if TransactionState = tsActivePending then
    begin
      SetTransactionState( tsInactive );
      UpdateStatus;
    end;
  end
  else
  if TransactionState in [ tsNone, tsInactive ] then
  begin
    SetTransactionState( tsActivePending );
    UpdateStatus;
  end;
end;

procedure TIB_Transaction.SysAdjustPostPendingCount( Adj: integer );
begin
  Inc( FPostPendingCount, Adj );
  SysAdjustPendingCount;
  if FWantPostPendingChanged > 0 then
    ProcessEvent( tetOnPostPendingChanged );
end;

procedure TIB_Transaction.SysAdjustCachedUpdatePendingCount( Adj: integer );
begin
  Inc( FCachedUpdatePendingCount, Adj );
  SysAdjustPendingCount;
  if FWantCachedUpdatePendingChanged > 0 then
    ProcessEvent( tetOnCachedUpdatePendingChanged );
end;

procedure TIB_Transaction.Activate;
begin
  if TransactionState in [ tsNone,
                           tsInactive,
                           tsActivePending ] then
  begin
    SetTransactionState( tsActive );
    UpdateStatus;
  end
  else
  if TransactionState = tsActive then
    SetTransactionState( tsActive )
  else
  if TransactionState in PendingTransactionStates then
  // Allow changes to be posted during the completion of a transaction.
  else
    raise EIB_TransactionError.CreateWithSender( Self, E_Cannot_Activate );
  if FActivateCount < High( cardinal ) then
    Inc( FActivateCount );
  if TimeoutProps.FDisableCheckOATFromError then
    TimeoutProps.FDisableCheckOATFromError := false;
end;

procedure TIB_Transaction.UpdateStatus;
begin
  ProcessEvent( tetOnStatusChanged );
end;

function TIB_Transaction.GetTransactionIsActive: boolean;
begin
  Result := ( PostPendingCount > 0 ) or
            ( CachedUpdatePendingCount > 0 ) or
            ( TransactionState in [ tsActivePending, tsActive ] ) or
            ( TransactionState in PendingTransactionStates );
end;

{  Protected methods                                                           }

function TIB_Transaction.SysStart: boolean;
begin
  if not Started then
  begin
    if ( csReading in ComponentState ) or
       ( csFixups  in ComponentState ) then
      SetTransactionState( tsInactivePending )
    else
    begin
      SysBeforeStart;
      if ( not Started ) and ( TransactionState <> tsNone ) then
      begin
        API_Start;
        SysAfterStart;
        UpdateStatus;
      end;
    end;
  end;
  Result := Started;
end;

procedure TIB_Transaction.SysBeforeStart;
var
  ii: integer;
begin
  if TransactionState = tsNone then
    SetTransactionState( tsInactivePending );
  try
    DoBeforeStart;
    ProcessEvent( tetBeforeStart );
    if ConnectionCount > 0 then
      for ii := 0 to ConnectionCount - 1 do
        with Connections[ ii ] do
          if (( ConnectionStatus in [ csDisconnectPending,
                                      csDropPending ] ) and ( not ReadOnly )) or
             ( not SysConnect( false )) then
            SysFailedStart;
  except
    SysFailedStart;
    raise;
  end;
end;

procedure TIB_Transaction.SysFailedStart;
begin
  ResetTransactionState( true );
  UpdateStatus;
end;

procedure TIB_Transaction.ResetTransactionState( LogicalEnd: boolean );
var
  NewTransactionState: TIB_TransactionState;
begin
  if TransactionState in [ tsSavePointPending,
                           tsLosePointPending,
                           tsCommitRetainingPending,
                           tsRollbackRetainingPending ] then
  begin
    if Started then
      NewTransactionState := tsInactive
    else
      NewTransactionState := tsNone;
  end
  else
    if TransactionState in [ tsInactivePending,
                             tsCommitPending,
                             tsCommitRefreshPending,
                             tsRollbackPending,
                             tsRollbackRefreshPending ] then
      NewTransactionState := tsNone
    else
      NewTransactionState := TransactionState;
  if ( PostPendingCount > 0 ) or
     ( CachedUpdatePendingCOunt > 0 ) then
  begin
    if NewTransactionState in [ tsNone, tsInactive ] then
      NewTransactionState := tsActivePending;
  end
  else
    if Started then
      if NewTransactionState in [ tsNone ] then
        NewTransactionState := tsInactive;
  if ( not LogicalEnd ) and ( NewTransactionState = tsNone ) then
    NewTransactionState := tsInactive;
  SetTransactionState( NewTransactionState );
end;

procedure TIB_Transaction.SysAfterStart;
begin
  if TransactionState = tsInactivePending then
    SetTransactionState( tsInactive )
  else
    SetTransactionState( TransactionState );
  ProcessEvent( tetAfterStart );
  DoAfterStart;
end;

procedure TIB_Transaction.SysCommit( ARefreshing: boolean );
begin
  if SysCommitBegin( ARefreshing ) then
    SysCommitEnd( ARefreshing );
end;

procedure TIB_Transaction.SysProcessCommitAction( const ABeforeEnd,
                                                        ACommitChanges,
                                                        ARefreshing,
                                                        ARetaining: boolean );
var
  ii: integer;
  tmpDataset: TIB_Dataset;
begin
  if ARetaining then Exit;
  ii := 0;
  while ii < DatasetCount do
  begin
    tmpDataset := Datasets[ ii ];
    if ABeforeEnd then
      if tmpDataset.NeedToPost and ( not tmpDataset.FCachedUpdates ) then
        if ACommitChanges then
        begin
          if not FOATPending then
            tmpDataset.SysPost( false, false );
        end
        else
          tmpDataset.SysCancel;
    try
      with tmpDataset do
      begin
        if Self.FClosePending  or
           tmpDataset.Unidirectional or
           ( not Assigned( IB_Connection )) or
           ( IB_Connection.ConnectionStatus in [ csDisconnectPending,
                                                 csDropPending ] ) then
        begin
          if ABeforeEnd then
            Close;
        end
        else
        if ARefreshing then
        begin
          if Unidirectional then
            Close
          else
          if ABeforeEnd then
            {KillCursor}
          else
            Refresh;
        end
        else
          case CommitAction of
            caClose: if ( CursorIsOpen ) or
                        ( BufferActive and ( not FOATPending )) then
            begin
              if ABeforeEnd then
              begin
                if ( not FOATPending ) or ( RefreshAction = raOpen ) then
                  Close;
              end
              else
              begin
                if FOATPending and ( RefreshAction <> raOpen ) and
                   CursorIsOpen then
                  RefreshKeys;
              end;
            end;
            caInvalidateCursor:
              if ( CursorIsOpen ) or
                 ((( not ACommitChanges ) and
                   ( not ARetaining ) and
                   ( not ARefreshing ) and
                    ( FDatasetFlags * [ dsfInsertWasPosted,
                                        dsfEditWasPosted,
                                        dsfDeleteWasPosted,
                                        dsfWasInvalidated,
                                        dsfWasRefreshed ] <> [] ) )) then
                if ABeforeEnd then
                begin
                  if FCachedUpdates and NeedToPost then
                    FetchAll; // This really never should happen.
                end
                else
                if CursorIsOpen then
                  RefreshKeys;
            caFetchAll:
              if ABeforeEnd then
                if Active then
                  FetchAll;
            caRefresh,
            caRefreshKeys:
              if ABeforeEnd then
                {KillCursor}
              else
                if Active then
                  if CommitAction = caRefresh then
                    Refresh
                  else
                    RefreshKeys;
          end;
        if ( not ABeforeEnd ) and ( FDatasetFlags <> [] ) then
          FDatasetFlags := FDatasetFlags - [ dsfInsertWasPosted,
                                             dsfEditWasPosted,
                                             dsfDeleteWasPosted,
                                             dsfWasInvalidated,
                                             dsfWasRefreshed ];
      end;
    except
      on E: Exception do
        Application.HandleException( E );
    end;
    if FDatasetList.IndexOf( tmpDataset ) >= 0 then
      Inc( ii );
  end;
end;

function TIB_Transaction.SysCommitBegin( ARefreshing: boolean ): boolean;
var
  OldStatus: TIB_TransactionState;
begin
  Result := false; 
  if Started or InTransaction or TransactionIsActive then
  begin
    if not ( TransactionState in [ tsCommitPending,
                                   tsCommitRefreshPending,
                                   tsRollbackPending,
                                   tsRollbackRefreshPending ] ) then
    begin
      OldStatus := TransactionState;
      Result := true;
      if ARefreshing then
        SetTransactionState( tsCommitRefreshPending )
      else
        SetTransactionState( tsCommitPending );
      try
        SysBeforeEnd;
        DoBeforeCommit;
        ProcessEvent( tetBeforeCommit );
        // Make sure all datasets are posted prior to applying commit actions.
        if not FOATPending then
          SysPostAll( false );
        SysProcessCommitAction( true, true, ARefreshing, false );
        if Started then
          API_Commit;
        try
          FDMLCache.ProcessItems( true );
        except
          // Do nothing.
        end;
      except
        SetTransactionState( OldStatus );
        raise;
      end;
    end;
  end;
end;

procedure TIB_Transaction.SysCommitEnd( ARefreshing: boolean );
begin
  SysProcessCommitAction( false, true, ARefreshing, false );
  try
    ProcessEvent( tetAfterCommit );
  finally
    ResetTransactionState( true );
    if not ARefreshing then
      FInTransaction := false;
    DoAfterCommit;
    SysAfterEnd( true );
    UpdateStatus;
  end;
end;

procedure TIB_Transaction.SysCommitRetaining( ASavePointOnly: boolean );
var
  ii: integer;
  OldStatus: TIB_TransactionState;
  WasEnded: boolean;
begin
  if Started or InTransaction or TransactionIsActive then
  begin
    OldStatus := TransactionState;
    if ASavePointOnly then
      SetTransactionState( tsSavePointPending )
    else
      SetTransactionState( tsCommitRetainingPending );
    try
      if ASavePointOnly then
        DoBeforeSavePoint
      else
      begin
        DoBeforeCommitRetaining;
        ProcessEvent( tetBeforeCommitRetaining );
        SysPostAll( false );
      end;
      WasEnded := false;
      if TransactionState in [ tsSavePointPending,
                               tsCommitRetainingPending ] then
      begin
        if Started then
        begin
          if ( FOpenCursors = 0 ) and
             ( Isolation = tiCommitted ) and
             ( not ( ASavePointOnly and InTransaction )) and
             ( TimeActive > ( TimeoutProps.AllowCheckOAT / 88400 )) then
          begin
            API_Commit;
            WasEnded := true;
          end
          else
            API_CommitRetaining;
          if ASavePointOnly then
            if FPessimisticLockCount > 0 then
              for ii := 0 to DatasetCount - 1 do
                with Datasets[ ii ] do
                  if FIsRowLocked then
                  begin
                    FIsRowLocked := false;
                    Dec( FPessimisticLockCount );
                  end;
        end;
        try
          FDMLCache.ProcessItems( true );
        except
          // Do nothing.
        end;
      end;
    except
      FTransactionState := OldStatus;
      UpdateStatus;
      raise;
    end;
    try
      if ASavePointOnly then
        ProcessEvent( tetAfterSavePoint )
      else
        ProcessEvent( tetAfterCommitRetaining );
    finally
      ResetTransactionState( true );
      if ASavePointOnly then
        DoAfterSavePoint
      else
      begin
        FInTransaction := false;
        DoAfterCommitRetaining;
      end;
      if WasEnded then
        SysAfterEnd( true );
      UpdateStatus;
    end;
  end;
end;

procedure TIB_Transaction.SysRollbackRetaining( ALosePointOnly: boolean );
var
  ii: integer;
  OldStatus: TIB_TransactionState;
  WasEnded: boolean;
begin
  if Started or InTransaction or TransactionIsActive then
  begin
    OldStatus := TransactionState;
    if ALosePointOnly then
      SetTransactionState( tsLosePointPending )
    else
      SetTransactionState( tsRollbackRetainingPending );
    try
      if ALosePointOnly then
        DoBeforeLosePoint
      else
      begin
        DoBeforeRollbackRetaining;
        ProcessEvent( tetBeforeRollbackRetaining );
        SysCancelAll( false );
      end;
      WasEnded := false;
      if TransactionState in [ tsLosePointPending,
                               tsRollbackRetainingPending ] then
      begin
        if Started then
        begin
          if ( FOpenCursors = 0 ) and
             ( Isolation = tiCommitted ) and
             ( not ( ALosePointOnly and InTransaction )) and
             ( TimeActive > ( TimeoutProps.AllowCheckOAT / 88400 )) then
          begin
            API_Rollback;
            WasEnded := true;
          end
          else
            API_RollbackRetaining;
          if ALosePointOnly then
            if FPessimisticLockCount > 0 then
              for ii := 0 to DatasetCount - 1 do
                with Datasets[ ii ] do
                  if FIsRowLocked then
                  begin
                    FIsRowLocked := false;
                    Dec( FPessimisticLockCount );
                  end;
        end;
        try
          FDMLCache.ProcessItems( false );
        except
          // Do nothing.
        end;
      end;
    except
      FTransactionState := OldStatus;
      UpdateStatus;
      raise;
    end;
    try
      if ALosePointOnly then
        ProcessEvent( tetAfterLosePoint )
      else
        ProcessEvent( tetAfterRollbackRetaining );
    finally
      ResetTransactionState( true );
      if ALosePointOnly then
        DoAfterLosePoint
      else
      begin
        FInTransaction := false;
        DoAfterRollbackRetaining;
      end;
      if WasEnded then
        SysAfterEnd( false );
      UpdateStatus;
    end;
  end;
end;

procedure TIB_Transaction.SysRollback( ARefreshing: boolean );
begin
  if SysRollbackBegin( ARefreshing ) then
    SysRollbackEnd( ARefreshing );
end;

function TIB_Transaction.SysRollbackBegin( ARefreshing: boolean ): boolean;
var
  OldStatus: TIB_TransactionState;
begin
  Result := false;
  if Started or InTransaction or TransactionIsActive then
  begin
    if not ( TransactionState in [ tsCommitPending,
                                   tsCommitRefreshPending,
                                   tsRollbackPending,
                                   tsRollbackRefreshPending ] ) then
    begin
      OldStatus := TransactionState;
      Result := true;
      if ARefreshing then
        SetTransactionState( tsRollbackRefreshPending )
      else
        SetTransactionState( tsRollbackPending );
      try
        SysBeforeEnd;
        DoBeforeRollback;
        ProcessEvent( tetBeforeRollback );
        SysProcessCommitAction( true, false, ARefreshing, false );
        if Started then
          API_Rollback;
        try
          FDMLCache.ProcessItems( false );
        except
          // Do nothing.
        end;
      except
        SetTransactionState( OldStatus );
        raise;
      end;
    end;
  end;
end;

procedure TIB_Transaction.SysRollbackEnd( ARefreshing: boolean );
begin
  SysProcessCommitAction( false, false, ARefreshing, false );
  try
    ProcessEvent( tetAfterRollback );
  finally
    ResetTransactionState( true );
    if not ARefreshing then
      FInTransaction := false;
    DoAfterRollback;
    SysAfterEnd( false );
    UpdateStatus;
  end;
end;

procedure TIB_Transaction.SysBeforeEnd;
begin
  DoBeforeEnd;
  ProcessEvent( tetBeforeEnd );
end;

procedure TIB_Transaction.SysAfterEnd( WasCommitted: boolean );
begin
  ProcessEvent( tetAfterEnd );
  DoAfterEnd;
end;

procedure TIB_Transaction.SysPostAll( IncludeCachedUpdates: boolean );
var
  ii: integer;
  tmpDataset: TIB_Dataset;
  tmpCnt: integer;
begin
  tmpCnt := 0;
  for ii := 0 to DatasetCount - 1 do
  begin
    tmpDataset := Datasets[ ii ];
    if ( tmpDataset.NeedToPost ) and
       (( not tmpDataset.FCachedUpdates ) or IncludeCachedUpdates ) then
    begin
      if Assigned( tmpDataset.MasterSource ) and
         Assigned( tmpDataset.MasterSource.Dataset ) then
        if ( tmpDataset.MasterSource.Dataset.IB_Transaction = Self ) and
           ( tmpDataset.MasterSource.Dataset.State = dssInsert ) then
          tmpDataset.MasterSource.Dataset.SysPost( false, false );
      tmpDataset.SysPost( false, false );
    end;
    if tmpDataset.NeedToPost and tmpDataset.FCachedUpdates then
      Inc( tmpCnt );
  end;
  if ( PostPendingCount - tmpCnt ) > 0 then
    raise EIB_TransactionError.CreateWithSender( Self,
                                                 E_Failed_To_Post_Datasets );
end;

procedure TIB_Transaction.SysCancelAll( IncludeCachedUpdates: boolean );
var
  ii: integer;
  tmpDataset: TIB_Dataset;
  tmpCnt: integer;
begin
  tmpCnt := 0;
  for ii := 0 to DatasetCount - 1 do
  begin
    tmpDataset := Datasets[ ii ];
    if ( tmpDataset.NeedToPost ) and
       (( not tmpDataset.FCachedUpdates ) or IncludeCachedUpdates ) then
      tmpDataset.SysCancel;
    if tmpDataset.NeedToPost and tmpDataset.FCachedUpdates then
      Inc( tmpCnt );
  end;
  if ( PostPendingCount - tmpCnt ) > 0 then
    raise EIB_TransactionError.CreateWithSender( Self,
                                                 E_Failed_To_Cancel_Datasets );
end;

procedure TIB_Transaction.SysBeforeExecDDL;
begin
// Abstract.
end;

procedure TIB_Transaction.SysAfterExecDDL;
begin
// Abstract.
end;

procedure TIB_Transaction.SetAutoCommit( AValue: boolean );
begin
  if FAutoCommit <> AValue then
  begin
    FAutoCommit := AValue;
    SetTransactionState( TransactionState );
  end;
end;

function TIB_Transaction.GetAutoCommit: boolean;
begin
  Result := FAutoCommit or FServerAutoCommit;
end;

function TIB_Transaction.IsAutoCommitStored: boolean;
begin
  Result := AutoCommit and ( not ServerAutoCommit );
end;

procedure TIB_Transaction.SetServerAutoCommit( AValue: boolean );
begin
  if ServerAutoCommit <> AValue then
  begin
    if Started then
      if AValue then
        Commit;
      Close;
    FServerAutoCommit := AValue;
  end;
end;

procedure TIB_Transaction.SetStarted( AValue: boolean );
begin
  if Started <> AValue then
  begin
    if AValue then
      SysStart
    else
      Close;
  end;
end;

function TIB_Transaction.NeedTimerNotifications: boolean;
begin
  Result := Started or inherited NeedTimerNotifications;
end;

procedure TIB_Transaction.SetTransactionState( AValue: TIB_TransactionState );
begin
  if IsPaused then
    IsPausedError;
  if AutoCommit and ( AValue = tsActive ) and ( not InTransaction ) then
  begin
    if not ServerAutoCommit then
      SavePoint;
  end
  else
  if TransactionState <> AValue then
  begin
    if TransactionState = tsNone then
    begin
      FActivateCount := Low( cardinal );
      FStartedDateTime := now;
    end;
    FTransactionState := AValue;
    UpdateStatus;
  end;
end;

function TIB_Transaction.GetStarted: boolean;
begin
  Result := FtrHandle <> nil;
end;

function TIB_Transaction.GetPdbHandles( Index: integer ): pisc_db_handle;
begin
  if ( Index < 0 ) or ( Index > ConnectionCount ) then
    Result := nil
  else
    Result := Connections[ Index ].PdbHandle;
end;

function TIB_Transaction.GetPtrHandle: pisc_tr_handle;
begin
  Result := @FtrHandle;
end;

function TIB_Transaction.GetStatementCount: integer;
begin
  if FStatementList <> nil then
    Result := FStatementList.Count
  else
    Result := 0;
end;

function TIB_Transaction.GetStatement( Index: integer ): TIB_Statement;
begin
  if ( Index >= 0 ) and
     ( Index <= StatementCount - 1 ) and
     ( FStatementList <> nil )then
    Result := TIB_Statement( FStatementList.Items[ Index ] )
  else
    Result := nil;
end;

function TIB_Transaction.GetDatasetCount: integer;
begin
  if FDatasetList <> nil then
    Result := FDatasetList.Count
  else
    Result := 0;
end;

function TIB_Transaction.GetDataset( Index: integer ): TIB_Dataset;
begin
  if ( Index >= 0 ) and
     ( Index <= DatasetCount - 1 ) and
     ( FDatasetList <> nil )then
    Result := TIB_Dataset( FDatasetList.Items[ Index ] )
  else
    Result := nil;
end;

function TIB_Transaction.GetConnectionCount: integer;
begin
  if FConnectionLinkList <> nil then
    Result := FConnectionLinkList.Count
  else
    Result := 0;
end;

function TIB_Transaction.GetConnection( Index: integer ): TIB_Connection;
begin
  if ( Index >= 0 ) and
     ( Index <= ConnectionCount - 1 ) and
     ( FConnectionLinkList <> nil ) then
    Result := TIB_ConnectionLink(
                              FConnectionLinkList.Items[ Index ] ).IB_Connection
  else
    Result := nil;
end;

function TIB_Transaction.GetConnectionIndex( Index: TIB_Connection ): integer;
var
  i: integer;
begin
  Result := -1;
  for i := 0 to ConnectionCount - 1 do
    if Index = Connections[ i ] then
    begin
      Result := i;
      Break;
    end;
end;

procedure TIB_Transaction.SetReadOnly( AValue: boolean );
begin
  if ReadOnly <> AValue then
  begin
    CheckOAT;
    if Started then
      raise EIB_TransactionError.CreateWithSender( Self, E_NO_CHANGE_READONLY )
    else
      FReadOnly := AValue;
  end;
end;

procedure TIB_Transaction.SetIsolation( AValue: TIB_Isolation );
begin
  if Isolation <> AValue then
  begin
    CheckOAT;
    if Started then
      raise EIB_TransactionError.CreateWithSender( Self, E_NO_CHANGE_ISOLATION )
    else
      FIsolation := AValue;
  end;
end;

procedure TIB_Transaction.SetRecVersion( AValue: boolean );
begin
  if RecVersion <> AValue then
  begin
    CheckOAT;
    if Started then
      raise EIB_TransactionError.CreateWithSender( Self,
                                                   E_NO_CHANGE_RECVERSION )
    else
      FRecVersion := AValue;
  end;
end;

procedure TIB_Transaction.SetLockWait( AValue: boolean );
begin
  if LockWait <> AValue then
  begin
    CheckOAT;
    if Started then
      raise EIB_TransactionError.CreateWithSender( Self, E_NO_CHANGE_LOCKWAIT )
    else
      FLockWait := AValue;
  end;
end;

function TIB_Transaction.GetIsPaused: boolean;
begin
  Result := FPaused > 0;
end;

function TIB_Transaction.GetIsPauseDisabled: boolean;
begin
  Result := not ( FPauseDisabled = 0 );
end;

function TIB_Transaction.GetTimeActive: TDateTime;
begin
  if not Started then
    Result := 0
  else
    Result := now - FLastStarted;
end;

procedure TIB_Transaction.IsPausedError;
begin
  raise EIB_TransactionError.CreateWithSender( Self, E_TransactionPaused );
end;

// Note that this function theoretically allows a transaction that
// has not been started to be paused.  Essentially "locking" the
// transaction from auto-start until the transaction is resumed.
// It can also return true when already paused, allowing pause
// to go into multiple depth.
function TIB_Transaction.GetCanPause: boolean;
begin
  // Only pause a live transaction
  // NB. NEVER allow override of FPauseDisabled
  Result := Started and ( FPauseDisabled = 0 );
  if Result then
  begin
    // Dont permit a pause in the middle of any commit/rollback
    // pending states.
    // Not sure about Active/Inactive Pending - I'll try without
    // for the moment because I think these are potentially long
    // term states (started but not "InTransaction").
    if TransactionState in PendingTransactionStates then
    begin
      Result := false;
    end else
    begin
      Result := not TransactionIsActive;
      if Assigned( FOnGetCanPause ) then
        FOnGetCanPause( Self, Result )
    end;
  end;
end;

procedure TIB_Transaction.DoPauseChanged;
begin
  if Assigned( FOnPauseChanged ) then
    FOnPauseChanged( Self );
end;

function TIB_Transaction.Pause( CommitChanges: boolean ): boolean;
begin
  if CanPause then
  begin
    if FPaused = 0 then
    begin
      try
        FIsPausePending := true;
        BeginBusy( false );
        if CommitChanges then
        begin
          SysCommitBegin( true );
          FResumeFromCommit := true;
        end
        else
        begin
          SysRollbackBegin( true );
          FResumeFromCommit := false;
        end;
      finally
        FIsPausePending := false;
        EndBusy;
      end;
      Inc( FPaused );
      DoPauseChanged;
    end
    else
      Inc( FPaused );
  end;
  Result := IsPaused;
end;

function TIB_Transaction.Resume( WithRestart: boolean ): boolean;
begin
  if IsPaused then
  begin
    Dec( FPaused ); // Must occur BEFORE the *End calls to allow state change
    if FPaused = 0 then
    begin
      try
        BeginBusy( false );
        try
          if FResumeFromCommit then
          begin
            SysCommitEnd( WithRestart );
            FResumeFromCommit := false;
          end
          else
            SysRollbackEnd( WithRestart );
        except
          Inc( FPaused ); // reset pause state if except occurs
          raise;
        end;
      finally
        EndBusy;
      end;
      DoPauseChanged;
    end;
  end;
  Result := not IsPaused;
end;

procedure TIB_Transaction.DisablePause;
begin
  if IsPaused then
    IsPausedError;
  Inc( FPauseDisabled );
end;

procedure TIB_Transaction.EnablePause;
begin
  if FPauseDisabled > 0 then
    Dec( FPauseDisabled );
end;

{  Action routines                                                             }

procedure TIB_Transaction.API_Start;
var
  pteb: pisc_teb;
  teb_buffer: pointer;
  ii: integer;
  BufInd: longint;
  buffer: pchar;
  tmpTPBLen: smallint;
  ticks: DWORD;
  procedure BuildTPB( Item: byte; Contents: string);
  begin
    buffer[ BufInd ] := Char(Item);
    Inc( BufInd );
    buffer[ BufInd ] := Char(Length(Contents));
    Inc( BufInd );
    StrPCopy( @buffer[ BufInd ], Contents );
    Inc( BufInd, Length( Contents ));
  end;
var
  tmpReadOnly: boolean;
begin
  if ( ConnectionCount = 0 ) and
     IB_Session.AllowDefaultConnection then
    SysAddConnection( IB_Session.DefaultConnection );
  if ConnectionCount = 0 then
    raise EIB_TransactionError.CreateWithSender( Self, E_NO_CONNECTIONS );
  tmpReadOnly := false;
  for ii := 0 to ConnectionCount - 1 do
    with Connections[ ii ] do
    begin
      Connected := true;
      if not Connected then
        Exit { The connection component is still being loaded in.             }
             { Otherwise the above Connection would have raised an exception. }
      else
        tmpReadOnly := tmpReadOnly or Characteristics.dbReadOnly;
    end;
  if Started then
    Exit; { The above code may cause a recursive call. }
          { This will terminate it!                    }
  if IsPaused then // dont permit start while paused
    IsPausedError;
// Localize the buffer length.
  tmpTPBLen := TPBLength;
  Buffer := StrAlloc( tmpTPBLen );
  try
    BufInd := 0;
    buffer[ BufInd ] := Char( isc_tpb_version3 );
    Inc( BufInd );
    if ReadOnly or tmpReadOnly then 
      buffer[ BufInd ] := Char( isc_tpb_read )
    else
      buffer[ BufInd ] := Char( isc_tpb_write );
    Inc( BufInd );
    case Isolation of
      tiConcurrency: buffer[ BufInd ] := Char( isc_tpb_concurrency    );
      tiConsistency: buffer[ BufInd ] := Char( isc_tpb_consistency    );
      tiCommitted:   buffer[ BufInd ] := Char( isc_tpb_read_committed );
    end;
    Inc( BufInd );
    if Isolation = tiCommitted then
    begin
      if RecVersion then
        buffer[ BufInd ] := Char( isc_tpb_rec_version )
      else
        buffer[ BufInd ] := Char( isc_tpb_no_rec_version );
      Inc( BufInd );
    end;
    if LockWait then
      buffer[ BufInd ] := Char( isc_tpb_wait )
    else
      buffer[ BufInd ] := Char( isc_tpb_nowait );
    Inc( BufInd );
    if ServerAutoCommit then
    begin
      buffer[ BufInd ] := Char( isc_tpb_autocommit );
      Inc( BufInd );
    end;
//    buffer[ BufInd ] := Char( isc_tpb_no_auto_undo );
//    Inc( BufInd );
// Use nil to indicate that customizations should be applied to all connections.
    if ConnectionCount > 1 then
      DoCustomizeTPB( nil, BufInd, buffer );
    teb_buffer := AllocMem(( SizeOf(isc_teb) + tmpTPBLen ) * ConnectionCount );
    try
      for ii := 0 to ConnectionCount - 1 do
      begin
        pteb := ptr( integer( teb_buffer ) + SizeOf( isc_teb ) * ii );
        with Connections[ ii ], pteb^ do
        begin
          db_ptr  := PdbHandle;
          tpb_len := BufInd;
          tpb_ptr := pchar( integer( teb_buffer ) +
                            SizeOf( isc_teb ) * ConnectionCount +
                            tmpTPBLen * ii );
          Move( Buffer^, tpb_ptr^, tpb_len );
          DoCustomizeTPB( Connections[ ii ], tpb_len, tpb_ptr );
        end;
      end;
      pteb := teb_buffer;
      with IB_Session do
      begin
        errcode := isc_conn_lost;
        ticks := GetTickCount + 2000;
        while ( errcode = isc_conn_lost ) and ( ticks > GetTickCount ) do
          errcode := isc_start_multiple( @status,
                                         PtrHandle,
                                         ConnectionCount,
                                         pteb );
        if errcode <> 0 then HandleException( Self );
      end;
    finally
      FreeMem( teb_buffer );
    end;
  finally
    StrDispose( Buffer );
  end;
  if Started then
  begin
    TimeOutProps.FDisableCheckOATFromError := false;
    TimeoutProps.FNextAttempt := TimeoutProps.FAttempt;
    TimeoutProps.FNextPromptUser := TimeoutProps.FPromptUser;
    FLastStarted := now;
    FConnectionWasLost := false;
    for ii := 0 to ConnectionCount - 1 do
      Inc( Connections[ ii ].FStartedTransactionCount );
    IB_Session.ResetTimerNotification( Self );
  end;
end;

function TIB_Transaction.CheckLostConnection: boolean;
var
  ii: integer;
begin
  with IB_Session do
  begin
    Result := ( errcode = isc_network_error ) or
              ( errcode = isc_conn_lost ) or
              ( errcode = isc_shutdown );
    if Result then
    begin
      FConnectionWasLost := true;
      if ConnectionCount = 1 then
        Connections[0].SetLostConnection
      else
        for ii := 0 to ConnectionCount - 1 do
          Connections[ii].VerifyConnection;
    end;
  end;
end;

procedure TIB_Transaction.API_Commit;
var
  ii: integer;
  SaveCW: word;
begin
  if not Started then Exit;
  with IB_Session do
  begin
    asm fstcw [SaveCW] end;
    errcode := isc_commit_transaction( @status, PtrHandle );
    asm fldcw [SaveCW] end;
//  if errcode = isc_no_meta_update then begin
    if errcode <> 0 then
    begin
      if not CheckLostConnection then
      begin
        for ii := 0 to ConnectionCount - 1 do
          Connections[ ii ].FlushSchemaCache;
        if Started then
        begin
          asm fstcw [SaveCW] end;
          errcode := isc_commit_transaction( @status, PtrHandle );
          asm fldcw [SaveCW] end;
        end;
      end;
    end;
    if errcode <> 0 then
    begin
      TimeOutProps.FDisableCheckOATFromError := true;
      HandleException( Self );
    end;
  end;
  if not Started then
  begin
    FLastStopped := now;
    for ii := 0 to ConnectionCount - 1 do
      Dec( Connections[ ii ].FStartedTransactionCount );
    IB_Session.ResetTimerNotification( Self );
  end;
end;

procedure TIB_Transaction.API_CommitRetaining;
var
  ii: integer;
  SaveCW: word;
begin
  with IB_Session do
  begin
    asm fstcw [SaveCW] end;
    errcode := isc_commit_retaining( @status, PtrHandle);
    asm fldcw [SaveCW] end;
//  if errcode = isc_no_meta_update then begin
    if errcode <> 0 then
    begin
      if not CheckLostConnection then
      begin
        for ii := 0 to ConnectionCount - 1 do
          Connections[ ii ].FlushSchemaCache;
        asm fstcw [SaveCW] end;
        errcode := isc_commit_retaining( @status, PtrHandle);
        asm fldcw [SaveCW] end;
      end;
    end;
    if errcode <> 0 then HandleException( Self );
  end;
  // NB. The transaction was NOT stopped here?
end;

procedure TIB_Transaction.API_Rollback;
var
  ii: integer;
  SaveCW: word;
begin
  if not Started then Exit;
  if not FConnectionWasLost then
    with IB_Session do
    begin
      asm fstcw [SaveCW] end;
      isc_rollback_transaction( @status, PtrHandle );
      asm fldcw [SaveCW] end;
    end;
  PtrHandle^ := nil;
  FLastStopped := now;
  for ii := 0 to ConnectionCount - 1 do
    Dec( Connections[ ii ].FStartedTransactionCount );
  IB_Session.ResetTimerNotification( Self );
end;

procedure TIB_Transaction.API_RollbackRetaining;
var
  ii: integer;
  SaveCW: word;
begin
  if Assigned( dll_rollback_retaining ) then
  begin
    with IB_Session do
    begin
      asm fstcw [SaveCW] end;
      errcode := isc_rollback_retaining( @status, PtrHandle);
      asm fldcw [SaveCW] end;
      if errcode <> 0 then
        HandleException( Self );
    end;
  end
  else
  begin
    API_Rollback;
    API_Start;
  end;
  for ii := 0 to DatasetCount - 1 do
    try
      with Datasets[ ii ] do
      begin
        if Unidirectional then
        begin
          if not Assigned( dll_rollback_retaining ) then
            Close;
        end
        else
        if ( not ReadOnly ) and
           ( RefreshAction <> raOpen ) and
           ( ( not Assigned( dll_rollback_retaining )) or
           ( FDatasetFlags * [ dsfInsertWasPosted,
                               dsfEditWasPosted,
                                 dsfDeleteWasPosted,
                                 dsfWasInvalidated,
                                 dsfWasRefreshed ] <> [] )) then
        begin
          if BufferActive and ( not NeedToPost ) then
            Refresh;
          FDatasetFlags := FDatasetFlags - [ dsfInsertWasPosted,
                                             dsfEditWasPosted,
                                             dsfDeleteWasPosted,
                                             dsfWasInvalidated,
                                             dsfWasRefreshed ];
        end;
      end;
    except
      on E: Exception do Application.HandleException( E );
    end;
  IB_Session.ResetTimerNotification( Self );
end;

{  Event dispatch routines                                                     }

procedure TIB_Transaction.DoCustomizeTPB(     AConnection: TIB_Connection;
                                          var AIndex: longint;
                                          var ABuffer: pchar );
begin
  if Assigned( FOnCustomizeTPB ) then
    FOnCustomizeTPB( Self, AConnection, AIndex, ABuffer );
end;

procedure TIB_Transaction.DoBeforeStart;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FBeforeStart) then
      FBeforeStart( Self );
end;

procedure TIB_Transaction.DoAfterStart;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FAfterStart) then
      FAfterStart( Self );
end;

procedure TIB_Transaction.DoBeforeCommit;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FBeforeCommit) then
      FBeforeCommit( Self );
end;

procedure TIB_Transaction.DoAfterCommit;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FAfterCommit) then
      FAfterCommit( Self );
end;

procedure TIB_Transaction.DoBeforeCommitRetaining;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FBeforeCommitRetaining) then
      FBeforeCommitRetaining( Self );
end;

procedure TIB_Transaction.DoAfterCommitRetaining;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FAfterCommitRetaining) then
      FAfterCommitRetaining( Self );
end;

procedure TIB_Transaction.DoBeforeSavePoint;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FBeforeSavePoint) then
      FBeforeSavePoint( Self );
end;

procedure TIB_Transaction.DoAfterSavePoint;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FAfterSavePoint) then
      FAfterSavePoint( Self );
end;

procedure TIB_Transaction.DoBeforeLosePoint;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FBeforeLosePoint) then
      FBeforeLosePoint( Self );
end;

procedure TIB_Transaction.DoAfterLosePoint;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FAfterLosePoint) then
      FAfterLosePoint( Self );
end;

procedure TIB_Transaction.DoBeforeRollbackRetaining;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FBeforeRollbackRetaining) then
      FBeforeRollbackRetaining( Self );
end;

procedure TIB_Transaction.DoAfterRollbackRetaining;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FAfterRollbackRetaining) then
      FAfterRollbackRetaining( Self );
end;

procedure TIB_Transaction.DoBeforeRollback;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FBeforeRollback) then
      FBeforeRollback( Self );
end;

procedure TIB_Transaction.DoAfterRollback;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned(FAfterRollback) then
      FAfterRollback( Self );
end;

procedure TIB_Transaction.DoBeforeEnd;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( FBeforeEnd ) then
      FBeforeEnd( Self );
end;

procedure TIB_Transaction.DoAfterEnd;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( FAfterEnd ) then
      FAfterEnd( Self );
end;

{------------------------------------------------------------------------------}

function TIB_Transaction.GetTransactionLinkCount: integer;
begin
  if FTransactionLinkList <> nil then
    Result := FTransactionLinkList.Count
  else
    Result := 0;
end;

function TIB_Transaction.GetTransactionLink(Index:integer): TIB_TransactionLink;
begin
  if ( Index >= 0 ) and
     ( Index < TransactionLinkCount ) and
     ( FTransactionLinkList <> nil ) then
    Result := TIB_TransactionLink( FTransactionLinkList.Items[ Index ] )
  else
    Result := nil;
end;

{------------------------------------------------------------------------------}

procedure TIB_Transaction.SysAddConnection( NewConnection: TIB_Connection );
var
  i: integer;
  tmpInt: integer;
  tmpConnLink: TIB_ConnectionLink;
begin
  if ( NewConnection <> nil ) then
  begin
    tmpInt := -1;
    for i := 0 to ConnectionCount - 1 do
      if NewConnection = Connections[ i ] then
      begin
        tmpInt := i;
        Break;
      end;
    if ( tmpInt < 0 ) then
    begin
      tmpConnLink := TIB_ConnectionLink.Create( Self );
      with tmpConnLink do
      begin
        OnProcessEvent := ProcessConnectionEvent;
        IB_Connection := NewConnection;
      end;
      FConnectionLinkList.Add( tmpConnLink );
      NewConnection.FTransactionList.Add( Self );
    end;
  end;
end;

procedure TIB_Transaction.SysRemoveConnection( OldConnection: TIB_Connection );
var
  i: integer;
  tmpInt: integer;
  tmpConnLink: TIB_ConnectionLink;
begin
  if ( OldConnection <> nil ) then
  begin
    tmpInt := -1;
    for i := 0 to ConnectionCount - 1 do
      if OldConnection = Connections[ i ] then
      begin
        tmpInt := i;
        Break;
      end;
    if ( tmpInt >= 0 ) then
    begin
      tmpConnLink := TIB_ConnectionLink( FConnectionLinkList.Items[ tmpInt ] );
      tmpConnLink.IB_Connection := nil;
      tmpConnLink.OnProcessEvent := ProcessConnectionEvent;
      FConnectionLinkList.Remove( tmpConnLink );
      tmpConnLink.Free;
      OldConnection.FTransactionList.Remove( Self );
    end;
  end;
end;

procedure TIB_Transaction.SysRemoveAllConnections;
begin
  while ConnectionCount > 0 do
    SysRemoveConnection( Connections[ ConnectionCount - 1 ] );
end;

{------------------------------------------------------------------------------}

procedure TIB_Transaction.ProcessConnectionEvent( Sender: TIB_ConnectionLink;
                                                  AEvent: TIB_ConnectionEventType );
begin
  if AEvent = cetBeforeAssignment then
    Close;
end;

{------------------------------------------------------------------------------}

function TIB_Transaction.GetIB_Connection: TIB_Connection;
begin
  Result := Connections[ 0 ];
end;
function TIB_Transaction.GetIB_Connection1: TIB_Connection;
begin
  Result := Connections[ 1 ];
end;
function TIB_Transaction.GetIB_Connection2: TIB_Connection;
begin
  Result := Connections[ 2 ];
end;

procedure TIB_Transaction.SetIB_Connection( AValue: TIB_Connection );
begin
  if ( AValue = nil ) or
     (( AValue <> IB_Connection ) and
      ( AValue <> IB_Connection1 ) and
      ( AValue <> IB_Connection2 )) then
  begin
    SysRemoveConnection( IB_Connection );
    if Assigned( AValue ) then
      SysAddConnection( AValue );
  end;
end;

procedure TIB_Transaction.SetIB_Connection1( AValue: TIB_Connection );
begin
  if ( AValue = nil ) or
     (( AValue <> IB_Connection ) and
      ( AValue <> IB_Connection1 ) and
      ( AValue <> IB_Connection2 )) then
  begin
    SysRemoveConnection( IB_Connection1 );
    if Assigned( AValue ) then
      SysAddConnection( AValue );
  end;
end;

procedure TIB_Transaction.SetIB_Connection2( AValue: TIB_Connection );
begin
  if ( AValue = nil ) or
     (( AValue <> IB_Connection ) and
      ( AValue <> IB_Connection1 ) and
      ( AValue <> IB_Connection2 )) then
  begin
    SysRemoveConnection( IB_Connection2 );
    if Assigned( AValue ) then
      SysAddConnection( AValue );
  end;
end;

procedure TIB_Transaction.AddConnection( New: TIB_Connection );
begin
  SysAddConnection( New );
end;

procedure TIB_Transaction.RemoveConnection( Old: TIB_Connection );
begin
  SysRemoveConnection( Old );
end;

{------------------------------------------------------------------------------}

procedure TIB_Transaction.SetFocus;
begin
  IB_Session.FocusedTransaction := Self;
end;

procedure TIB_Transaction.DoGainFocus;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( OnGainFocus ) then
      OnGainFocus( Self );
end;

procedure TIB_Transaction.DoLoseFocus;
begin
  if not ( csDestroying in ComponentState ) then
    if Assigned( OnLoseFocus ) then
      OnLoseFocus( Self );
end;

procedure TIB_Transaction.SetAnnounceFocus( AValue: boolean );
begin
  if AnnounceFocus <> AValue then
  begin
    FAnnounceFocus := AValue;
    if AnnounceFocus then
      IB_Session.FocusedTransaction := Self;
  end;
end;

procedure TIB_Transaction.ProcessEvent( AEvent: TIB_TransactionEventType );
var
  ii: integer;
begin
  for ii := 0 to TransactionLinkCount - 1 do
    with TIB_TransactionLink( FTransactionLinkList.Items[ ii ] ) do
      ProcessEvent( AEvent );
end;

procedure TIB_Transaction.BeginPostPendingNotify;
begin
  Inc( FWantPostPendingChanged );
end;

procedure TIB_Transaction.EndPostPendingNotify;
begin
  Dec( FWantPostPendingChanged );
end;

procedure TIB_Transaction.BeginCachedUpdatePendingNotify;
begin
  Inc( FWantCachedUpdatePendingChanged );
end;

procedure TIB_Transaction.EndCachedUpdatePendingNotify;
begin
  Dec( FWantCachedUpdatePendingChanged );
end;

procedure TIB_Transaction.CheckOAT;
begin
  if ( Started ) and
     ( Isolation = tiCommitted ) and
     ( FOpenCursors = 0 ) and
     ( FPessimisticLockCount = 0 ) and
//   ( not InTransaction ) and // This is actually totally irrelevant.
     ( TransactionState in [ tsNone, tsInactive, tsActivePending ] ) and
     ( TimeActive > ( TimeoutProps.AllowCheckOAT / 88400 )) and
     ( not TimeoutProps.FDisableCheckOATFromError ) then
// I removed this because it was preventing cycling during a refresh.
// The session is always flagged as busy and yet it is only during a
// refresh that the transaction can be cycled.
// There are sufficient checks to make sure that this won't cause a problem
// of any kind.
//     ( IB_Session.BusyLevel = 0 ) then
  begin
    try
      API_Commit;
      ResetTransactionState( false );
      SysAfterEnd( true );
      UpdateStatus;
    except
      // Ignore an exception here.
    end;
  end;
end;

procedure TIB_Transaction.ProcessPassiveTasks( var IsDone,
                                                   IsWaiting,
                                                   Terminate: boolean );
var
  TranIsDone: boolean;
begin
  inherited ProcessPassiveTasks( IsDone, IsWaiting, Terminate );
  if Started then
  begin
    if TimeActive > ( TimeoutProps.AllowCheckOAT / 88400 ) then
    begin
      if IB_Session.BusyLevel = 0 then // Don't use IB_Session.IsBusy here.
      begin
        TranIsDone := true;
        CheckOAT;
        if Started and
           ( TimeoutProps.Attempt > 0 ) and
           ( TimeActive > ( TimeoutProps.FNextAttempt / 88400 )) then
        begin
          Inc( TimeoutProps.FNextAttempt, TimeoutProps.AttemptRetry );
          SysTimeOutAttempt( TranIsDone );
          CheckOAT;
        end;
        if Started and
           ( FTimeoutProps.PromptUser > 0 ) and
           ( TimeActive > ( FTimeoutProps.FNextPromptUser / 88400 )) then
        begin
          Inc( FTimeoutProps.FNextPromptUser, FTimeoutProps.PromptUserRetry );
          SysTimeOutPromptUser;
          CheckOAT;
        end;
        if Started and
           ( FTimeoutProps.ForceClosed > 0 ) and
           ( TimeActive > ( FTimeoutProps.ForceClosed / 88400 )) then
          Close;
        if IsDone then
          IsDone := TranIsDone;
      end;
    end;
    if not IsWaiting then
      IsWaiting := Started;
  end;
end;

procedure TIB_Transaction.SysTimeoutAttempt( var Done: boolean );
var
  ii: integer;
  CanAttemptCommit: boolean;
  WasInTransaction: boolean;
  tmpMult: dword;
  tmpTicks: dword;
begin
  if ( Started ) and
     ( Isolation = tiCommitted ) and
     ( FOpenCursors > 0 ) and
     ( FPessimisticLockCount = 0 ) and
     ( TransactionState in [ tsNone, tsInactive, tsActivePending ] ) then
  begin
    CanAttemptCommit := true;
    for ii := 0 to DatasetCount - 1 do
      with Datasets[ ii ] do
      begin
        if CursorIsOpen then
          if Unidirectional or Fetching {or ( BufferRowCount > 1000 )} then
          begin
            CanAttemptCommit := false;
            Break;
          end
          else
          if CommitAction = caClose then
          begin
            if BufferRowCount < TimeOutProps.AttemptMaxRows then
            begin
              CanAttemptCommit := false;
              SysFetchAll( TimeOutProps.AttemptTicks );
              CheckOAT;
              if Done and CursorIsOpen then
                Done := BufferRowCount >= TimeOutProps.AttemptMaxRows;
              Break;
            end;
            if RefreshAction = raOpen then
              CanAttemptCommit := false;
          end
          else
          if CommitAction = caFetchAll then
          begin
            CanAttemptCommit := false;
            SysFetchAll( TimeOutProps.AttemptTicks );
            CheckOAT;
            if Done then
              Done := not CursorIsOpen;
            Break;
          end;
        // We don't want the cursor position to get messed up in a way that
        // can possibly throw the user off. So, if a refresh will possibly
        // move the record pointer then don't allow the Commit which will
        // force a refresh somewhere and lose the current record pointer.
        if ( RefreshAction = raOpen ) then
          if (( CommitAction in [ caRefresh, caRefreshKeys ] )) or
             (( CommitAction = caInvalidateCursor ) and CursorIsOpen ) then
          begin
            CanAttemptCommit := false;
            Break;
          end;
      end;
    if Started and CanAttemptCommit then
    begin
      // Make one last effort to try to get cursors permanently freed up...
      // Allow up to 6 times the normal AttemptTicks duration to get a clean
      // shot at clearing things up.
      // Allow less time if the transaction is active since it won't actually
      // attempt to perform the commit. This will lessen the apparent background
      // activity to the user.
      // It is much better to fetch all records in than have a loop of records
      // continuously being fetched due to the the refresh taking place.
      // I need to just get the caInvalidateCursor property finished up the
      // way I intend for it to operate which won't require a fetchall or an
      // immediate refresh of the dataset (which will leave a new transaction
      // in an opened state just to cause another refresh, and on and on...
      tmpMult := dword(Abs(DatasetCount)) * 2;
      if TransactionIsActive and ( tmpMult > 4 ) then
        tmpMult := 4
      else if ( tmpMult > 6 ) then
        tmpMult := 6;
      tmpTicks := GetTickCount + TimeOutProps.AttemptTicks * tmpMult;
      for ii := 0 to DatasetCount - 1 do with Datasets[ ii ] do
      begin
        if FCursorIsOpen then
          if not ( CommitAction in [ caClose, caFetchAll ] ) then
            if BufferRowCount < TimeOutProps.AttemptMaxRows then
              SysFetchAll( TimeOutProps.AttemptTicks * 3 );
        if GetTickCount > tmpTicks then
          Break;
      end;
      CheckOAT;
      if not TransactionIsActive then
      begin
        if Started then
        begin
          WasInTransaction := InTransaction;
          try
            FOATPending := true;
            Commit;
          finally
            FOATPending := false;
            if WasInTransaction and ( not InTransaction ) then
              StartTransaction;
          end;
        end;
      end;
    end;
  end;
end;

procedure TIB_Transaction.SysTimeOutPromptUser;
begin
  if Assigned( FOnTimeoutPromptUser ) then
    FOnTimeoutPromptUser( Self )
  else
  if ( not IB_Session.IsBusy ) and TransactionIsActive then
  begin
    MessageBeep( 0 );
    EndWithConfirm;
  end;
end;

procedure TIB_Transaction.SetTimeoutProps( AValue: TIB_TimeoutProps);
begin
  with FTimeOutProps do
  begin
    FAllowCheckOAT      := AValue.FAllowCheckOAT;
    FAttempt            := AValue.FAttempt;
    FAttemptTicks       := AValue.FAttemptTicks;
    FAttemptRetry       := AValue.FAttemptRetry;
    FNextAttempt        := AValue.FNextAttempt;
    FAttemptMaxRows     := AValue.FAttemptMaxRows;
    FPromptUser         := AValue.FPromptUser;
    FPromptUserDuration := AValue.FPromptUserDuration;
    FPromptUserRetry    := AValue.FPromptUserRetry;
    FNextPromptUser     := AValue.FNextPromptUser;
    FForceClosed        := AValue.FForceClosed;        
  end;
end;

{------------------------------------------------------------------------------}

constructor TIB_TransactionDefault.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  inherited AutoCommit := true;
  inherited Isolation := tiCommitted;
  inherited AnnounceFocus := true;
end;

constructor TIB_TransactionInternal.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  inherited ReadOnly := true;
end;

//IBA_TransactionLink.INT

destructor TIB_TransactionLink.Destroy;
begin
  IB_Transaction := nil;
  inherited Destroy;
end;

procedure TIB_TransactionLink.Loaded;
begin
  inherited Loaded;
  if Assigned( FIB_Session ) then
    DoReceiveFocus( FIB_Session.FocusedTransaction );
end;

procedure TIB_TransactionLink.SetTransaction( AValue: TIB_Transaction );
begin
  if ( AValue <> IB_Transaction ) then
  begin
    FNewTransaction := AValue;
    ProcessEvent( tetBeforeAssignment );
    if Assigned( IB_Transaction ) then
      IB_Transaction.FTransactionLinkList.Remove( Self );
    FIB_Transaction := AValue;
    if Assigned( IB_Transaction ) then
      IB_Transaction.FTransactionLinkList.Add( Self );
    ProcessEvent( tetAfterAssignment );
  end;
end;

function TIB_TransactionLink.GetStarted: boolean;
begin
  if Assigned( IB_Transaction ) then
    Result := IB_Transaction.Started
  else
    Result := false;
end;

function TIB_TransactionLink.GetTransactionState: TIB_TransactionState;
begin
  if Assigned( IB_Transaction ) then
    Result := IB_Transaction.TransactionState
  else
    Result := tsNone;
end;

procedure TIB_TransactionLink.SetReceiveFocus( AValue: boolean );
begin
  if ReceiveFocus <> AValue then
  begin
    FReceiveFocus := AValue;
    DoReceiveFocus( IB_Session.FocusedTransaction );
  end;
end;

procedure TIB_TransactionLink.DoReceiveFocus( T: TIB_Transaction );
begin
  if ReceiveFocus and ( IB_Transaction <> T ) then
    if Assigned( OnReceiveFocus ) then
      OnReceiveFocus( Self, T )
    else
      IB_Transaction := T;
end;

procedure TIB_TransactionLink.ProcessEvent( AEvent: TIB_TransactionEventType );
begin
  if Assigned( OnProcessEvent ) then OnProcessEvent( Self, AEvent );
end;

// IBA_Database.INT

constructor TIB_Database.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  FIB_Transaction := CreateTransaction( Self );
  FIB_Transaction.IB_Connection := Self;
  FDefaultTransaction := FIB_Transaction;
end;
function TIB_DataBase.CreateTransaction(AOwner: TComponent): TIB_Transaction;
begin
  Result := TIB_TransactionDefault.Create(AOwner);
end;
procedure TIB_Database.ApplyUpdates( const ADatasets: array of TIB_BDataset );
begin FIB_Transaction.ApplyUpdates( ADatasets ); end;
procedure TIB_Database.CancelUpdates( const ADatasets: array of TIB_BDataset );
begin FIB_Transaction.CancelUpdates( ADatasets ); end;
procedure TIB_Database.StartTransaction;
begin FIB_Transaction.StartTransaction; end;
procedure TIB_Database.Commit;
begin FIB_Transaction.Commit; end;
procedure TIB_Database.Rollback;
begin FIB_Transaction.Rollback; end;
procedure TIB_Database.RollbackRetaining;
begin FIB_Transaction.RollbackRetaining; end;
procedure TIB_Database.Refresh( CommitChanges: boolean );
begin FIB_Transaction.Refresh( CommitChanges ); end;
procedure TIB_Database.CommitRetaining;
begin FIB_Transaction.CommitRetaining; end;
procedure TIB_Database.Savepoint;
begin FIB_Transaction.Savepoint; end;
procedure TIB_Database.Activate;
begin FIB_Transaction.Activate; end;
function TIB_Database.CloseWithConfirm: integer{TModalResult};
begin Result := FIB_Transaction.CloseWithConfirm; end;
function TIB_Database.EndWithConfirm: integer{TModalResult};
begin Result := FIB_Transaction.EndWithConfirm; end;
procedure TIB_Database.PostAll;
begin FIB_Transaction.PostAll; end;
procedure TIB_Database.CancelAll;
begin FIB_Transaction.CancelAll; end;
procedure TIB_Database.UpdateStatus;
begin FIB_Transaction.UpdateStatus; end;
function TIB_Database.GetInTransaction: boolean;
begin Result := FIB_Transaction.InTransaction; end;
function TIB_Database.GetTransactionIsActive: boolean;
begin Result := FIB_Transaction.TransactionIsActive; end;
function TIB_Database.GetPtrHandle: pisc_tr_handle;
begin Result := FIB_Transaction.PtrHandle; end;
function TIB_Database.GetTransactionState: TIB_TransactionState;
begin Result := FIB_Transaction.TransactionState; end;
function TIB_Database.GetPostPendingCount: integer;
begin Result := FIB_Transaction.PostPendingCount; end;
function TIB_Database.GetCachedUpdatePendingCount: integer;
begin Result := FIB_Transaction.CachedUpdatePendingCount; end;
function TIB_Database.GetStarted: boolean;
begin Result := FIB_Transaction.Started; end;
procedure TIB_Database.SetStarted( AValue: boolean );
begin FIB_Transaction.Started := AValue; end;
function TIB_Database.GetAutoCommit: boolean;
begin Result := FIB_Transaction.AutoCommit; end;
procedure TIB_Database.SetAutoCommit( AValue: boolean );
begin FIB_Transaction.AutoCommit := AValue; end;
procedure TIB_Database.SetName( const NewName: TComponentName );
begin
  inherited SetName( NewName );
  FIB_Transaction.Name := 'tr' + NewName;
end;
function TIB_Database.GetTransactionLinkCount: integer;
begin Result := FIB_Transaction.TransactionLinkCount; end;
function TIB_Database.GetBeforeStart: TIB_TransactionEvent;
begin Result := FIB_Transaction.BeforeStart; end;
procedure TIB_Database.SetBeforeStart( AValue: TIB_TransactionEvent );
begin FIB_Transaction.BeforeStart := AValue; end;
function TIB_Database.GetAfterStart: TIB_TransactionEvent;
begin Result := FIB_Transaction.AfterStart; end;
procedure TIB_Database.SetAfterStart( AValue: TIB_TransactionEvent );
begin FIB_Transaction.AfterStart := AValue; end;
function TIB_Database.GetBeforeCommit: TIB_TransactionEvent;
begin Result := FIB_Transaction.BeforeCommit; end;
procedure TIB_Database.SetBeforeCommit( AValue: TIB_TransactionEvent );
begin FIB_Transaction.BeforeCommit := AValue; end;
function TIB_Database.GetAfterCommit: TIB_TransactionEvent;
begin Result := FIB_Transaction.AfterCommit; end;
procedure TIB_Database.SetAfterCommit( AValue: TIB_TransactionEvent );
begin FIB_Transaction.AfterCommit := AValue; end;
function TIB_Database.GetBeforeCommitRetaining: TIB_TransactionEvent;
begin Result := FIB_Transaction.BeforeCommitRetaining; end;
procedure TIB_Database.SetBeforeCommitRetaining( AValue: TIB_TransactionEvent );
begin FIB_Transaction.BeforeCommitRetaining := AValue; end;
function TIB_Database.GetAfterCommitRetaining: TIB_TransactionEvent;
begin Result := FIB_Transaction.AfterCommitRetaining; end;
procedure TIB_Database.SetAfterCommitRetaining( AValue: TIB_TransactionEvent );
begin FIB_Transaction.AfterCommitRetaining := AValue; end;
function TIB_Database.GetBeforeRollbackRetaining: TIB_TransactionEvent;
begin Result := FIB_Transaction.BeforeRollbackRetaining; end;
procedure TIB_Database.SetBeforeRollbackRetaining(AValue: TIB_TransactionEvent);
begin FIB_Transaction.BeforeRollbackRetaining := AValue; end;
function TIB_Database.GetAfterRollbackRetaining: TIB_TransactionEvent;
begin Result := FIB_Transaction.AfterRollbackRetaining; end;
procedure TIB_Database.SetAfterRollbackRetaining( AValue: TIB_TransactionEvent);
begin FIB_Transaction.AfterRollbackRetaining := AValue; end;
function TIB_Database.GetBeforeRollback: TIB_TransactionEvent;
begin Result := FIB_Transaction.BeforeRollback; end;
procedure TIB_Database.SetBeforeRollback( AValue: TIB_TransactionEvent );
begin FIB_Transaction.BeforeRollback := AValue; end;
function TIB_Database.GetAfterRollback: TIB_TransactionEvent;
begin Result := FIB_Transaction.AfterRollback; end;
procedure TIB_Database.SetAfterRollback( AValue: TIB_TransactionEvent );
begin FIB_Transaction.AfterRollback := AValue; end;
function TIB_Database.GetBeforeEnd: TIB_TransactionEvent;
begin Result := FIB_Transaction.BeforeEnd; end;
procedure TIB_Database.SetBeforeEnd( AValue: TIB_TransactionEvent );
begin FIB_Transaction.BeforeEnd := AValue; end;
function TIB_Database.GetAfterEnd: TIB_TransactionEvent;
begin Result := FIB_Transaction.AfterEnd; end;
procedure TIB_Database.SetAfterEnd( AValue: TIB_TransactionEvent );
begin FIB_Transaction.AfterEnd := AValue; end;
function TIB_Database.GetOnSessionTimer: TIB_SessionTimerEvent;
begin Result := FIB_Transaction.OnSessionTimer; end;
procedure TIB_Database.SetOnSessionTimer( AValue: TIB_SessionTimerEvent );
begin FIB_Transaction.OnSessionTimer := AValue; end;
procedure TIB_Database.SetAnnounceFocus( AValue: boolean );
begin
  FIB_Transaction.AnnounceFocus := AValue;
  inherited SetAnnounceFocus( AValue );
end;
procedure TIB_Database.SetFocus;
begin
  FIB_Transaction.SetFocus;
  inherited SetFocus;
end;
function TIB_Database.GetReadOnly: boolean;
begin Result := FIB_Transaction.ReadOnly; end;
procedure TIB_Database.SetReadOnly( AValue: boolean );
begin FIB_Transaction.ReadOnly := AValue; end;
function TIB_Database.GetIsolation: TIB_Isolation;
begin Result := FIB_Transaction.Isolation; end;
procedure TIB_Database.SetIsolation( AValue: TIB_Isolation );
begin FIB_Transaction.Isolation := AValue; end;
function TIB_Database.GetRecVersion: boolean;
begin Result := FIB_Transaction.RecVersion; end;
procedure TIB_Database.SetRecVersion( AValue: boolean );
begin FIB_Transaction.RecVersion := AValue; end;
function TIB_Database.GetLockWait: boolean;
begin Result := FIB_Transaction.LockWait; end;
procedure TIB_Database.SetLockWait( AValue: boolean );
begin FIB_Transaction.LockWait := AValue; end;
function TIB_Database.GetTimeOutProps: TIB_TimeoutProps;
begin Result := FIB_Transaction.TimeOutProps; end;
procedure TIB_Database.SetTimeOutProps( AValue: TIB_TimeoutProps );
begin FIB_Transaction.SetTimeOutProps( AValue ); end;
procedure TIB_Database.SetDefaultTransaction( AValue: TIB_Transaction );
begin
  if ( AValue <> nil ) and ( AValue <> FDefaultTransaction ) then
    raise EIB_ConnectionError.CreateWithSender( Self, E_Cant_Repl_Int_Tr );
end;

procedure TIB_Database.GetTransIsolation( AReader: TReader );
var
  tmpStr: string;
begin
  tmpStr := AReader.ReadIdent;
  if tmpStr = 'tiDirtyRead' then
  begin
    Isolation := tiCommitted;
    RecVersion := false;
  end
  else
  if tmpStr = 'tiReadCommitted' then
  begin
    Isolation := tiCommitted;
    RecVersion := true;
  end
  else
  if tmpStr = 'tiRepeatableRead' then
    Isolation := tiConcurrency;
end;

procedure TIB_Database.DefineProperties( AFiler: TFiler );
begin
  inherited DefineProperties( AFiler );
  AFiler.DefineProperty( 'TransIsolation', GetTransIsolation, nil, false );
end;

function TIB_DataBase.GetDriverName: string;
begin
  Result := 'INTRBASE';
end;

procedure TIB_Database.ExecSQL( const ASQL: string );
begin
  if Assigned( DefaultTransaction ) then
    DefaultTransaction.ExecuteImmediate( ASQL )
  else
    raise EIB_ConnectionError.Create( E_Invalid_Internal_Transaction );
end;

// IBA_Statement.INT

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Jason Wharton <jwharton@ibobjects.com>                                      }
{  09/09/2001                                                                  }
{     I made it so that the OnMacroSubstitute event wouldn't get wiped out     }
{     the IB_Script.SQL.Assign() method is used in the property editor.        }
{                                                                              }
{  Jason Wharton <jwharton@ibobjects.com>                                      }
{  08/08/2001                                                                  }
{     I improved the integration between the internal dataset and the TDataset }
{     component so that it would respect the FieldsIndex setting. This is due  }
{     to the AssignSQLWithSearch() method will inject various settings into    }
{     the TDataset based component.                                            }
{                                                                              }
{******************************************************************************}

constructor TIB_Statement.CreateWithBinding( AOwner: TComponent;
                                             ADataset: TIB_Dataset );
begin
  FBindingCursor := ADataset;
  Create( AOwner );
end;

constructor TIB_Statement.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);
  FIB_StatementLinkList := TList.Create;
  FSQL := TIB_SQLStrings.Create;
  (FSQL as TIB_SQLStrings).FStatement := Self;
  (FSQL as TIB_SQLStrings).OnChange := OnSQLChange;
  FJoinLinks := TIB_StringProperty.Create;
  FRelAliasList := TIB_StringProperty.Create;
  (FJoinLinks as TIB_StringList).OnChange := OnSQLChange;
  FParamValueLinks := TIB_StringProperty.Create;
  FSysParamNames := TIB_StringList.Create;
  FSysFieldNames := TIB_StringList.Create;
  FSysTableNames := TIB_StringList.Create;
  FOldParamValueLinks := TIB_StringProperty.Create;
  FSQLHistory := TIB_StringList.Create;
  FParams := TIB_Row.Create( Self, rtParam  );
  FParams.OnRowStateChanged := SysParamsStateChanged;
  FParams.BeforeModify := SysBeforeParamsDataChange;
  FParams.AfterModify := SysAfterParamsDataChange;
  FCursorFields := TIB_Row.Create( Self, rtField );
  FCursorFields.OnRowStateChanged := SysCursorFieldsStateChanged;
  FCursorFields.BeforeModify := SysBeforeCursorFieldDataChange;
  FCursorFields.AfterModify := SysAfterCursorFieldDataChange;
  FCursorKeyFields := TIB_Row.Create( Self, rtKey );
  FCursorKeyFields.FIsKeyFields := true;
  FKeepSQLHistory := false;
  FSQLHistoryRef := 0;
  FstHandle := nil;
  FPrepared := false;
  FCursorFieldCount := 0;
  FParamCount := 0;
  FParamChar := ':';
  FParamCheck := true;
  FSQLDialect := 3;
  FHints := TIB_StringProperty.Create;
  (FHints as TIB_StringList).OnChange := HintsChange;
  FColumnAttributes := TIB_StringProperty.Create;
  FCalculatedFields := TIB_StringProperty.Create;
  FCalculatedFields.OnChange := OnSQLChange;
  FFieldsAlignment := TIB_StringProperty.Create;
  FFieldsCharCase := TIB_StringProperty.Create;
  FFieldsDisplayFormat := TIB_StringProperty.Create;
  FFieldsDisplayLabel := TIB_StringProperty.Create;
  FFieldsGridLabel := TIB_StringProperty.Create;
  FFieldsGridTitleHint := TIB_StringProperty.Create;
  FFieldsDisplayWidth := TIB_StringProperty.Create;
  FFieldsEditMask := TIB_StringProperty.Create;
  FFieldsIndex := TIB_StringProperty.Create;
  FFieldsReadOnly := TIB_StringProperty.Create;
  FFieldsTrimming := TIB_StringProperty.Create;
  FFieldsVisible := TIB_StringProperty.Create;
  (FFieldsAlignment as TIB_StringList).OnChange := LayoutChange;
  (FFieldsCharCase as TIB_StringList).OnChange := LayoutChange;
  (FFieldsDisplayFormat as TIB_StringList).OnChange := LayoutChange;
  (FFieldsDisplayLabel as TIB_StringList).OnChange := LayoutChange;
  (FFieldsGridLabel as TIB_StringList).OnChange := LayoutChange;
  (FFieldsGridTitleHint as TIB_StringList).OnChange := LayoutChange;
  (FFieldsDisplayWidth as TIB_StringList).OnChange := LayoutChange;
  (FFieldsEditMask as TIB_StringList).OnChange := LayoutChange;
  (FFieldsIndex as TIB_StringList).OnChange := LayoutChange;
  (FFieldsReadOnly as TIB_StringList).OnChange := LayoutChange;
  (FFieldsTrimming as TIB_StringList).OnChange := LayoutChange;
  (FFieldsVisible as TIB_StringList).OnChange := LayoutChange;
  FIB_ConnectionLink := TIB_ConnectionLink.Create( Self );
  FIB_ConnectionLink.OnProcessEvent := ProcessConnectionEvent;
  FIB_TransactionLink := TIB_TransactionLink.Create( Self );
  FIB_TransactionLink.OnProcessEvent := ProcessTransactionEvent;
  FStoredProcHasDML := true;
  flag_statement_type_invalid := true;
  flag_rows_affected_invalid  := true;
  flag_statement_plan_invalid := true;
  flag_rel_alias_invalid := true;
  FDefineCursorKeyFields := true;
  FRetrieveDomainNames := true;
  FMacroBegin := '';
  FMacroEnd := '';
  FCombineDuplicateParams := true;
  FAlwaysCallMacroSubstitute := false;
  FSetParamAttribs := false;
  FCalculateAllFields := false;
end;

destructor TIB_Statement.Destroy;
begin
  try
    SysDeallocate( true );
  except
    FPrepared := false;
    if Assigned( IB_Connection ) and Assigned( FStHandle ) then
      IB_Connection.DeallocateStmtHandle( PstHandle );
  end;
  IB_Transaction := nil;
  IB_Connection := nil;
  while StatementLinkCount > 0 do
    TIB_StatementLink( FIB_StatementLinkList.Items[ 0 ] ).Statement := nil;
  (FSQL as TIB_SQLStrings).OnChange := nil;
  FIB_ConnectionLink.OnProcessEvent := nil;
  FIB_TransactionLink.OnProcessEvent := nil;
  with FParams do
  begin
    OnRowStateChanged := nil;
    BeforeModify := nil;
    AfterModify := nil;
  end;
  with FCursorFields do
  begin
    OnRowStateChanged := nil;
    BeforeModify := nil;
    AfterModify := nil;
  end;
  inherited Destroy; 
  FSQL.Free;
  FSQL := nil;
  FParams.Free;
  FParams := nil;
  FCursorFields.Free;
  FCursorFields := nil;
  FCursorKeyFields.Free;
  FCursorKeyFields := nil;
  FParamValueLinks.Free;
  FParamValueLinks := nil;
  FSysParamNames.Free;
  FSysParamNames := nil;
  FSysFieldNames.Free;
  FSysFieldNames := nil;
  FSysTableNames.Free;
  FSysTableNames := nil;
  FOldParamValueLinks.Free;
  FOldParamValueLinks := nil;
  FJoinLinks.Free;
  FJoinLinks := nil;
  FRelAliasList.Free;
  FRelAliasList := nil;
  FSQLHistory.Free;
  FSQLHistory := nil;
  FIB_StatementLinkList.Free;
  FIB_StatementLinkList := nil;
  FHints.Free;
  FHints := nil;
  FColumnAttributes.Free;
  FColumnAttributes := nil;
  FCalculatedFields.Free;
  FCalculatedFields := nil;
  FFieldsAlignment.Free;
  FFieldsAlignment := nil;
  FFieldsCharCase.Free;
  FFieldsCharCase := nil;
  FFieldsDisplayFormat.Free;
  FFieldsDisplayFormat := nil;
  FFieldsDisplayLabel.Free;
  FFieldsDisplayLabel := nil;
  FFieldsGridLabel.Free;
  FFieldsGridLabel := nil;
  FFieldsGridTitleHint.Free;
  FFieldsGridTitleHint := nil;  
  FFieldsDisplayWidth.Free;
  FFieldsDisplayWidth := nil;
  FFieldsEditMask.Free;
  FFieldsEditMask := nil;
  FFieldsIndex.Free;
  FFieldsIndex := nil;
  FFieldsReadOnly.Free;
  FFieldsReadOnly := nil;
  FFieldsTrimming.Free;
  FFieldsTrimming := nil;
  FFieldsVisible.Free;
  FFieldsVisible := nil;
end;

procedure TIB_Statement.Notification( AComponent: TComponent;
                                      Operation: TOperation);
begin
  if ( Operation = opRemove ) and ( AComponent = FIB_Transaction ) then
    FIB_Transaction := nil;
  inherited Notification( AComponent, Operation );
end;

procedure TIB_Statement.Loaded;
begin
  inherited Loaded;
  if not ( csFixups in ComponentState ) then
    SysPrepareAfterLoad;
end;

procedure TIB_Statement.SysPrepareAfterLoad;
begin
  try
    if flag_prepare_after_load then
    begin
      SysPrepare;
      if Prepared then
        flag_prepare_after_load := false;
    end;
  except
    // Throw away the exception.
  end;
end;

procedure TIB_Statement.DoHandleError(       Sender: TObject;
                                       const errcode: isc_long;
                                             ErrorMessage,
                                             ErrorCodes: TStringList;
                                       const SQLCODE: isc_long;
                                             SQLMessage,
                                             SQL: TStringList;
                                         var RaiseException: boolean);
  function GetNameInfo( AComponent: TComponent ): string;
  var
    tmpStr: string;
  begin
    Result := '';
    if Assigned( AComponent ) then
    begin
      if ( AComponent.Name <> '' ) then
        Result := Result + AComponent.Name + '.'
      else
        Result := Result + '<' + AComponent.ClassName + '>.';
      tmpStr := GetNameInfo( AComponent.Owner );
      if tmpStr = '' then
      else
        Result := tmpStr + Result;
    end;
  end;
var
  tmpStr: string;
begin
  if PreparingSQL then
  begin
    SQL.Add( '' );
    SQL.Add( RefinedSQL );
    SQL.Add( '' );
  end;
  tmpStr := Self.ClassName + ': ' + mkLitCriteria( GetNameInfo( Self ), '"' );
  if IB_Session.ClientMonitorHooksIn then
  begin
    tmpStr := tmpStr + ' stHandle=' + IntToStr( Integer( FstHandle )) +
                       ' (ERROR)';
  end;
  ErrorMessage.Add( '' );
  ErrorMessage.Add( 'STATEMENT:' );
  ErrorMessage.Add( tmpStr );
  ErrorMessage.Add( '' );
  inherited DoHandleError( Self,
                           errcode,
                           ErrorMessage,
                           ErrorCodes,
                           SQLCODE,
                           SQLMessage,
                           SQL,
                           RaiseException );
  if RaiseException and Assigned( IB_Transaction ) then
    IB_Transaction.DoHandleError( Self,
                                  errcode,
                                  ErrorMessage,
                                  ErrorCodes,
                                  SQLCODE,
                                  SQLMessage,
                                  SQL,
                                  RaiseException );
  if RaiseException and Assigned( IB_Connection ) then
    IB_Connection.DoHandleError( Self,
                                 errcode,
                                 ErrorMessage,
                                 ErrorCodes,
                                 SQLCODE,
                                 SQLMessage,
                                 SQL,
                                 RaiseException );
end;

{------------------------------------------------------------------------------}

function TIB_Statement.IsPreparedStored: boolean;
begin
  Result := IB_Session.StoreActive and Prepared and ( not Active );
end;

function TIB_Statement.IsActiveStored: boolean;
begin
  Result := IB_Session.StoreActive and Active;
end;

function TIB_Statement.GetDatabaseName: string;
begin
  if Assigned( IB_Connection ) then
    FDatabaseName := IB_Connection.DatabaseName;
  Result := FDatabaseName;
end;

procedure TIB_Statement.SetDatabaseName( const AValue: string );
begin
  FDatabaseName := AValue;
  if Assigned( FIB_Session ) then
    IB_Connection := IB_Session.GetConnectionByName( AValue );
end;

procedure TIB_Statement.SetColumnAttributes( Value: TIB_StringList );
begin FColumnAttributes.Assign(Value); end;
procedure TIB_Statement.SetCalculatedFields( Value: TIB_StringList );
begin FCalculatedFields.Assign(Value); end;
procedure TIB_Statement.SetFieldsAlignment( Value: TIB_StringList );
begin FFieldsAlignment.Assign(Value); end;
procedure TIB_Statement.SetFieldsCharCase( Value: TIB_StringList );
begin FFieldsCharCase.Assign(Value); end;
procedure TIB_Statement.SetFieldsDisplayFormat( Value: TIB_StringList );
begin FFieldsDisplayFormat.Assign(Value); end;
procedure TIB_Statement.SetFieldsDisplayLabel( Value: TIB_StringList );
begin FFieldsDisplayLabel.Assign(Value); end;
procedure TIB_Statement.SetFieldsGridLabel( Value: TIB_StringList );
begin FFieldsGridLabel.Assign(Value); end;
procedure TIB_Statement.SetFieldsGridTitleHint( Value: TIB_StringList );
begin FFieldsGridTitleHint.Assign(Value); end;
procedure TIB_Statement.SetFieldsDisplayWidth( Value: TIB_StringList );
begin FFieldsDisplayWidth.Assign(Value); end;
procedure TIB_Statement.SetFieldsEditMask( Value: TIB_StringList );
begin FFieldsEditMask.Assign(Value); end;
procedure TIB_Statement.SetFieldsIndex( Value: TIB_StringList );
begin FFieldsIndex.Assign(Value); end;
procedure TIB_Statement.SetFieldsReadOnly( Value: TIB_StringList );
begin FFieldsReadOnly.Assign(Value); end;
procedure TIB_Statement.SetFieldsTrimming( Value: TIB_StringList );
begin FFieldsTrimming.Assign(Value); end;
procedure TIB_Statement.SetFieldsVisible( Value: TIB_StringList );
begin FFieldsVisible.Assign(Value); end;

procedure TIB_Statement.SetKeyRelation( const AValue: string );
begin
  if KeyRelation <> AValue then
  begin
    SysUnprepare;
    FKeyRelation := AValue;
  end;
end;

procedure TIB_Statement.SetHints( Value: TStrings );
begin
  FHints.Assign(Value);
end;

procedure TIB_Statement.GetColumnIsReadOnly(     AColumn: TIB_Column;
                                             var AReadOnly: boolean );
var
  tmpR,
  tmpS,
  tmpA: string;
begin
  if ( not AReadOnly ) and AColumn.IsCalculated then
    AReadOnly := not AColumn.IsAttributeSet[ IBO_MODIFIABLE ]
  else {Keep this "else" here.}
  if ( not AReadOnly ) and ( not AColumn.Computed ) then
  begin
    tmpS := SysKeyRelation;
    if tmpS = '' then
    begin
    { should be false if live unidirectional curosr }
      if ( Self is TIB_Cursor ) and
         ( not (Self as TIB_Cursor).RequestLive ) then  
      AReadOnly := true
    end
    else
    begin
      tmpR := AColumn.RelName;
      tmpA := AColumn.RelAliasName;
      if Pos( '"', tmpS ) = 0 then tmpS := '"' + tmpS + '"';
      if Pos( '"', tmpR ) = 0 then tmpR := '"' + tmpR + '"';
      if Pos( '"', tmpS ) = 0 then tmpS := '"' + tmpS + '"';
      if Pos( '"', tmpA ) = 0 then tmpA := '"' + tmpA + '"';
      AReadOnly := ( tmpR <> tmpS ) and ( tmpA <> tmpS ) ;
    end;
  end;
end;

procedure TIB_Statement.GetControlIsReadOnly(     AColumn: TIB_Column;
                                              var AReadOnly: boolean );
begin
// Abstract.
end;

procedure TIB_Statement.BeginLayout;
begin
  Inc( FIgnoreLayoutChange );
end;

procedure TIB_Statement.EndLayout;
begin
  Dec( FIgnoreLayoutChange );
  if FIgnoreLayoutChange = 0 then
    UpdateLayout;
end;

procedure TIB_Statement.UpdateLayout;
begin
  SysUpdateLayout( false );
end;

procedure TIB_Statement.UpdateRecord;
begin
  ProcessLinkEvent( setFieldsUpdateData, 0 );
end;

procedure TIB_Statement.SysUpdateLayout( FirstTime: boolean );
var
  ii: integer;
  tmpCol: TIB_Column;
begin
  Inc( FIgnoreLayoutChange );
  if Assigned( FBindingCursor ) then
    Inc( FBindingCursor.FIgnoreLayoutChange );
  try
    if Fields.ColumnCount > 0 then
    begin
      if ( not FirstTime ) or ( FieldsIndex.Count > 0 ) then
        UpdateIndex;
      if ( not FirstTime ) or
         ( ColumnAttributes.Count > 0 ) or
         ( FieldsAlignment.Count > 0 ) or
         ( FieldsCharCase.Count > 0 ) or
         ( FieldsDisplayFormat.Count > 0 ) or
         ( FieldsDisplayLabel.Count > 0 ) or
         ( FieldsGridLabel.Count > 0 ) or
         ( FieldsGridTitleHint.Count > 0 ) or
         ( FieldsDisplayWidth.Count > 0 ) or
         ( FieldsEditMask.Count > 0 ) or
         ( FieldsReadOnly.Count > 0 ) or
         ( FieldsTrimming.Count > 0 ) or
         ( FieldsVisible.Count > 0 ) or
         ( IB_Connection.ColumnAttributes.Count > 0 ) or
         ( IB_Connection.FieldsAlignment.Count > 0 ) or
         ( IB_Connection.FieldsCharCase.Count > 0 ) or
         ( IB_Connection.FieldsDisplayFormat.Count > 0 ) or
         ( IB_Connection.FieldsDisplayLabel.Count > 0 ) or
         ( IB_Connection.FieldsGridLabel.Count > 0 ) or
         ( IB_Connection.FieldsGridTitleHint.Count > 0 ) or
         ( IB_Connection.FieldsDisplayWidth.Count > 0 ) or
         ( IB_Connection.FieldsEditMask.Count > 0 ) or
         ( IB_Connection.FieldsReadOnly.Count > 0 ) or
         ( IB_Connection.FieldsTrimming.Count > 0 ) or
         ( IB_Connection.FieldsVisible.Count > 0 ) then
        for ii := 0 to Fields.ColumnCount - 1 do
        begin
          tmpCol := Fields.Columns[ii];
          SysUpdateAlignment( tmpCol );
          SysUpdateCharCase( tmpCol );
          SysUpdateDisplayFormat( tmpCol );
          SysUpdateDisplayLabel( tmpCol );
          SysUpdateGridLabel( tmpCol );
          SysUpdateGridTitleHint( tmpCol );
          SysUpdateDisplayWidth( tmpCol );
          SysUpdateEditMask( tmpCol );
          SysUpdateReadOnly( tmpCol );
          SysUpdateTrimming( tmpCol );
          SysUpdateVisible( tmpCol );
          tmpCol.SysLayoutChanged;
        end;
    end;
    if SetParamAttribs and (Params.ColumnCount > 0) then
    begin
      if ( not FirstTime ) or
         ( ColumnAttributes.Count > 0 ) or
         ( FieldsAlignment.Count > 0 ) or
         ( FieldsCharCase.Count > 0 ) or
         ( FieldsDisplayFormat.Count > 0 ) or
         ( FieldsDisplayLabel.Count > 0 ) or
         ( FieldsGridLabel.Count > 0 ) or
         ( FieldsGridTitleHint.Count > 0 ) or
         ( FieldsDisplayWidth.Count > 0 ) or
         ( FieldsEditMask.Count > 0 ) or
         ( FieldsReadOnly.Count > 0 ) or
         ( FieldsTrimming.Count > 0 ) or
         ( FieldsVisible.Count > 0 ) or
         ( IB_Connection.ColumnAttributes.Count > 0 ) or
         ( IB_Connection.FieldsAlignment.Count > 0 ) or
         ( IB_Connection.FieldsCharCase.Count > 0 ) or
         ( IB_Connection.FieldsDisplayFormat.Count > 0 ) or
         ( IB_Connection.FieldsDisplayLabel.Count > 0 ) or
         ( IB_Connection.FieldsGridLabel.Count > 0 ) or
         ( IB_Connection.FieldsGridTitleHint.Count > 0 ) or
         ( IB_Connection.FieldsDisplayWidth.Count > 0 ) or
         ( IB_Connection.FieldsEditMask.Count > 0 ) or
         ( IB_Connection.FieldsReadOnly.Count > 0 ) or
         ( IB_Connection.FieldsTrimming.Count > 0 ) or
         ( IB_Connection.FieldsVisible.Count > 0 ) then
        for ii := 0 to Params.ColumnCount - 1 do
        begin
          tmpCol := Params.Columns[ii];
          SysUpdateAlignment( tmpCol );
          SysUpdateCharCase( tmpCol );
          SysUpdateDisplayFormat( tmpCol );
          SysUpdateDisplayLabel( tmpCol );
          SysUpdateGridLabel( tmpCol );
          SysUpdateGridTitleHint( tmpCol );
          SysUpdateDisplayWidth( tmpCol );
          SysUpdateEditMask( tmpCol );
          SysUpdateReadOnly( tmpCol );
          SysUpdateTrimming( tmpCol );
          SysUpdateVisible( tmpCol );
          tmpCol.SysLayoutChanged;
        end;
    end;
  finally
    if Assigned( FBindingCursor ) then
      Dec( FBindingCursor.FIgnoreLayoutChange );
    Dec( FIgnoreLayoutChange );
  end;
  if FIgnoreLayoutChange = 0 then
    SysLayoutChanged;
end;

function GetColAttrTxt( const ACol: TIB_Column;
                        const AColStrings,
                              AConStrings: TIB_StringList ): string;
var
  tmpStr: string;
  tmpPos: integer;
begin
  with ACol do
  begin
    Result := AColStrings.LinkValues[ FullFieldName ];
    if Length( Result ) = 0 then
      Result := AColStrings.LinkValues[ FullSQLName ];
    if ( Length( Result ) = 0 ) and ( AConStrings.Count > 0 ) then
    begin
      Result := AConStrings.LinkValues[ FullFieldName ];
      if ( Length( Result ) = 0 ) then
        Result := AConStrings.LinkValues[ FullSQLName ];
      if ( Length( Result ) = 0 ) and
         ( fetDomainName in Statement.IB_Connection.FieldEntryTypes ) then
      begin
        Result := AConStrings.LinkValues[ DomainName ];
      end;
      if ( Length( Result ) = 0 ) and
         ( fetSQLType in Statement.IB_Connection.FieldEntryTypes ) then
      begin
        tmpStr := SQLTypeSource[ true ];
        tmpPos := getLitSafeStrPos( '(', tmpStr, 1 );
        if tmpPos > 0 then
          tmpStr := Trim( Copy( tmpStr, 1, tmpPos - 1 ));
        Result := AConStrings.LinkValues[ tmpStr ];
      end;
    end;
  end;
end;

function GetColAttrChar( const ACol: TIB_Column;
                         const AColStrings,
                               AConStrings: TIB_StringList ): char;
var
  tmpStr: string;
begin
  tmpStr := GetColAttrTxt( ACol, AColStrings, AConStrings );
  tmpStr := UpperCase( Copy( Trim( tmpStr ), 1, 1 ));
  if Length( tmpStr ) = 0 then
    Result := #0
  else
    Result := tmpStr[1];
end;

function GetColParamValueEx( const ACol: TIB_Column;
                             const AParam: string;
                               var IsSet: boolean;
                             const AColStrings,
                                   AConStrings: TIB_StringList ): string;
  procedure GetPrmVal( const AStrings: TIB_StringList;
                       const AFieldName: string );
  var
    tmpStr: string;
    BegPos,
    EndPos: longint;
  begin
    tmpStr := AStrings.LinkValues[ AFieldName ];
    if tmpStr <> '' then
    begin
      ExtractListLinkParam( tmpStr, AParam, BegPos, EndPos, Result );
      IsSet := BegPos <> ParseLineInvalid;
    end;
  end;
var
  tmpStr: string;
  tmpPos: integer;
begin
  IsSet := false;
  GetPrmVal( AColStrings, ACol.FullFieldName );
  if ( not IsSet ) and ( ACol.FullFieldName <> ACol.FullSQLName ) then
    GetPrmVal( AColStrings, ACol.FullSQLName );
  if ( not IsSet ) and Assigned( AConStrings ) then
  begin
    GetPrmVal( AConStrings, ACol.FullFieldName );
    if ( not IsSet ) and ( ACol.FullFieldName <> ACol.FullSQLName ) then
      GetPrmVal( AConStrings, ACol.FullSQLName );
  end;
  if ( not IsSet ) and Assigned( ACol.Row.Statement.IB_Connection ) then
  begin
    if ( fetDomainName in ACol.Row.Statement.IB_Connection.FieldEntryTypes )then
    begin
      GetPrmVal( AColStrings, ACol.DomainName );
      if ( not IsSet ) then
        GetPrmVal( AConStrings, ACol.DomainName );
    end;
    if ( not IsSet ) then
    begin
      if ( fetSQLType in ACol.Row.Statement.IB_Connection.FieldEntryTypes ) then
      begin
        tmpStr := ACol.SQLTypeSource[ true ];
        tmpPos := getLitSafeStrPos( '(', tmpStr, 1 );
        if tmpPos > 0 then
          tmpStr := Trim( Copy( tmpStr, 1, tmpPos - 1 ));
        GetPrmVal( AColStrings, tmpStr );
        if ( not IsSet ) then
          GetPrmVal( AConStrings, tmpStr );
      end;
    end;
  end;
end;

procedure TIB_Statement.UpdateAlignment;
var
  ii: integer;
begin
  for ii := 0 to Fields.ColumnCount - 1 do
    SysUpdateAlignment( Fields.Columns[ ii] );
end;

procedure TIB_Statement.SysUpdateAlignment( AField: TIB_Column );
begin
  with AField do
  begin
    case GetColAttrChar( AField,
                         FieldsAlignment,
                         IB_Connection.FieldsAlignment ) of
      'L': FAlignment := taLeftJustify;
      'R': FAlignment := taRightJustify;
      'C': FAlignment := taCenter;
      else
        if IsBoolean then
          FAlignment := taCenter
        else
        if AField.IsNumeric then
          FAlignment := taRightJustify
        else
          FAlignment := taLeftJustify;
    end;
  end;
end;

procedure TIB_Statement.UpdateCharCase;
var
  ii: integer;
begin
  for ii := 0 to Fields.ColumnCount - 1 do
    SysUpdateCharCase( Fields.Columns[ii] );
end;

procedure TIB_Statement.SysUpdateCharCase( Afield: TIB_Column );
begin
  with AField do
  begin
    case GetColAttrChar( AField,
                         FieldsCharCase,
                         IB_Connection.FieldsCharCase ) of
      'P': FCharCase := ccProper;
      'U': FCharCase := ccUpper;
      'L': FCharCase := ccLower;
      else FCharCase := ccNormal;
    end;
  end;
end;

procedure TIB_Statement.UpdateDisplayFormat;
var
  ii: integer;
begin
  with Fields do
    for ii := 0 to ColumnCount - 1 do
      SysUpdateDisplayFormat( Columns[ii] );
  if SetParamAttribs then
    with Params do
      for ii := 0 to ColumnCount - 1 do
        SysUpdateDisplayFormat( Columns[ii] );
end;

procedure TIB_Statement.SysUpdateDisplayFormat( AField: TIB_Column );
begin
  AField.FDisplayFormat := GetColAttrTxt( AField,
                           FieldsDisplayFormat,
                           IB_Connection.FieldsDisplayFormat );
end;

procedure TIB_Statement.UpdateDisplayLabel;
var
  ii: integer;
begin
  for ii := 0 to Fields.ColumnCount - 1 do
    SysUpdateDisplayLabel( Fields[ii] );
  if SetParamAttribs then
    for ii := 0 to Params.ColumnCount - 1 do
      SysUpdateDisplayLabel( Params[ii] );
end;

procedure TIB_Statement.SysUpdateDisplayLabel( AField: TIB_Column );
begin
  AField.FDisplayLabel := GetColAttrTxt( AField,
                                         FieldsDisplayLabel,
                                         IB_Connection.FieldsDisplayLabel );
end;

procedure TIB_Statement.UpdateGridLabel;
var
  ii: integer;
begin
  with Fields do
    for ii := 0 to ColumnCount - 1 do
      SysUpdateGridLabel( Columns[ii] );
  if SetParamAttribs then
    with Params do
      for ii := 0 to ColumnCount - 1 do
        SysUpdateGridLabel( Columns[ii] );
end;

procedure TIB_Statement.SysUpdateGridLabel( AField: TIB_Column );
begin
  AField.FGridDisplayLabel := GetColAttrTxt( AField,
                                             FieldsGridLabel,
                                             IB_Connection.FieldsGridLabel );
end;

procedure TIB_Statement.UpdateGridTitleHint;
var
  ii: integer;
begin
  with Fields do
    for ii := 0 to ColumnCount - 1 do
      SysUpdateGridTitleHint( Columns[ii] );
  if SetParamAttribs then
    with Params do
      for ii := 0 to ColumnCount - 1 do
        SysUpdateGridTitleHint( Columns[ii] );
end;

procedure TIB_Statement.SysUpdateGridTitleHint( AField: TIB_Column );
begin
  AField.FGridTitleHint := GetColAttrTxt( AField,
                                          FieldsGridTitleHint,
                                          IB_Connection.FieldsGridTitleHint );
end;

procedure TIB_Statement.UpdateDisplayWidth;
var
  ii: integer;
begin
  for ii := 0 to Fields.ColumnCount - 1 do
    SysUpdateDisplayWidth( Fields.Columns[ii] );
end;

procedure TIB_Statement.SysUpdateDisplayWidth( AField: TIB_Column );
var
  tmpStr: string;
begin
  with AField do
  begin
    tmpStr := Trim( GetColAttrTxt( AField,
                                   FieldsDisplayWidth,
                                   IB_Connection.FieldsDisplayWidth ));
    if Length( tmpStr ) = 0 then
      FDisplayWidth := -1
    else
      FDisplayWidth := StrToInt( tmpStr );
  end;
end;

procedure TIB_Statement.UpdateEditMask;
var
  ii: integer;
begin
  for ii := 0 to Fields.ColumnCount - 1 do
    with Fields.Columns[ii] do
      SysUpdateEditMask( Fields.Columns[ii] );
  if SetParamAttribs then
    for ii := 0 to Params.ColumnCount - 1 do
      with Params.Columns[ii] do
        SysUpdateEditMask( Params.Columns[ii] );
end;

procedure TIB_Statement.SysUpdateEditMask( AField: TIB_Column );
begin
  AField.FEditMask := GetColAttrTxt( AField,
                                     FieldsEditMask,
                                     IB_Connection.FieldsEditMask );
end;

procedure TIB_Statement.UpdateIndex;
var
  ii, jj: integer;
  tmpCol: TIB_Column;
begin
  for jj := 0 to FieldCount - 1 do
    if Fields.GetBySQLNo( jj, tmpCol ) then
      if tmpCol.Index <> jj then
        tmpCol.SetIndex( jj );
  ii := 0;
  for jj := 0 to FieldsIndex.Count - 1 do
    if Fields.GetByName( FieldsIndex[ jj ], tmpCol ) then
    begin
      tmpCol.SetIndex( ii );
      Inc( ii );
    end;
end;
procedure TIB_Statement.UpdateReadOnly;
var
  ii: integer;
begin
  for ii := 0 to Fields.ColumnCount - 1 do
    SysUpdateReadOnly( Fields.Columns[ii] );
end;
procedure TIB_Statement.SysUpdateReadOnly( AField: TIB_Column );
var
  tmpS1: TIB_StringList;
  tmpS2: TIB_StringList;
begin
  tmpS1 := FieldsReadOnly;
  tmpS2 := nil;
  if Assigned( IB_Connection ) then
    tmpS2 := IB_Connection.FieldsReadOnly;
  with AField do
  begin
    FReadOnly := GetColAttrChar( AField, tmpS1, tmpS2 ) = 'T';
    GetColParamValueEx( AField, IBO_NOINSERT, FPreventInserting, tmpS1, tmpS2 );
    GetColParamValueEx( AField, IBO_NOEDIT,   FPreventEditing,   tmpS1, tmpS2 );
    GetColParamValueEx( AField, IBO_NOSEARCH, FPreventSearching, tmpS1, tmpS2 );
  end;
end;
procedure TIB_Statement.UpdateTrimming;
var
  ii: integer;
begin
  for ii := 0 to Fields.ColumnCount - 1 do
    SysUpdateTrimming( Fields.Columns[ii] );
end;
procedure TIB_Statement.SysUpdateTrimming( AField: TIB_Column );
var
  tmpStr: string;
begin
  begin
    tmpStr := GetColAttrTxt( AField,
                             FieldsTrimming,
                             IB_Connection.FieldsTrimming );
    tmpStr := UpperCase( Copy( Trim( tmpStr ), 1, 2 ));
    if tmpStr = '' then tmpStr := ' ';
    with AField do
    begin
      if tmpStr[1] = 'N'  then FTrimming := ctNone else
      if tmpStr[1] = 'A'  then FTrimming := ctAll else
      if tmpStr[1] = 'B'  then FTrimming := ctBoth else
      if tmpStr[1] = 'L'  then FTrimming := ctLeft else
      if tmpStr[1] = 'R'  then FTrimming := ctRight else
      if tmpStr[1] = 'S'  then FTrimming := ctSentence else
      if tmpStr    = 'CN' then FTrimming := ctNoneCheckLen else
      if tmpStr    = 'CA' then FTrimming := ctAllCheckLen else
      if tmpStr    = 'CB' then FTrimming := ctBothCheckLen else
      if tmpStr    = 'CL' then FTrimming := ctLeftCheckLen else
      if tmpStr    = 'CR' then FTrimming := ctRightCheckLen else
      if tmpStr    = 'CS' then FTrimming := ctSentenceCheckLen else
      begin
        FTrimming := ctNone;
        if IsText then
        begin
          if not IB_Connection.DefaultNoTrimming then
            FTrimming := ctRight;
          if not IB_Connection.DefaultNoLengthCheck then
            Inc( FTrimming );
        end;
      end;
    end;
  end;
end;
procedure TIB_Statement.UpdateVisible;
var
  ii: integer;
begin
  with Fields do for ii := 0 to ColumnCount - 1 do
    SysUpdateVisible( Columns[ii] );
  if SetParamAttribs then
  with Params do for ii := 0 to ColumnCount - 1 do
    SysUpdateVisible( Columns[ii] );
end;
procedure TIB_Statement.SysUpdateVisible( AField: TIB_Column );
begin
  AField.FVisible := GetColAttrChar( AField,
                                     FieldsVisible,
                                     IB_Connection.FieldsVisible ) <> 'F';
end;

{------------------------------------------------------------------------------}

procedure TIB_Statement.GetBlobNodeData( AVar: PXSQLVAR;
                                         ArrayDesc: PISC_Array_Desc;
                                         ABlobNode: PIB_BlobNode );
begin
  if not Assigned( ABlobNode ) then
    raise EIB_Error.CreateWithSender( Self, E_Unassigned_Blob_Node );
  if CheckTransaction( true ) then
    with IB_Session do
    begin
      errcode := GetBlobData( pdbHandle,
                              ptrHandle,
                              AVar,
                              ArrayDesc,
                              ABlobNode,
                              FOnBlobCallback );
      if errcode = isc_segstr_eof then
        errcode := 0;
      if errcode <> 0 then
        HandleException( Self );
    end;
end;

procedure TIB_Statement.PutBlobNodeData( AVar: PXSQLVAR;
                                         ArrayDesc: PISC_Array_Desc;
                                         ABlobNode: PIB_BlobNode );
begin
  if not Assigned( ABlobNode ) then
    raise EIB_Error.CreateWithSender( Self, E_Unassigned_Blob_Node );
  CheckTransaction( true );
  with IB_Session do
  begin
    errcode := PutBlobData( pdbHandle,
                            ptrHandle,
                            AVar,
                            ArrayDesc,
                            ABlobNode,
                            FOnBlobCallback );
    if errcode <> 0 then
      HandleException( Self );
  end;
end;

procedure TIB_Statement.GetFieldNamesList( AStrings: TStrings );
var
  ii: integer;
begin
  try
    if AStrings <> nil then
      with AStrings do
      begin
        BeginUpdate;
        try
          Clear;
          for ii := 0 to FieldCount - 1 do
            AStrings.Add( Fields.Columns[ii].BestFieldName );
        finally
          EndUpdate;
        end;
      end;
  except
    on E: Exception do
      Application.HandleException( E );
  end;
end;

procedure TIB_Statement.GetParamNamesList( AStrings: TStrings );
var
  ii: integer;
begin
  if AStrings <> nil then
    with AStrings do
    begin
      BeginUpdate;
      try
        Clear;
        for ii := 0 to ParamCount - 1 do
          AStrings.Add( Params[ii].FullFieldName );
      finally
        EndUpdate;
      end;
    end;
end;

function TIB_Statement.GetFields: TIB_Row;
begin
  Result := FCursorFields;
end;

{------------------------------------------------------------------------------}

procedure TIB_Statement.SetSQLHistoryRef( AValue: integer );
begin
  if KeepSQLHistory then
  begin
    FSQLHistoryRef := AValue;
    if ( FSQLHistoryRef >= 0 ) and
       ( FSQLHistoryRef < FSQLHistory.Count ) then
      SQL.CommaText := SQLHistory[ SQLHistoryRef ]
    else
    begin
      SQL.Text := '';
      FSQLHistoryRef := SQLHistory.Count;
    end;
  end;
end;

function TIB_Statement.AddSQLHistory( ASQL: TStrings;
                                      Position: integer ): boolean;
var
  tmpStr: string;
begin
  Result := false;
  if KeepSQLHistory then
  begin
    Result := true;
    tmpStr := Trim( ASQL.CommaText );
    if Position < SQLHistory.Count then
      FSQLHistory[ Position ] := tmpStr
    else
    if ( tmpStr <> '' ) and
       (( FSQLHistory.Count = 0 ) or
        ( Trim( FSQLHistory[ FSQLHistory.Count - 1 ] ) <> tmpStr )) then
    begin
      if FSQLHistoryRef = SQLHistory.Count then
        Inc( FSQLHistoryRef );
      FSQLHistory.Add( tmpStr );
    end
    else
      Result := false;
  end;
end;

procedure TIB_Statement.ClearSQLHistory;
begin
  FSQLHistory.Clear;
  FSQLHistoryRef := 0;
end;

procedure TIB_Statement.RemoveSQLHistory( Position: integer );
begin
  FSQLHistory.Delete( Position );
  if FSQLHistoryRef > Position then
    Dec( FSQLHistoryRef );
  SetSQLHistoryRef( SQLHistoryRef );
end;

function TIB_Statement.GetSysKeyRelation: string;
begin
  Result := KeyRelation;
  if ( Result = '' ) and ( Fields.RelationCount = 1 ) then
    Result := Fields.RelationNames[0];
end;

{------------------------------------------------------------------------------}

procedure TIB_Statement.CancelQuery;
var
  SaveCW: word;
  t_st: status_vector;
  t_er: isc_status;
begin
  if Assigned( PstHandle ) then
  begin
    if ( PstHandle^ <> nil ) and
       ( PstHandle^ <> FakePointer ) then
    begin
      with IB_Session do
      begin
        asm fstcw [SaveCW] end;
        t_er := isc_dsql_free_statement( @t_st, PstHandle, DSQL_Cancel );
        asm fldcw [SaveCW] end;
        if t_er <> 0 then
        begin
        // This is NOT likely to error out but I want to avoid corrupting
        // the status vector when calling this from a sub-thread.
          status := t_st;
          errcode := t_er;
          HandleException( Self );
        end;
      end;
    end;
  end;
end;
 
function TIB_Statement.GetCalculatingFields: boolean;
begin
  Result := Fields.FCalculatingFields;
end;

procedure TIB_Statement.CalculateFields;
begin
  if Prepared then
    with Fields do
    begin
      BeginUpdate;
      try
        CalculateFields;
      finally
        EndUpdate( false );
      end;
    end;
end;

procedure TIB_Statement.DoCalculateField( ARow: TIB_Row; AField: TIB_Column );
begin
  if Prepared then
  begin
    if Assigned( OnCalculateField ) then
      OnCalculateField( Self, ARow, AField )
    else
    if not Assigned( AField ) then
      if ( Self is TIB_Dataset ) then
        TIB_Dataset(Self).KeyToChildAction( kcaUpdateKeyDescCalcFields,
                                            longint(ARow) );
  end;
end;

procedure TIB_Statement.Prepare;
begin
  BeginBusy( false );
  try
    SysPrepare;
  finally
    EndBusy;
  end;
end;

procedure TIB_Statement.Unprepare;
begin
  if Prepared then
  begin
    BeginBusy( false );
    try
      SysDeallocate( false );
    finally
      EndBusy;
    end;
  end;
end;

procedure TIB_Statement.Execute;
begin
  BeginBusy( false );
  try
    SysClose;
    SysExecute;
  finally
    EndBusy;
  end;
end;

procedure TIB_Statement.ExecSQL;
begin
  Execute;
end;

procedure TIB_Statement.ExecuteDDL( const Statement: string );
begin
  if CheckConnection( true ) and
     CheckTransaction( true ) then
  begin
    BeginBusy( false );
    try
      ProcessEvent( BeforeExecDDL );
      ProcessLinkEvent( setBeforeExecDDL, 0 );
      IB_Connection.SysBeforeExecDDL;
      IB_Transaction.SysBeforeExecDDL;
      try
        SysExecuteImmediate( Statement, nil );
        IB_Transaction.SysAfterExecDDL;
        IB_Connection.SysAfterExecDDL;
        ProcessEvent( AfterExecDDL );
        ProcessLinkEvent( setAfterExecDDL, 0 );
      finally
        IB_Transaction.Activate;
      end;
    finally
      EndBusy;
    end;
  end;
end;

procedure TIB_Statement.ExecuteDML( const Statement: string; AParam: PXSQLDA );
begin
  if CheckConnection( true ) and
     CheckTransaction( true ) then
  begin
    BeginBusy( false );
    try
      ProcessEvent( BeforeExecDML );
      ProcessLinkEvent( setBeforeExecDML, 0 );
      try
        SysExecuteImmediate( Statement, AParam );
        ProcessEvent( AfterExecDML );
        ProcessLinkEvent( setAfterExecDML, 0 );
      finally
        IB_Transaction.Activate;
      end;
    finally
      EndBusy;
    end;
  end;
end;

procedure TIB_Statement.ExecuteImmediate( const Statement: string;
                                                AParam: PXSQLDA );
begin
  BeginBusy( false );
  try
    SysExecuteImmediate( Statement, AParam );
  finally
    EndBusy;
  end;
end;

procedure TIB_Statement.ExecImmed2( const Statement: string;
                                          AParam, AField: PXSQLDA );
begin
  BeginBusy( false );
  try
    SysExecImmed2( Statement, AParam, AField );
  finally
    EndBusy;
  end;
end;

procedure TIB_Statement.FreeServerResources;
var
  SaveCW: word;
begin
  if Assigned( FStHandle ) and ( FStHandle <> pointer(-1) ) then
  begin
    if Prepared then
      InvalidateSQL;
    if Assigned( IB_Connection ) then
      IB_Connection.DeallocateStmtHandle( PstHandle )
    else
    with IB_Session do
    begin
      asm fstcw [SaveCW] end;
      errcode := isc_dsql_free_statement( @status, PstHandle, DSQL_DROP );
      asm fldcw [SaveCW] end;
    end;
    FStHandle := pointer(-1);
  end;
end;

function TIB_Statement.FieldByName( const AFieldName: string ): TIB_Column;
begin
  Prepared := true;
  Result := Fields.ByName( AFieldName );
end;

function TIB_Statement.ParamByName( const AFieldName: string ): TIB_Column;
begin
  Prepared := true;
  Result := Params.ByName( AFieldName );
end;
function TIB_Statement.FindField( const FieldName: string ): TIB_Column;
begin Fields.GetByname( FieldName, Result ); end;
function TIB_Statement.FindKeyField( const FieldName: string ): TIB_Column;
begin CursorKeyFields.GetByName( FieldName, Result ); end;
function TIB_Statement.FindParam( const FieldName: string ): TIB_Column;
begin Params.GetByName( FieldName, Result ); end;
function TIB_Statement.GetParams: TIB_Row;
begin Result := FParams; end;
function TIB_Statement.GetFieldCount: integer;
begin Result := Fields.ColumnCount; end;
function TIB_Statement.GetParamCount: integer;
begin Result := Params.ColumnCount; end;
function TIB_Statement.GetIsSelectSQL: boolean;
begin
  Result := StatementType in [stSelect, stSelectForUpdate];
end;

function TIB_Statement.GetSQLIsAggregate: boolean;
var
  ii: integer;
  tmpPos: integer;
  tmpSel: integer;
  tmpStr: string;
  tmpLen: longint;
  tmpCh: char;
  BegPos,
  EndPos,
  UnionLevel: integer;
  s: string;
begin
  s := SQL.Text;
  if FAlwaysCallMacroSubstitute or Assigned( FOnMacroSubstitute ) then
    s := IB_Parse.SubstMacros( s,
                               SysSubstituteMacros,
                               MacroBegin,
                               MacroEnd );
  GetSQLGroup( s, tmpStr, BegPos, EndPos );
  Result := Trim( tmpStr ) <> '';
  if not Result then
  begin
    UnionLevel := -1;
    GetSQLSelect( s, tmpStr, BegPos, EndPos, UnionLevel );
    tmpStr := AnsiUpperCase( tmpStr );
    for ii := 1 to Length( tmpStr ) do
      if Ord( tmpStr[ii] ) < 32 then
        tmpStr[ii] := ' ';
    if ( getLitsRoundSafeStrPos(' DISTINCT ', tmpStr, 1, true, false ) > 0) or
       ( getLitsRoundSafeStrPos(' DISTINCT(', tmpStr, 1, true, false ) > 0) then
      Result := true
    else
    if Pos( '(', tmpStr ) > 0 then
    begin
      if FSysFieldNames.Count = 0 then
      begin
        if PreparingSQL then
        begin
          if ParamCheck then
            tmpCh := ParamChar
          else
            tmpCh := #0;
          // Avoides infinite loop.
          FSelectParamsCount := MakeServerSQL( s,
                                               FSysParamNames,
                                               FSysFieldNames,
                                               FSysTableNames,
                                               tmpStr,
                                               tmpCh,
                                               false,
                                               tmpLen,
                                               IB_Connection.OldParameterOrder );
          FSysFieldNamesNeedRefine := true;
        end
        else
          SysGetRawSQL;
      end;
      for ii := 0 to FSysFieldNames.Count - 1 do
      begin
        tmpStr := AnsiUpperCase( GetCharValues( FSysFieldNames[ii] ));
        if Pos( '(', tmpStr ) > 0 then
        begin
          replace_string( tmpStr, ' (', '(' );
            tmpPos := getLitsRoundSafeStrPos( 'COUNT(', tmpStr, 1, true, false );
          if tmpPos = 0 then
            tmpPos := getLitsRoundSafeStrPos( 'MAX(', tmpStr, 1, true, false );
          if tmpPos = 0 then
            tmpPos := getLitsRoundSafeStrPos( 'MIN(', tmpStr, 1, true, false );
          if tmpPos = 0 then
            tmpPos := getLitsRoundSafeStrPos( 'AVG(', tmpStr, 1, true, false );
          if tmpPos = 0 then
            tmpPos := getLitsRoundSafeStrPos( 'SUM(', tmpStr, 1, true, false );
          if tmpPos > 0 then
          begin
          // I cannot remember what this check for SELECT is all about.
          // I think it is to detect whether there is an in-line select that
          // is using the aggregate instead of the query itself.
          // I put in a new check to avoid corruption from stuff in comments.
            tmpSel := getLitSafeStrPos( 'SELECT ', tmpStr, 1 );
            Result := ( tmpSel = 0 ) or ( tmpPos < tmpSel );
            if Result then
              Break;
          end;
        end;
      end;
    end;
  end;
end;

function TIB_Statement.GetSQLIsSelectProc: boolean;
var
  tmpStr: string;
  tmpStu: string;
  tmpPos: integer;
  tmpLen: longint;
  BegPos,
  EndPos,
  UnionLevel: integer;
begin
  UnionLevel := -1;
  GetSQLFrom( SQL.Text, tmpStr, BegPos, EndPos, UnionLevel );
  MakeServerSQL( Trim(tmpStr), nil,nil,nil, tmpStr, ParamChar, false, tmpLen,
                 false );
  if ( not Assigned( IB_Connection )) or ( IB_Connection.SQLDialect <> 3 ) then
    tmpStr := AnsiUpperCase( tmpStr );
  tmpStu := AnsiUpperCase( tmpStr );
  for tmpPos := 1 to Length( tmpStr ) do
    if Ord( tmpStr[tmpPos] ) < 32 then
      tmpStr[tmpPos] := ' ';
  Result := ( Pos( '(', tmpStu ) > 0 ) and
            ( not (( Pos( ' ON ', tmpStu ) > 0 ) or
                   ( Pos( ' JOIN ', tmpStu ) > 0 )));
  CheckConnection( false );
  if Assigned( IB_Connection ) and ( not Result ) then
  begin
    tmpStr := SysKeyRelation;
    if tmpStr <> '' then
      Result := IB_Connection.SchemaCache.ProcedureNames.IndexOf(tmpStr) <> -1;
  end;
end;

function TIB_Statement.GetSQLIsExecuteBlock: boolean;
var
  tmpStr: string;
  trm: String;
  tmpLen: longint;
  BegPos, EndPos: longint;
begin
  MakeServerSQL( Trim(SQL.Text), nil,nil,nil, tmpStr, ParamChar, false, tmpLen,
                 false );
  BegPos := ParseLineInvalid;
  EndPos := ParseLineEnd;
  Result := ParseStr( tmpStr,
             'EXECUTE', [ pfFirstToken, pfPairedTokens ], //true, false, true,
             ['BLOCK'], BegPos, EndPos, trm );
end;

function TIB_Statement.GetFieldValue( const FieldName: string ): Variant;
begin Result := Fields.GetColumnValue( FieldName ); end;
procedure TIB_Statement.SetFieldValue( const FieldName: string;
                                       const Value: Variant );
begin Fields.SetColumnValue( FieldName, Value ); end;
function TIB_Statement.GetParamValue( const ParamName: string ): Variant;
begin Result := Params.GetColumnValue( ParamName ); end;
procedure TIB_Statement.SetParamValue( const ParamName: string;
                                       const Value: Variant );
begin Params.SetColumnValue( ParamName, Value ); end;
procedure TIB_Statement.GetFieldList( AList: TList; const FieldNames: string );
begin Fields.GetColumnList( AList, FieldNames ); end;
procedure TIB_Statement.GetParamList( AList: TList; const ParamNames: string );
begin Params.GetColumnList( AList, ParamNames ); end;

{------------------------------------------------------------------------------}

function TIB_Statement.CheckConnection( RequestConnect: boolean ): boolean;
var
  tmpStr: string;
begin
  if ( IB_Connection = nil ) and ( FDatabaseName <> '' ) then
    IB_Connection := IB_Session.GetConnectionByName( FDatabaseName );
  if ( IB_Connection = nil ) and IB_Session.AllowDefaultConnection then
    IB_Connection := GetDefaultConnection;
  if RequestConnect and
     ( not Assigned( IB_Connection )) and
     ( not ( csDestroying in ComponentState )) and
     ( not ( csReading in ComponentState )) then
  begin
    if Assigned( Owner ) then tmpStr := Owner.Name else tmpStr := '';
    if tmpStr <> '' then tmpStr := tmpStr + '.';
    if Name <> '' then tmpStr := tmpStr + Name else
      tmpStr := tmpStr + '<no name>';
    raise EIB_StatementError.CreateWithSender( Self,
                                               E_NO_CONNECTION+ ': ' + tmpStr );
  end;
  if RequestConnect and ( not FIB_ConnectionLink.Connected ) then
    IB_Connection.Connect;
  Result := FIB_ConnectionLink.Connected;
end;

function TIB_Statement.CheckTransaction( RequestStart: boolean ): boolean;
begin
  if IB_Transaction = nil then
    CheckConnection( false );
  if ( IB_Transaction = nil ) then
    IB_Transaction := GetDefaultTransaction;
  if ( IB_Transaction = nil ) and
     (( IB_Session.AllowDefaultTransaction ) or
      ( csDesigning in ComponentState )) then
  begin
    if FIB_Transaction = nil then
    begin
      FIB_Transaction := TIB_TransactionDefault.Create( Self );
      FIB_Transaction.Name := 'tr' + Self.Name;
      FIB_Transaction.AutoCommit := true;
      FIB_Transaction.Isolation := tiCommitted;
    end
    else
    with FIB_Transaction do
      if AutoCommit and Started then
        Commit;
    IB_Transaction := FIB_Transaction;
  end;
  if Assigned( FIB_Transaction ) then
    FIB_Transaction.IB_Connection := IB_Connection;
  if RequestStart and ( not FIB_TransactionLink.Started ) then
  begin
    if not Assigned( IB_Transaction ) then
      raise EIB_Error.CreateWithSender( Self, E_Invalid_Internal_Transaction );
    if not Assigned( IB_Transaction.IB_Connection ) then
      CheckConnection( false );
    if Assigned( IB_Transaction.IB_Connection ) then
      with IB_Transaction.IB_Connection do
        if ( not Connected ) and
           ( ConnectionStatus <> csDisconnected ) then
          API_Connect;
    IB_Transaction.SysStart;
    if ( not FIB_TransactionLink.Started ) and Assigned( IB_Transaction ) then
      IB_Transaction.SysStart;
  end;
  Result := FIB_TransactionLink.Started;
end;

function TIB_Statement.SysAllocate: boolean;
begin
  if ( not Assigned( FStHandle )) and CheckConnection( true ) then
  begin
    IB_Connection.AllocateStmtHandle( PstHandle );
    FSQLDialect := IB_Connection.SQLDialect;
  end;
  Result := Assigned( FStHandle );
end;

procedure TIB_Statement.SysDeallocate( AllowCachedHandle: boolean );
var
  SaveCW: word;
begin
  if Assigned( FStHandle ) then
  begin
    SysUnprepare;
    if longint(FStHandle) <> -1 then
    begin
      if Assigned( IB_Connection ) and AllowCachedHandle then
        IB_Connection.DeallocateStmtHandle( PstHandle )
      else
      with IB_Session do
      begin
        asm fstcw [SaveCW] end;
        errcode := isc_dsql_free_statement( @status, PstHandle, DSQL_DROP );
        asm fldcw [SaveCW] end;
      end;
    end;
    FStHandle := nil;
  end;
  if Assigned( FIB_Transaction ) and FIB_Transaction.Started then
    if FIB_Transaction.FConnectionWasLost then FIB_Transaction.Rollback else
                                               FIB_Transaction.Commit;
  if Assigned( IB_Connection ) then
  //!!! Check for a lost connection.
    FSQLDialect := IB_Connection.SQLDialect;
end;

procedure TIB_Statement.InvalidateSQL;
begin
  ProcessEvent( OnInvalidateSQL );
  if FSQLIsValid then
    FSQLIsValid := false;
end;

function TIB_Statement.SysPrepare: boolean;
begin
  if Prepared then
  begin
    if not SQLIsValid then
    begin
      if Active then
        SysClose;
      SysExecPrepare;
    end;
  end
  else
  if ( csLoading in ComponentState ) or
     ( csReading in ComponentState ) or
     ( csFixups  in ComponentState ) then
    flag_prepare_after_load := true
  else
  if SysAllocate and CheckTransaction( true ) then
  begin
    SysExecPrepare;
    if not SQLIsValid then
      SysExecPrepare;
  end;
  Result := Prepared;
end;

function TIB_Statement.GetSQLIsRefined: boolean;
begin
  Result := ServerSQL <> RefinedSQL;
end;

function TIB_Statement.GetSQLDialect: integer;
begin
  Result := FSQLDialect;
  if ( Result < 3 ) then
  begin
    if not Assigned( IB_Connection ) then
      CheckConnection( false );
    if Assigned( IB_Connection ) then
    begin
      if IB_Connection.SQLDialect > 0 then
        Result := IB_Connection.SQLDialect;
    end;
  end;
end;

procedure TIB_Statement.SysExecPrepare;
var
  New_In,
  New_Out: smallint;
  oldSQL: string;
  oldJoin: string;
  tmpSQL: string;
  tmpLen: longint;
  oldSQLValid: boolean;
begin
  if not PreparingSQL then
  try
    if Prepared then
    begin
      FRepreparingSQL := true;
      SysStoreParamValueLinks;
    end
    else
      SysBeforePrepare;
    FPreparingSQL := true;
    oldSQL := SQL.Text;
    oldJoin := FJoinLinks.Text;
    oldSQLValid := SQLIsValid;
    try
      try
        SQL.BeginUpdate;
        try
          ParamValueLinks.Clear;
          FSQLIsValid := true;
          SysGetRawSQL;
        finally
          if SQL.Text <> oldSQL then
            SQL.Text := oldSQL;
          if FJoinLinks.Text <> oldJoin then
            FJoinLinks.Text := oldJoin;
        end;
      finally
        SQL.EndUpdate;
      end;
    except
      FSQLIsValid := oldSQLValid;
      SysPrepareFailed;
      raise;
    end;
    with IB_Session do
    begin
      errcode := API_Prepare( PChar( RefinedSQL ), New_In, New_Out );
      if errcode = isc_dsql_error then
      begin
        if IsTriggerOrProcedureDDL( SQL.Text ) then
        begin
          if ParamCheck then
          begin
            MakeServerSQL( SQL.Text, nil,
                                     nil,
                                     nil, tmpSQL, #0, true, tmpLen, false );
            errcode := API_Prepare( PChar( tmpSQL ), New_In, New_Out );
          end;
        end
        else
        begin
        // Try stripping comments out.
          MakeServerSQL( RefinedSQL, nil,
                                     nil,
                                     nil,
                                     tmpSQL, ParamChar, false, tmpLen, false );
          errcode := API_Prepare( PChar( tmpSQL ), New_In, New_Out );
        end;
      end;
      if errcode = 0 then
      begin
        FPrepared := true;
        try
          FParamCount := New_In;
          FCursorFieldCount := New_Out;
          SysUpdateDescriptors;
        except
          FSQLIsValid := oldSQLValid;
          SysPrepareFailed;
          raise;
        end;
        try
          if ( StatementLinkCount > 0 ) and
             (( FParamCount > 0 ) or RepreparingSQL ) then
          begin
            ProcessLinkEvent( setParamsRefsChanged, 0 );
          end;
          if RepreparingSQL then
            SysLayoutChanged
          else
          begin
            try
              if ( StatementLinkCount > 0 ) then
                ProcessLinkEvent( setFieldsRefsChanged, 0 );
              SysUpdateLayout( true );
              AddSQLHistory( SQL, FSQLHistoryRef );

            finally
              if FCursorFieldCount > 0 then
                CursorFields.CleanBuffers( true );
              SysPreparedChanged;
            end;
          end;
        finally
          if ( not Assigned( FBindingCursor )) and ( ParamCount > 0 ) then
            SysRestoreParamValueLinks;
          SysAfterPrepare;
        end;
      end
      else
      begin
        SysPrepareFailed;
        HandleException( Self );
      end;
      if (Status[0] = isc_arg_gds {1}) and
         (Status[1] = 0) and
         (Status[2] = isc_arg_warning {18}) then
        HandleException (Self)
    end;
  finally
    FPreparingSQL := false;
    FRepreparingSQL := false;
  end;
end;

procedure TIB_Statement.SysDescribeVARList( ARow: TIB_Row );
var
  ii: integer;
begin
  with IB_Session, ARow do
  begin
    case RowType of
      rtParam:
      begin
        for ii := 0 to FInDa.SQLd - 1 do
          FPSQLDA.SQLVAR[ ii ] := FInDa.SQLVAR[ ii ];
        NameParams;
      end;
      rtField:
      begin
        for ii := 0 to FOutDa.SQLd - 1 do
          FPSQLDA.SQLVAR[ ii ] := FOutDa.SQLVAR[ ii ];
      end;
    end;
  end;
end;

function TIB_Statement.DoMacroSubstitute( const ASQL: string ): string; 
begin
  if FAlwaysCallMacroSubstitute or Assigned( FOnMacroSubstitute ) then
    Result := IB_Parse.SubstMacros( ASQL,
                                    SysSubstituteMacros,
                                    MacroBegin,
                                    MacroEnd )
  else
    Result := ASQL;
end;

procedure TIB_Statement.SysGetRawSQL;
var
  tmpLen: longint;
  tmpBool: boolean;
  tmpCh: char;
begin
  if ParamCheck then tmpCh := ParamChar else tmpCh := #0;
  tmpBool := SysNeedToRefineSQL;
  SysInitRawSQL;
  SQL.Text := DoMacroSubstitute( SQL.Text );
  SysPrepareSQL;
  SysFinishRawSQL;
  if tmpBool then
  begin
    MakeServerSQL( SQL.Text, nil, nil, nil,
                   FServerSQL, tmpCh, true, tmpLen, false );
    try
      FRefiningSQL := true;
      try
        SysInitRawSQL;
        SysRefineSQL;
        SysFinishRawSQL;
      except
        FSQLIsValid := false;
        raise;
      end;
    finally
      FRefiningSQL := false;
    end;
  end;
  FSelectParamsCount := MakeServerSQL( SQL.Text,
                                       FSysParamNames,
                                       FSysFieldNames,
                                       FSysTableNames,
                                       FRefinedSQL,
                                       tmpCh,
                                       true,
                                       tmpLen,
                                       IB_Connection.OldParameterOrder );
  FSysFieldNamesNeedRefine := true;
  // Have the refinedsql parsed for macros - to cater for additions
  // coming from filters, searches etc.
  if FAlwaysCallMacroSubstitute or Assigned( FOnMacroSubstitute ) then
    FRefinedSQL := IB_Parse.SubstMacros( FRefinedSQL,
                                         SysSubstituteMacros,
                                         MacroBegin,
                                         MacroEnd );
  if not tmpBool then
    FServerSQL := FRefinedSQL
  else
    // if ServerSQL was not copied from refined then also have it parsed
    // for macro insertion
    if FAlwaysCallMacroSubstitute or Assigned( FOnMacroSubstitute ) then
      FServerSQL := IB_Parse.SubstMacros( FServerSQL,
                                          SysSubstituteMacros,
                                          MacroBegin,
                                          MacroEnd );
  FPreparedSQL := SQL.Text;
end;

function TIB_Statement.SysSubstituteMacros( const ATextBlock: string ): string;
begin
  Result := '';
  if Assigned( FOnMacroSubstitute ) then
    OnMacroSubstitute( Self, ATextBlock, Result );
end;

procedure TIB_Statement.SysFieldNamesRefine;
var
  ii, jj: integer;
  tmpStr: string;
  tmpRel: string;
  tmpFlds: TIB_StringList;
  tmpPos: integer;
  trm: string;
  begPos, endPos: longint;
begin
  FSysFieldNamesNeedRefine := false;
  ii := 0;
  tmpFlds := TIB_StringList.Create;
  try
    FSysFieldNames.BeginUpdate;
    try
      while ii <= FSysFieldNames.Count - 1 do
      begin
        tmpStr := GetCharValues( FSysFieldNames[ii] );
        for jj := 1 to Length( tmpStr ) do
          if tmpStr[ jj ] < ' ' then
            tmpStr[ jj ] := ' ';
        tmpStr := Trim( tmpStr );
        if tmpStr = '<*>' then
        begin
          FSysFieldNames.Delete( ii );
          tmpStr := Trim( FSysFieldNames[ii] );
          if tmpStr = '*' then
            tmpRel := GetRelNameByRelAlias( SysKeyRelation )
            // Need to parse for multiple table names later on.
            // We are safe for now because it is only needing the count and if
            // it is wrong it auto corrects by describing the output fields
            // after the prepare is performed.
          else
          begin
            tmpRel := '';
            tmpPos := getLitSafeStrPos( '.*', tmpStr, 1 );
            if tmpPos > 0 then
            begin
              tmpRel := Copy( tmpStr, 1, tmpPos - 1 );
              tmpRel := GetRelNameByRelAlias( tmpRel );
            end;
            // Parse table alias names later on.
            // If an alias name is used and no table exists then it will simply
            // get corrected by describing the fields after the prepare.
          end;
          if tmpRel <> '' then
          begin
            with IB_Connection.SchemaCache do
            begin
              if TableNames.IndexOf( tmpRel ) <> -1 then
              begin
                GetTableFields( tmpRel, tmpFlds );
                if tmpFlds.Count > 0 then
                begin
                  FSysFieldNames.Delete( ii );
                  for jj := tmpFlds.Count - 1 downto 0 do
                    FSysFieldNames.Insert( ii, SetCharValues( tmpFlds[jj] ));
                  Inc( ii, tmpFlds.Count );
                end;
              end;
            end;
          end;
        end
        else
        begin
        // Need to remove the AS < aliasname > part so that this won't foul up
        // other uses. This is also because we already have access to this info
        // using the aliasname property.
          trm := '';
          begPos := ParseLineInvalid;
          endPos := ParseLineEnd;
          if ParseStr( tmpStr, '', [], ['AS'], begPos, endPos, trm ) then
          begin
            tmpStr := Trim( Copy( tmpStr, 1, endPos - 1 ));
            FSysFieldNames[ii] := SetCharValues( tmpStr );
          end
          else
          begin
          // This appears to be looking to remove an alias name that was put
          // here without the AS operator.

          // !! use the GetNextValue() routine and then what's left is junk.
            begPos := getLitSafePosFromRight( ' ', tmpStr, 0, true );
            if begPos > 0 then
            begin
              tmpStr := Trim( Copy( tmpStr, 1, begPos - 1 ));
              FSysFieldNames[ii] := SetCharValues( tmpStr );
            end;
          end;
          Inc( ii );
        end;
        tmpFlds.Clear;
      end;
    finally
      FSysFieldNames.EndUpdate;
    end;
  finally
    tmpFlds.Free;
  end;
end;

function TIB_Statement.GetSysParamNames: TIB_StringList;
begin
  Result := FSysParamNames;
end;

function TIB_Statement.GetSysFieldNames: TIB_StringList;
begin
  if FSysFieldNamesNeedRefine then
    SysFieldNamesRefine;
  Result := FSysFieldNames;
end;

function TIB_Statement.GetSysTableNames: TIB_StringList;
begin
  Result := FSysTableNames;
end;

procedure TIB_Statement.SysInitRawSQL;
begin
end;

procedure TIB_Statement.SysFinishRawSQL;
begin
end;

procedure TIB_Statement.SysRefineSQL;
begin
end;

function TIB_Statement.SysNeedToRefineSQL: boolean;
begin
  Result := false;
end;

function TIB_Statement.GetClientSQL: string;
begin
  if Prepared then
  begin
    SysBeforeExecute;
    SysPrepare;
    MakeClientSQL( ServerSQL, Result, ParamChar );
  end
  else
    Result := SQL.Text;
end;

procedure TIB_Statement.SysBeforePrepare;
begin
  ProcessEvent( BeforePrepare );
  ProcessLinkEvent( setBeforePrepare, 0 );
end;

procedure TIB_Statement.SysAfterPrepare;
begin
  ProcessLinkEvent( setAfterPrepare, 0 );
  ProcessEvent( AfterPrepare );
end;

procedure TIB_Statement.SysClose;
begin
  if Active then
  begin
    FActive := false;
    SysActiveChange;
  end;
end;

procedure TIB_Statement.SysUnprepare;
begin
  if Prepared then
  begin
    if not FUnpreparing then
    begin
      FUnpreparing := true;
      try
        SysClose;
        SysBeforeUnprepare;
        SysStoreParamValueLinks;
        FPrepared := false;
        FServerSQL := '';
        FSysParamNames.Clear;
        FSysFieldNames.Clear;
        FSysTableNames.Clear;
        FParamCount := 0;
        FCursorFieldCount := 0;
        try
          SysUpdateDescriptors;
          ProcessLinkEvent( setParamsRefsChanged, 0 );
          ProcessLinkEvent( setFieldsRefsChanged, 0 );
          SysLayoutChanged;
        finally
          SysPreparedChanged;
          SysAfterUnprepare;
        end;
      finally
        FUnpreparing := false;
      end;
    end;
  end;
end;

procedure TIB_Statement.SysBeforeUnprepare;
begin
  ProcessEvent( BeforeUnprepare );
  ProcessLinkEvent( setBeforeUnprepare, 0 );
end;

procedure TIB_Statement.SysAfterUnprepare;
begin
  ProcessLinkEvent( setAfterUnprepare, 0 );
  ProcessEvent( AfterUnprepare );
end;

procedure TIB_Statement.SysExecute;
begin
  if CheckTransaction( true ) then
  begin
    if SysPrepare then
    begin
      SysBeforeExecute;
      if FieldCount > 0 then
        SysBeforeExecuteForOutput;
      if ParamCount > 0 then
        SysBeforeExecuteWithInput;
      FWasSingleton := false;
      try
        SysExecStatement;
      except
        SysExecuteFailed;
        raise;
      end;
      flag_rows_affected_invalid := true;
      try
        if Assigned( IB_Transaction ) then
        begin
          if ( StatementType in [ stInsert,
                                  stUpdate,
                                  stDelete,
                                  stDDL ] ) or
             (( StatementType = stExecProcedure ) and
               ( StoredProcHasDML or ( FieldCount = 0 ))) then
          begin
            if IB_Transaction.Started then
              IB_Transaction.Activate;
          end;
        end;
      finally
        if ParamCount > 0 then
          SysAfterExecuteWithInput;
        if FieldCount > 0 then
        begin
          if IB_Session.errcode = 100 then
            SysAfterExecuteWithEof
          else
            SysAfterExecuteForOutput;
        end;
        SysAfterExecute;
      end;
    end
    else
      raise EIB_StatementError.CreateWithSender( Self, E_FAILED_TO_PREPARE );
  end
  else
  if //not Assinged( IB_Transaction ) and
     not (( csLoading in ComponentState ) or
          ( csDestroying in ComponentState ) or
          ( csFixups  in ComponentState )) then
  begin
    raise EIB_StatementError.CreateWithSender( Self, E_NO_ACTIVE_TRANS );
  end;
end;

procedure TIB_Statement.SysExecuteFailed;
begin
  if StatementType in [ stInsert, stUpdate, stExecProcedure ] then
    Params.AfterPostBuffers( false );
end;

procedure TIB_Statement.SysBeforeExecuteForOutput;
begin
  CursorFields.CleanBuffers( true );
end;

procedure TIB_Statement.SysAfterExecuteForOutput;
begin
  if not Active then
  begin
    FActive := true;
    if WasSingleton then
    begin
      CursorFields.RefreshBuffers( true, true, true );
      CursorFields.SysAfterModify( nil );
    end;
    SysActiveChange;
  end;
end;

procedure TIB_Statement.SysAfterExecuteWithEof;
begin
  if Active then
  begin
    FActive := false;
    CursorFields.ClearBuffers( rsNone );
    CursorFields.SysAfterModify( nil );
    SysActiveChange;
  end
  else
  begin
    CursorFields.ClearBuffers( rsNone );
    CursorFields.SysAfterModify( nil );
  end;;
end;


procedure TIB_Statement.SysBeforeExecuteWithInput;
begin
  Params.PostBlobBuffers;
end;

procedure TIB_Statement.SysAfterExecuteWithInput;
begin
  Params.AfterPostBuffers( true );
end;

procedure TIB_Statement.SysExecStatement;
begin
  if not ( StatementType in [ stSetGenerator,
                              stCommit,
                              stRollback,
                              stStartTrans,
                              stSavePoint ] ) then
    CheckTransaction( true );
  case StatementType of
    stSelect:
      SysExecSelect;
    stInsert,
    stUpdate,
    stDelete:
      begin
        ProcessEvent( BeforeExecDML );
        ProcessLinkEvent( setBeforeExecDML, 0 );
        API_Execute;
        ProcessEvent( AfterExecDML );
        ProcessLinkEvent( setAfterExecDML, 0 );
      end;
    stDDL:
      begin
        ProcessEvent( BeforeExecDDL );
        ProcessLinkEvent( setBeforeExecDDL, 0 );
        IB_Connection.SysBeforeExecDDL;
        IB_Transaction.SysBeforeExecDDL;
        API_Execute;
        IB_Transaction.SysAfterExecDDL;
        IB_Connection.SysAfterExecDDL;
        ProcessEvent( AfterExecDDL );
        ProcessLinkEvent( setAfterExecDDL, 0 );
      end;
    stGetSegment,
    stPutSegment:
      API_Execute;
    stExecProcedure:
      API_Execute2;
    stStartTrans:
      if Assigned( IB_Transaction ) then
      begin
        IB_Transaction.Started := true;
        IB_Transaction.StartTransaction
      end else
        raise EIB_StatementError.CreateWithSender( Self,
                                                   E_Unassigned_Transaction );
    stCommit:
      if Assigned( IB_Transaction ) then
        IB_Transaction.SysCommit( false )
      else
        raise EIB_StatementError.CreateWithSender( Self,
                                                   E_Unassigned_Transaction );
    stRollback:
      if Assigned( IB_Transaction ) then
        IB_Transaction.SysRollback( false )
      else
        raise EIB_StatementError.CreateWithSender( Self,
                                                   E_Unassigned_Transaction );
    stSelectForUpdate:
      SysExecSelect;
    stSetGenerator:
      API_Execute;
    stSavePoint:
      API_Execute;
    else
      raise EIB_StatementError.CreateWithSender( Self,
                                                 E_UNKNOWN_STATEMENT + ': ' +
                                               IntToStr( Ord( StatementType )));
  end;
end;

procedure TIB_Statement.SysExecSelect;
begin
  API_Execute2;
end;

procedure TIB_Statement.SysBeforeExecute;
begin
  ProcessEvent( BeforeExecute );
  ProcessLinkEvent( setBeforeExecute, 0 );
  if ParamCount > 0 then
    ProcessLinkEvent( setParamsUpdateData, 0 );
end;

procedure TIB_Statement.SysAfterExecute;
begin
  ProcessLinkEvent( setAfterExecute, 0 );
  ProcessEvent( AfterExecute );
end;

procedure TIB_Statement.SysExecuteImmediate( const Statement: string;
                                                   AParam: PXSQLDA );
var
  tmpStr,
  tmpType: string;
var
  SaveCW: word;
  NewDBHandle: isc_db_handle;
  NewTRHandle: isc_db_handle;
  tmpLen: longint;
  tmpS: string;
begin
  if FAlwaysCallMacroSubstitute or Assigned( FOnMacroSubstitute ) then
    tmpS := IB_Parse.SubstMacros( Statement,
                                  SysSubstituteMacros,
                                  MacroBegin,
                                  MacroEnd )
  else
    tmpS := Statement;
  MakeServerSQL( tmpS, nil, nil, nil, tmpS, #0, true, tmpLen, false );
  if FIB_ConnectionLink.Connected then
  begin
    if CheckTransaction( true ) then
      API_ExecuteImmediate( tmpS, AParam );
  end
  else
  if IB_Connection <> nil then
  begin
    NewDBHandle := nil;
    NewTRHandle := nil;
    with IB_Connection.IB_Session do
    begin
      if not Assigned( isc_dsql_execute_immediate ) then
        RevertToOriginalHooks; // does an acquire of hooks if necessary
      if Assigned( isc_dsql_execute_immediate ) then
      begin
        asm fstcw [SaveCW] end;
        errcode := isc_dsql_execute_immediate( @status,
                                               @NewDBHandle,
                                               @NewTRHandle,
                                               Length(tmpS),
                                               PChar(tmpS),
                                               IB_Connection.SQLDialect,
                                               AParam );
        asm fldcw [SaveCW] end;
      end
      else
        errcode := -1;
      if ( NewDBHandle <> nil ) or ( errcode = isc_bad_db_handle ) then
      begin
        if SetConnectionProperties( IB_Connection, tmpS, tmpType ) then
        begin
          if NewDBHandle <> nil then
            IB_Connection.dbHandle := NewDBHandle
          else
          if tmpType = 'DROP' then
          begin
            try
              IB_Connection.SysConnect( true );
            except
              Exit;
            end;
            IB_Connection.DropDatabase;
          end
          else
          if tmpType = 'CONNECT' then
            IB_Connection.SysConnect( true )
          else
          if tmpType <> 'DISCONNECT' then
            IB_Connection.SysConnect( true );
        end
        else
        if ExtractDBProp( tmpS + ';', '', [ 'CONNECT',
                                            'DISCONNECT',
                                            'DROP' ], tmpType, tmpStr ) then
        else
          raise EIB_StatementError.CreateWithSender( Self,
                                                    E_INV_CREATE_CONNECT_DROP );
      end
      else if errcode <> 0 then
        HandleException( Self );
    end;
  end else begin
    raise EIB_StatementError.CreateWithSender( Self, E_NO_CONNECTION );
  end;
end;

procedure TIB_Statement.SysExecImmed2( Statement: string;
                                       AParam, AField: PXSQLDA );
begin
  if CheckConnection( true ) then
    if CheckTransaction( true ) then
      API_ExecImmed2( Statement, AParam, AField );
end;

{------------------------------------------------------------------------------}

procedure TIB_Statement.SysPreparedChanged;
begin
  ProcessLinkEvent( setPreparedChanged, 0 );
  ProcessEvent( OnPreparedChanged );
end;

procedure TIB_Statement.SysActiveChange;
begin
  ProcessLinkEvent( setActiveChange, 0 );
  ProcessEvent( OnActiveChange );
end;

procedure TIB_Statement.SysLayoutChanged;
begin
  ProcessLinkEvent( setLayoutChanged, 0 );
  ProcessEvent( OnLayoutChanged );
end;

procedure TIB_Statement.SysStoreParamValueLinks;
var
  ii: integer;
  tmpS: string;
begin
{ Store all current AParam values to old params. }
  if not Assigned( FBindingCursor ) then
  begin
    FOldParamValueLinks.BeginUpdate;
    try
      FOldParamValueLinks.Clear;
      for ii := 0 to ParamCount - 1 do
        with Params[ii] do
          if ( not IsBlob ) and ( not IsArray ) then
          begin
            if IsNull then
              tmpS := ''
            else
              tmpS := BinaryToHexText( PSQLVar.SQLData, DataSize );
            FOldParamValueLinks.Add( BestFieldName + '=' + tmpS );
          end;
    finally
      FOldParamValueLinks.EndUpdate;
    end;
  end;
end;

procedure TIB_Statement.SysRestoreParamValueLinks;
var
  ii, L: integer;
  tmpStr: string;
  tmpInt: integer;
  tmpPrm: TIB_Column;
begin
  for ii := 0 to ParamCount - 1 do
    try
      with Params[ii] do
      begin
        if ( not IsBlob ) and ( not IsArray ) then
        begin
          tmpInt := FOldParamValueLinks.LinkIndex[ BestFieldName ];
          if tmpInt <> -1 then
          begin
            tmpStr := FOldParamValueLinks.IndexValues[ tmpInt ];
            if tmpStr = '' then
              PSQLVar.SQLInd^ := IB_Null
            else
            if Length( tmpStr ) = DataSize * 2 then
            begin
              PSQLVar.SQLInd^ := IB_NotNull;
              HexTextToBinary( tmpStr, PSQLVar.SQLData, DataSize );
            end
            else
            begin
              try
                if IsDateTime and ( tmpStr <> '' ) then
                  AsDateTime := EncodeStringToDateTime( tmpStr )
                else
                begin
                  if IsCurrencyDataType then
                  begin
                    L := Length( tmpStr ) + SQLScale;
                    if L >= 1 then
                      tmpStr[L] := DecimalSeparator;
                  end;
                  AsString := tmpStr;
                end;
              except
                Clear;
              end;
            end;
            FOldParamValueLinks.Delete( tmpInt );
          end;
        end;
      end;
    except
    // Toss any exception here.
    end;
{ Place the values from the ParamValues property into the Params. }
  with ParamValueLinks do
    for ii := 0 to Count - 1 do
    begin
      tmpPrm := FindParam( Names[ ii ] );
      if Assigned( tmpPrm ) then
      begin
        tmpStr := IndexValues[ ii ];
        try
          if tmpPrm.IsDateTime and ( tmpStr <> '' ) then
            tmpPrm.AsDateTime := StrToDateTime( tmpStr )
          else
            tmpPrm.AsString := tmpStr;
        except
        // Toss any exception here.
        end;
      end;
    end;
  Params.SetRowState( rsUnmodified );
end;

procedure TIB_Statement.RetrieveRelAliasList;
begin
  if flag_rel_alias_invalid then
  begin
    GetRelAliasList( SQL.Text, FRelAliasList );
    flag_rel_alias_invalid := false;
  end;
end;

procedure TIB_Statement.RetrieveRelAliasListFB2;
var
  ii: integer;
  idx: integer;
  ran: string;
begin
  for ii := 0 to Pred( Fields.ColumnCount ) do
  begin
    ran := Fields[ii].RelAliasName;
    if ran <> '' then
    begin
      idx := FRelAliasList.LinkIndex[ ran ];
      if idx < 0 then
        FRelAliasList.Add( ran + '=' + Fields[ii].RelName );
    end;
  end;
end;

function TIB_Statement.GetRelNameByRelAlias( const AValue: string ): string;
begin
  //  !!! Must safeguard against case sensitivity and quotes.
  Result := FRelAliasList.LinkValues[ AValue ];
end;

function TIB_Statement.GetRelAliasByRelName(const AValue: String): String;
var
  idx: Integer;
begin
  Result := AValue;
  for idx := 0 to FRelAliasList.Count - 1 do
  begin
  //  !!! Must safeguard against case sensitivity and quotes.
    if FRelAliasList.IndexValues[ idx ] = AValue then
    begin
      Result := FRelAliasList.IndexNames[ idx ];
      Break;
    end;
  end;
  if Result = '' then
    Result := AValue;
end;

procedure TIB_Statement.SysUpdateDescriptors;
begin
  FParams.SysUpdate( FParamCount );
  if not RepreparingSQL then
  begin
    RetrieveRelAliasList;
    FCursorFields.SysUpdate( FCursorFieldCount );
    if Prepared then
    begin
      if IsSelectSQL then
      begin
        FCursorFields.FillRelAliasInfo;
        with IB_Connection.Characteristics do
          if ( dbFBVersion <> '' ) and ( dbServer_Major_Version >= 2 ) then
            RetrieveRelAliasListFB2;
      end;
    end;
  end;
  flag_statement_plan_invalid := true;
end;

procedure TIB_Statement.SysUpdateStatementType;
var
  tmp: array [0..10] of Char;
  TypeBuffer: array [0..50] of Char;
  StmtType: integer;
  StmtLen: integer;
  StmtText: string;
  BegPos,
  EndPos,
  UnionLevel: integer;
begin
  UnionLevel := -1;
  GetSQLSelect( SQL.Text, StmtText, BegPos, EndPos, UnionLevel );
  if StmtText <> '' then
  begin
    GetSQLForUpdate( SQL.Text, StmtText, BegPos, EndPos );
    if StmtText <> '' then
      FStatementType := stSelectForUpdate
    else
      FStatementType := stSelect;
    flag_statement_type_invalid := false;
  end
  else
  begin
    FStatementType := stUnknown;
    if Prepared then
    begin
      FillChar( tmp, SizeOf(tmp), #0 );
      FillChar( TypeBuffer, SizeOf(TypeBuffer), #0 );
      tmp[0] := Char( isc_info_sql_stmt_type );
      API_DSQL_SQL_INFO( tmp, TypeBuffer );
      if TypeBuffer[0] = Char(isc_info_sql_stmt_type) then
      begin
        StmtLen := 0;
        Inc(StmtLen, byte(TypeBuffer[1])    );
        Inc(StmtLen, byte(TypeBuffer[2])*256);
        if StmtLen > 255 then
          raise EIB_StatementError.CreateWithSender( Self, E_BAD_SQL_INF_LEN )
        else
          StmtType := byte(TypeBuffer[3])
      end
      else
        raise EIB_StatementError.CreateWithSender( Self, E_BAD_SQL_INF_DATA );
      Inc( FStatementType, StmtType );
      flag_statement_type_invalid := false;
    end;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Statement.SysParamsStateChanged( Sender: TIB_Row );
begin
  ProcessLinkEvent( setParamsStateChanged, 0 );
end;

procedure TIB_Statement.SysCursorFieldsStateChanged( Sender: TIB_Row );
begin
  SysFieldsStateChanged( Sender )
end;

procedure TIB_Statement.SysFieldsStateChanged( Sender: TIB_Row );
begin
  ProcessLinkEvent( setFieldsStateChanged, 0 );
end;

{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - }

procedure TIB_Statement.SysBeforeParamsDataChange( Sender: TIB_Row;
                                                   AField: TIB_Column);
begin
// Abstract.
end;

procedure TIB_Statement.SysAfterParamsDataChange( Sender: TIB_Row;
                                                  AField: TIB_Column);
begin
  ProcessLinkEvent( setParamsDataChange, integer( AField ));
end;

{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - }

procedure TIB_Statement.SysBeforeCursorFieldDataChange( Sender: TIB_Row;
                                                        AField: TIB_Column);
begin
  SysBeforeFieldDataChange( Sender, AField );
end;

procedure TIB_Statement.SysAfterCursorFieldDataChange( Sender: TIB_Row;
                                                       AField: TIB_Column);
begin
  SysAfterFieldDataChange( Sender, AField );
end;

{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - }

procedure TIB_Statement.SysBeforeFieldDataChange( Sender: TIB_Row;
                                                  AField: TIB_Column);
begin
// Abstract.
end;

procedure TIB_Statement.SysAfterFieldDataChange( Sender: TIB_Row;
                                                 AField: TIB_Column);
begin
  ProcessLinkEvent( setFieldsDataChange, integer( AField ));
end;

{------------------------------------------------------------------------------}

procedure TIB_Statement.ProcessConnectionEvent( AConnectionLink: TIB_ConnectionLink;
                                                AEvent: TIB_ConnectionEventType );
begin
  if Prepared and ( AEvent = cetBeforeAssignment ) then
    Unprepare
  else
  if ( AEvent = cetAfterAssignment ) then
    if Assigned( IB_Connection ) then
      FSQLDialect := IB_Connection.SQLDialect
    else
      FSQLDialect := 3;
end;

procedure TIB_Statement.ProcessTransactionEvent( ATransactionLink: TIB_TransactionLink;
                                                 AEvent: TIB_TransactionEventType );
var
  TL: TIB_TransactionLink;
begin
  if Prepared and ( AEvent = tetBeforeAssignment ) then
  begin
    TL := FIB_TransactionLink;
    if ( not Assigned( TL.FNewTransaction )) or
       ( TL.FNewTransaction.ConnectionIndex[ IB_Connection ] = -1 ) then
      Unprepare;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Statement.SysPrepareSQL;
var
  ii: integer;
begin
  ProcessEvent( OnPrepareSQL );
  for ii := 0 to StatementLinkCount - 1 do
    with TIB_StatementLink( FIB_StatementLinkList.Items[ii] ) do
      SysPrepareSQL;
end;

function TIB_Statement.API_Prepare(     Text: PChar;
                                    var InVar,
                                        OutVar: smallint ): integer;
var
  SaveCW: word;
  procedure PrepareOutDa( VarCnt: word );
  begin
    if IB_Session.FOutCnt < VarCnt then
    begin
      FreeMem( IB_Session.FOutDa );
      IB_Session.FOutDa := AllocMem( XSQLDA_LENGTH( VarCnt ));
      IB_Session.FOutDa.version := SQLDA_VERSION1;
      IB_Session.FOutCnt := VarCnt;
    end;
    IB_Session.FOutDa.sqln := IB_Session.FOutCnt;
  end;
  procedure PrepareInDa( VarCnt: word );
  begin
    if IB_Session.FInCnt < VarCnt then
    begin
      FreeMem( IB_Session.FInDa );
      IB_Session.FInDa := AllocMem( XSQLDA_LENGTH( VarCnt ));
      IB_Session.FInDa.version := SQLDA_VERSION1;
      IB_Session.FInCnt := VarCnt;
    end;
    IB_Session.FInDa.sqln := VarCnt;
  end;
var
  MonitorText: string;
begin
  if Trim( Text ) = EmptyStr then
    raise EIB_StatementError.CreateWithSender( Self, E_NO_BLANK_SQL );
  with IB_Session do
  begin
    if Prepared then
      PrepareOutDa( Fields.ColumnCount )
    else
      PrepareOutDa( FSysFieldNames.Count );
    if PstHandle^ = FakePointer then
      IB_Connection.AllocateStmtHandle( PstHandle );
    asm fstcw [SaveCW] end;
    Result := isc_dsql_prepare( @status,
                                PtrHandle,
                                PstHandle,
                                null_terminated,
                                Text,
                                SQLDialect,
                                IB_Session.FOutDa );
    asm fldcw [SaveCW] end;
    if ( Result = 0 ) and ClientMonitorHooksIn then
    begin
      MonitorText := '//>>> STATEMENT PREPARED <<<//'#13#10 +
                     'TIB_Statement.API_Prepare()'#13#10 +
                     Self.ClassName + ': "';
      if Assigned( Self.Owner ) and ( Self.Owner.Name <> '' ) then
        MonitorText := MonitorText + Self.Owner.Name + '.';
      MonitorText := MonitorText + Self.Name +
                     '" stHandle=' + IntToStr(Integer(PstHandle^));
      OutputToMonitor( MonitorText );
    end;
    OutVar := IB_Session.FOutDa.sqld;
    if ( Result = 0 ) and ( OutVar > IB_Session.FOutDa.sqln ) then
    begin
      PrepareOutDa( OutVar );
      asm fstcw [SaveCW] end;
      Result := isc_dsql_describe( @status,
                                   PstHandle,
                                   SQLDialect,
                                   IB_Session.FOutDa );
      asm fldcw [SaveCW] end;
    end;
    FUnExPrmCnt := 0;
    if ( Result = 0 ) then
    begin
      InVar := FSysParamNames.Count;
      if InVar > 0 then
      begin
        PrepareInDa( InVar );
        asm fstcw [SaveCW] end;
        Result := isc_dsql_describe_bind( @status,
                                          PstHandle,
                                          SQLDialect,
                                          IB_Session.FInDa );
        asm fldcw [SaveCW] end;
     // There is a bug in InterBase that causes problems with input parameters..
     // A Statement of this form will result in 4 parameters instead of 2      .
     //                                                                        .
     // SELECT JOBTYPER.JOBNAVN                                                .
     //      , JOBTYPER.JOBID                                                  .
     //      , ( SELECT SUM(JOB.JOBTID)                                        .
     //          FROM JOB                                                      .
     //          WHERE JOB.JOBID = JOBTYPER.JOBID                              .
     //            AND JOB.START_DATOTID >= :STARTDATO                         .
     //            AND JOB.START_DATOTID <= :SLUTDATO ) AS TID                 .
     // FROM JOBTYPER                                                          .
     // ORDER BY 3                                                             .
     //                                                                        .
        if ( Result = 0 ) then
        begin
          if IB_Session.FInDa.sqld > InVar then
          begin
            FUnExPrmCnt := IB_Session.FInDa.sqld - InVar;
            InVar := IB_Session.FInDa.sqld;
            if ( IB_Session.FInDa.sqln < IB_Session.FInDa.sqld ) then
            begin
              PrepareInDa( InVar );
              asm fstcw [SaveCW] end;
              Result := isc_dsql_describe_bind( @status,
                                                PstHandle,
                                                SQLDialect,
                                                IB_Session.FInDa );
              asm fldcw [SaveCW] end;
            end;
          end;
        end;
      end;
    end;
  end;
end;

procedure TIB_Statement.SysPrepareFailed;
begin
  if not RepreparingSQL then
  begin
    FParamCount := 0;
    FCursorFieldCount := 0;
    FPrepared := false;
    FParams.SysPrepareFailed;
    FCursorFields.SysPrepareFailed;
  end;
  FSysParamNames.Clear;
  FSysFieldNames.Clear;
  FSysTableNames.Clear;
end;

procedure TIB_Statement.API_Execute;
var
  In_DA: PXSQLDA;
  SaveCW: word;
begin
  if ParamCount > 0 then
    In_DA := FParams.PSQLDA
  else
    In_DA := nil;
  with IB_Session do
  begin
    asm fstcw [SaveCW] end;
    errcode := isc_dsql_execute( @status,
                                 PtrHandle,
                                 PstHandle,
                                 SQLDialect,
                                 In_DA );
    asm fldcw [SaveCW] end;
    if ( errcode <> 0 ) then
    begin
      if ( errcode = isc_stream_eof ) and
         ( TreatSingletonEmptySetAsEof ) and
         ( StatementType in [ stSelect, stSelectForUpdate ] ) then
        errcode := 100
      else
        HandleException( Self );
    end;
  end;
  flag_rows_affected_invalid := true;
  FWasSingleton := false;
end;

procedure TIB_Statement.API_Execute2;
begin
  with IB_Session do
  begin
    errcode := API_QuickFetch( true );
    if ( errcode <> 0 ) then
    begin
      if ( errcode = isc_stream_eof ) and
         ( TreatSingletonEmptySetAsEof ) and
         ( StatementType in [ stSelect, stSelectForUpdate ] ) then
        errcode := 100
      else
        HandleException( Self );
    end;
  end;
end;

function TIB_Statement.API_QuickFetch( Exec2: boolean ): integer;
var
  In_DA,
  Out_DA: PXSQLDA;
  SaveCW: word;
  tmperr: longint;
  tmpstatus: status_vector;

  procedure DoTheFetch;
  begin
    with IB_Session do
    begin
      if Exec2 then
        errcode := isc_dsql_execute2( @status,
                                      PtrHandle,
                                      PstHandle,
                                      SQLDialect,
                                      In_DA,
                                      Out_DA )
      else
        errcode := isc_dsql_row_fetch2( @status,
                                        PtrHandle,
                                        PstHandle,
                                        SQLDialect,
                                        In_DA,
                                        Out_DA );
// There seems to be a bug where a cursor on the server is not getting closed.
// Or, it could be getting opened when it should not be.
// Added code below to attempt to avoid/recover.
      if ( errcode = 0 ) and Assigned( Out_DA ) then
        isc_dsql_free_row_statement( @status, PstHandle, DSQL_CLOSE );
    end;
  end;

var
  MonitorText: string;
begin
  flag_rows_affected_invalid := true;
  if ParamCount > 0 then
    In_DA := Params.PSQLDA
  else
    In_DA := nil;
  if FieldCount > 0 then
    Out_DA := Fields.PSQLDA
  else
    Out_DA := nil;
  asm fstcw [SaveCW] end;
  DoTheFetch;
  asm fldcw [SaveCW] end;
  if Assigned( Out_DA ) then with IB_Session do
// There seems to be a bug where the server is losing the compile.
// Added code below to attempt to recover.
    if ( errcode = isc_dsql_error ) or
       ( errcode = isc_port_len ) then
    begin
      if ClientMonitorHooksIn then
      begin
        MonitorText := '//>>> STATEMENT PREPARED <<<//'#13#10 +
                       'TIB_Statement.API_QuickFetch()'#13#10 +
                       'Recovery from lost server compile needed.'#13#10 +
                       Self.ClassName + ': "';
        if Assigned( Self.Owner ) and ( Self.Owner.Name <> '' ) then
          MonitorText := MonitorText + Self.Owner.Name + '.';
        MonitorText := MonitorText + Self.Name +
            '" stHandle=' + IntToStr(Integer(PstHandle^)) + ' #QF';
        OutputToMonitor( MonitorText );
      end;
      if not StoredProcHasDML then
      begin
        asm fstcw [SaveCW] end;
        tmperr := isc_dsql_prepare( @tmpstatus,
                                    PtrHandle,
                                    PstHandle,
                                    null_terminated,
                                    PChar(ServerSQL),
                                    SQLDialect,
                                    Out_DA );
        asm fldcw [SaveCW] end;
        if tmperr = 0 then
        begin
          asm fstcw [SaveCW] end;
          DoTheFetch;
          asm fldcw [SaveCW] end;
        end;
      end;
    end;
  Result := IB_Session.errcode;
  FWasSingleton := true;
end;

procedure TIB_Statement.API_DSQL_SQL_INFO( var Items:  array of Char;
                                           var Buffer: array of Char );
var
  SaveCW: word;
begin
  with IB_Session do
  begin
    asm fstcw [SaveCW] end;
    errcode := isc_dsql_sql_info( @status,
                                  PstHandle,
                                  SizeOf( Items ),
                                  Items,
                                  SizeOf( Buffer ),
                                  @Buffer );
    asm fldcw [SaveCW] end;
    if errcode <> 0 then HandleException( Self );
  end;
end;

procedure TIB_Statement.API_ExecuteImmediate( const Statement: string;
                                                    AParam: PXSQLDA );
var
  ii: integer;
  WasStarted: boolean;
  SaveCW: word;
begin
  WasStarted := Assigned( IB_Transaction ) and IB_Transaction.Started;
  try
    with IB_Session do
    begin
      if not Assigned( isc_dsql_execute_immediate ) then
        RevertToOriginalHooks; // does an acquire of hooks if necessary
      asm fstcw [SaveCW] end;
      errcode := isc_dsql_execute_immediate( @status,
                                             PdbHandle,
                                             PtrHandle,
                                             Length(Statement),
                                             PChar(Statement),
                                             SQLDialect,
                                             AParam );
      asm fldcw [SaveCW] end;
      if errcode <> 0 then
        API_CheckStatement( Statement, errcode );
      if errcode <> 0 then
        HandleException( Self );
    end;
  finally
    if WasStarted and
       Assigned( IB_Transaction) and ( not IB_Transaction.Started ) then
      for ii := 0 to IB_Transaction.ConnectionCount - 1 do
        Dec( IB_Transaction.Connections[ ii ].FStartedTransactionCount );
  end;
end;

procedure TIB_Statement.API_ExecImmed2( const AStatement: string;
                                              AParam,
                                              AField: PXSQLDA );
var
  ii: integer;
  WasStarted: boolean;
  SaveCW: word;
begin
  WasStarted := Assigned( IB_Transaction ) and IB_Transaction.Started;
  try
    with IB_Session do
    begin
      asm fstcw [SaveCW] end;
      errcode := ISC_ExecImmed2( AStatement, AParam, AField );
      asm fldcw [SaveCW] end;
      if errcode <> 0 then
        API_CheckStatement( AStatement, errcode );
      if errcode <> 0 then
        HandleException( Self );
    end;
  finally
    if WasStarted and
       Assigned( IB_Transaction) and ( not IB_Transaction.Started ) then
      for ii := 0 to IB_Transaction.ConnectionCount - 1 do
        Dec( IB_Transaction.Connections[ ii ].FStartedTransactionCount );
  end;
end;

function TIB_Statement.ISC_ExecImmed2( const AStatement: string;
                                             AParam,
                                             AField: PXSQLDA ): integer;

var
  immed2_stmt_handle: isc_stmt_handle;
  SaveCW: word;
  SQLDlct: word;
  MonitorText: string;
begin
  with IB_Session do
  begin
//    There is a bug in IB's API. Cannot use the call below with WIN NT server!
{
  errcode := isc_dsql_exec_immed2( @status,
                                   PdbHandle,
                                   PtrHandle,
                                   Length(AStatement),
                                   PChar(AStatement),
                                   SQLDA_VERSION1,
                                   AParam,
                                   AField );
  if errcode <> 0 then begin
    HandleException( Self );
  end;
}

// Initialize
    immed2_stmt_handle := nil;
    Result := 0;

// Allocate
    if Assigned( IB_Connection ) then
    begin
      IB_Connection.AllocateStmtHandle( @immed2_stmt_handle );
      SQLDlct := IB_Connection.SQLDialect;
    end
    else
      SQLDlct := SQL_DIALECT_V5;
    try

      asm fstcw [SaveCW] end;
// Prepare
      errcode := isc_dsql_prepare( @status,
                                   PtrHandle,
                                   @immed2_stmt_handle,
                                   Length(AStatement),
                                   pchar(AStatement),
                                   SQLDlct,
                                   AField );

      //ADDED// CW 2000-06-06
      if ( errcode = 0 ) and ClientMonitorHooksIn then begin
        MonitorText :=
            '//>>> STATEMENT PREPARED <<<//'#13#10 +
            'TIB_Statement.ISC_ExecImmed2()'#13#10 +
            Self.ClassName + ': "';
        if Assigned( Self.Owner ) and ( Self.Owner.Name <> '' ) then
          MonitorText := MonitorText + Self.Owner.Name + '.';
        MonitorText := MonitorText + Self.Name +
            '" stHandle=' + IntToStr(Integer(immed2_stmt_handle)) + ' #EI';
        OutputToMonitor( MonitorText );
      end;
      //ENDADDED//
// Execute
      if errcode = 0 then
        errcode := isc_dsql_execute2( @status,
                                      PtrHandle,
                                      @immed2_stmt_handle,
                                      SQLDlct,
                                      AParam,
                                      AField );
      Result := errcode;
      asm fldcw [SaveCW] end;

    finally

// Deallocate
      if Assigned( IB_Connection ) then
        IB_Connection.DeallocateStmtHandle( @immed2_stmt_handle );

      if errcode <> 0 then
        if Result = 0 then
          Result := errcode;
    end;
  end; { with IB_Session }
end;

procedure TIB_Statement.API_CheckStatement( const AStatement: string;
                                              var errcode: longint );
var
  tmpStr: string;
  tmpType: string;
begin
  if ( errcode = isc_dsql_error ) and Assigned( IB_Connection ) then
    if ExtractDBProp( AStatement, 'DATABASE', [ 'CONNECT',
                                                'DISCONNECT',
                                                'DROP' ], tmpType, tmpStr ) then
    begin
      errcode := 0;
      if tmpType = 'CONNECT' then
      begin
        if ( tmpStr <> '' ) and
           ( tmpStr <> IB_Connection.Database ) then
        begin
          IB_Connection.Disconnect;
          if SetConnectionProperties( IB_Connection, AStatement, tmpStr ) then
            IB_Connection.SysConnect( true );
        end;
      end
      else
      if tmpType = 'DISCONNECT' then
        IB_Connection.Disconnect
      else
      if tmpType = 'DROP' then
        IB_Connection.DropDatabase;
    end;
end;

{                                                                              }
{   Property Access Methods                                                    }
{                                                                              }

procedure TIB_Statement.SetConnection( AValue: TIB_Connection );
begin
  if IB_Connection <> AValue then
  begin
    if Assigned( IB_Connection ) then
    begin
      if IB_Transaction = GetDefaultTransaction then
        IB_Transaction := nil;
      IB_Connection.FStatementList.Remove( Self );
    end;
    FIB_ConnectionLink.IB_Connection := AValue;
    try
      if Assigned( FIB_Transaction ) and
         ( IB_Transaction = FIB_Transaction ) then
        FIB_Transaction.IB_Connection := IB_Connection;
      if Assigned( IB_Connection ) then
        IB_Connection.FStatementList.Add( Self );
    except
      FIB_ConnectionLink.IB_Connection := nil;
    end;
  end;
end;

function TIB_Statement.GetConnection: TIB_Connection;
begin
  Result := FIB_ConnectionLink.IB_Connection;
end;

procedure TIB_Statement.SetTransaction( AValue: TIB_Transaction );
begin
  if IB_Transaction <> AValue then
  begin
    if IB_Transaction <> nil then
    begin
      if ( AValue = nil ) or
         ( IB_Transaction.IB_Connection <>
           AValue.IB_Connection ) then
        Unprepare;
      IB_Transaction.FStatementList.Remove( Self );
    end;
    FIB_TransactionLink.IB_Transaction := AValue;
    try
      if ( FIB_Transaction <> nil ) and
         ( FIB_Transaction <> AValue ) then
      begin
        FIB_Transaction.Free;
        FIB_Transaction := nil;
      end;
      if IB_Transaction <> nil then
        IB_Transaction.FStatementList.Add( Self );
    except
      FIB_TransactionLink.IB_Transaction := nil;
    end;
  end;
end;

function TIB_Statement.GetTransaction: TIB_Transaction;
begin
  if Assigned( FIB_TransactionLink ) then
    Result := FIB_TransactionLink.IB_Transaction
  else
    Result := nil;
  if ( csWriting in ComponentState ) and
     (( Result = FIB_Transaction ) or
      ( Result = GetDefaultTransaction ) ) then
    Result := nil;
end;

function TIB_Statement.GetDefaultConnection: TIB_Connection;
begin
  Result := IB_Session.DefaultConnection;
end;

function TIB_Statement.GetDefaultTransaction: TIB_Transaction;
begin
  if Assigned( IB_Connection ) then
    Result := IB_Connection.FDefaultTransaction
  else
    Result := nil;
end;

function TIB_Statement.GetStatementLinkCount: integer;
begin
// Leave this check here.
  if Assigned( FIB_StatementLinkList ) then
    Result := FIB_StatementLinkList.Count
  else
    Result := 0;
end;

function TIB_Statement.GetSQL: TStrings;
begin
  Result := FSQL;
end;

procedure TIB_Statement.SetSQL( AValue: TStrings );
begin
  if AValue is TIB_SQLStrings then
    FSQL.Assign( AValue )
  else
  if Assigned( AValue ) then
  // Do not use Assign() here!
    FSQL.Text := AValue.Text
  else
    FSQL.Clear;
end;

procedure TIB_Statement.OnSQLChange( Sender: TObject );
var
  UnprepareSQL: boolean;
begin
  UnprepareSQL := ( not PreparingSQL ) and ( not AssigningSQL );
  SysSQLChange( Sender, UnprepareSQL );
  if UnprepareSQL then
  begin
    if Prepared then
      SysUnprepare
    else
    begin
      FSysParamNames.Clear;
      FSysFieldNames.Clear;
      FSysTableNames.Clear;
    end;
    flag_statement_type_invalid := true;
    flag_rel_alias_invalid := true;
  end;
end;

procedure TIB_Statement.SysSQLChange( Sender: TObject; var Unprepare: boolean );
begin
// Abstract at this level.
end;

procedure TIB_Statement.HintsChange( Sender: TObject );
begin
  if Prepared then
    ProcessLinkEvent( setHintsChanged, 0 );
end;

procedure TIB_Statement.LayoutChange( Sender: TObject );
begin
  if Prepared then
    SysLayoutChange( Sender );
  if FIgnoreLayoutChange = 0 then
    SysLayoutChanged;
end;

procedure TIB_Statement.SysLayoutChange( Sender: TObject );
begin
  if FIgnoreLayoutChange = 0 then
  begin
    Inc( FIgnoreLayoutChange );
    try
      if Sender = FieldsAlignment then
        UpdateAlignment
      else
      if Sender = FieldsCharCase then
        UpdateCharCase
      else
      if Sender = FieldsDisplayFormat then
        UpdateDisplayFormat
      else
      if Sender = FieldsDisplayLabel then
        UpdateDisplayLabel
      else
      if Sender = FieldsGridLabel then
        UpdateGridLabel
      else
      if Sender = FieldsGridTitleHint then
        UpdateGridTitleHint
      else
      if Sender = FieldsDisplayWidth then
        UpdateDisplayWidth
      else
      if Sender = FieldsEditMask then
        UpdateEditMask
      else
      if Sender = FieldsIndex then
        UpdateIndex
      else
      if Sender = FieldsReadOnly then
        UpdateReadOnly
      else
      if Sender = FieldsTrimming then
        UpdateTrimming
      else
      if Sender = FieldsVisible then
        UpdateVisible;
    finally
      Dec( FIgnoreLayoutChange );
    end;
    if Assigned( FBindingCursor ) and
       ( FBindingCursor.FIgnoreLayoutChange = 0 ) then
    begin
      Inc( FBindingCursor.FIgnoreLayoutChange );
      try
        if Sender = FieldsAlignment then
          FBindingCursor.FieldsAlignment.Assign(FieldsAlignment)
        else
        if Sender = FieldsCharCase then
          FBindingCursor.FieldsCharCase.Assign(FieldsCharCase)
        else
        if Sender = FieldsDisplayFormat then
          FBindingCursor.FieldsDisplayFormat.Assign(FieldsDisplayFormat)
        else
        if Sender = FieldsDisplayLabel then
          FBindingCursor.FieldsDisplayLabel.Assign(FieldsDisplayLabel)
        else
        if Sender = FieldsGridLabel then
          FBindingCursor.FieldsGridLabel.Assign(FieldsGridLabel)
        else
        if Sender = FieldsGridTitleHint then
          FBindingCursor.FieldsGridTitleHint.Assign(FieldsGridTitleHint)
        else
        if Sender = FieldsDisplayWidth then
          FBindingCursor.FieldsDisplayWidth.Assign(FieldsDisplayWidth)
        else
        if Sender = FieldsEditMask then
          FBindingCursor.FieldsEditMask.Assign(FieldsEditMask)
        else
        if Sender = FieldsIndex then
          FBindingCursor.FieldsIndex.Assign(FieldsIndex)
        else
        if Sender = FieldsReadOnly then
          FBindingCursor.FieldsReadOnly.Assign(FieldsReadOnly)
        else
        if Sender = FieldsTrimming then
          FBindingCursor.FieldsTrimming.Assign(FieldsTrimming)
        else
        if Sender = FieldsVisible then
          FBindingCursor.FieldsVisible.Assign(FieldsVisible);
      finally
        Dec( FBindingCursor.FIgnoreLayoutChange );
      end;
    end;
  end;
end;

procedure TIB_Statement.SetPrepared( Value: boolean );
begin
  if Value <> Prepared then
    if Value then
      Prepare
    else
      Unprepare;
end;

procedure TIB_Statement.SetActive( Value: boolean );
begin
  if Value <> Active then
    if Value then
      SysExecute
    else
      SysClose;
end;

function TIB_Statement.GetActive: boolean;
begin
  Result := FActive;
end;

function TIB_Statement.GetPdbHandle: pisc_db_handle;
begin
  if Assigned( IB_Connection ) then
    Result := IB_Connection.PdbHandle
  else
    Result := nil;
end;

function TIB_Statement.GetPtrHandle: pisc_tr_handle;
begin
  if Assigned( IB_Transaction ) then
    Result := IB_Transaction.PtrHandle
  else
    Result := nil;
end;

function TIB_Statement.GetPstHandle: pisc_stmt_handle;
begin
  Result := @FstHandle;
end;

function TIB_Statement.GetStatementType;
begin
  if flag_statement_type_invalid then
    try
      SysUpdateStatementType;
    except
      on E: Exception do
      begin
        FStatementType := stUnknown;
        Application.HandleException( E );
      end;
    end;
  Result := FStatementType;
end;

function TIB_Statement.GetRowsAffected;
begin
  if flag_rows_affected_invalid then
  begin
    try
      FRowsAffected := IB_Session.GetRowsAffected( PstHandle );
      flag_rows_affected_invalid := false;
    except
      on E: Exception do
      begin
        with FRowsAffected do
        begin
          UpdateCount := -1;
          DeleteCount := -1;
          SelectCount := -1;
          InsertCount := -1;
        end;
        Application.HandleException( E );
      end;
    end;
  end;
  case StatementType of
    stUpdate: Result := FRowsAffected.UpdateCount;
    stDelete: Result := FRowsAffected.DeleteCount;
    stInsert: Result := FRowsAffected.InsertCount;
    stSelect,
    stSelectForUpdate: Result := FRowsAffected.SelectCount;
    else Result := -1;
  end;
end;

function TIB_Statement.GetRowsSelected;
begin
  if flag_rows_affected_invalid then begin
    try
      FRowsAffected := IB_Session.GetRowsAffected( PstHandle );
      flag_rows_affected_invalid := false;
    except
      on E: Exception do
      begin
        with FRowsAffected do
        begin
          UpdateCount := -1;
          DeleteCount := -1;
          SelectCount := -1;
          InsertCount := -1;
        end;
        Application.HandleException( E );
      end;
    end;
  end;
  Result := FRowsAffected.SelectCount;
end;

function TIB_Statement.GetStatementPlan;
begin
  if flag_statement_plan_invalid then
  begin
    if Prepared then
      FStatementPlan := GetStatementPlanFromHandle( PstHandle )
    else
      FStatementPlan := 'Unprepared statement';
    flag_statement_plan_invalid := false;
  end;
  Result := FStatementPlan;
end;

function TIB_Statement.Gen_ID( const AGenerator: string;
                                     Increment: integer ): ISC_INT64;
begin
  Result := GeneratorValue( AGenerator, Increment );
end;

function TIB_Statement.GeneratorValue( const AGenerator: string;
                                             Increment: integer ): ISC_INT64;
begin
  CheckConnection( true );
  CheckTransaction( true );
  if Assigned( IB_Connection ) and
     ( not GetGeneratorValue( IB_Connection,
                              IB_Transaction,
                              IB_Connection.mkIdent( AGenerator ),
                              Increment,
                              Result )) then
    raise EIB_StatementError.CreateWithSender( Self, E_GeneratorFailed );
end;

{------------------------------------------------------------------------------}

procedure TIB_Statement.DefineProperties( Filer: TFiler );
begin
  inherited DefineProperties(Filer);
  Filer.DefineProperty( 'ParamValues',
                        ReadOldParamData,
                        WriteOldParamData,
                        ( ParamCount > 0 ) or
                        ( FOldParamValueLinks.Count > 0 ));
end;

procedure TIB_Statement.WriteOldParamData( Writer: TWriter );
var
  ii: integer;
  tmpS: string;
begin
  with Writer do
  begin
    WriteListBegin;
    try
      if ParamCount > 0 then
      begin
        for ii := 0 to ParamCount - 1 do
          with Params[ii] do
            if ( not IsBlob ) and ( not IsArray ) then
            begin
              if IsNull then
                tmpS := ''
              else
                tmpS := BinaryToHexText( PSQLVar.SQLData, DataSize );
              WriteString( FullFieldName + '=' + tmpS );
            end;
      end
      else
      if FOldParamValueLinks.Count > 0 then
        for ii := 0 to FOldParamValueLinks.Count - 1 do
          WriteString( FOldParamValueLinks[ii] );
    finally
      WriteListEnd;
    end;
  end;
end;

procedure TIB_Statement.ReadOldParamData(Reader: TReader);
begin
  FOldParamValueLinks.Clear;
  with Reader do
  begin
    ReadListBegin;
    try
      while not EndOfList do
        FOldParamValueLinks.Add( ReadString );
    finally
      ReadListEnd;
    end;
  end;
end;

procedure TIB_Statement.ProcessEvent( AEvent: TIB_StatementEvent );
begin
  if Assigned( AEvent ) and
     (( not ( csLoading    in ComponentState )) and
      ( not ( csFixups     in ComponentState )) and
      ( not ( csDestroying in ComponentState ))) then
    AEvent( Self );
end;

procedure TIB_Statement.ProcessLinkEvent( AEvent: TIB_StatementEventType;
                                          Info: integer );
var
  ii: integer;
begin
  if AEvent = setAfterExecDDL then
    for ii := 0 to CursorFields.ColumnCount - 1 do
      CursorFields[ii].FColInfoValid := false;
  for ii := 0 to StatementLinkCount - 1 do
    if ii < FIB_StatementLinkList.Count then
      with TIB_StatementLink(FIB_StatementLinkList.Items[ii]) do
        ProcessEvent( AEvent, Info );
end;

function TIB_Statement.IsConnectionStored: boolean;
begin
  Result := Assigned( IB_Connection );
//  if Result and IB_Session.AllowDefaultConnection then
//     Result := not UsingDefaultConnection;
end;

function TIB_Statement.IsTransactionStored: boolean;
begin
  Result := Assigned( IB_Transaction ) and
            ( IB_Transaction <> FIB_Transaction );
  if Result and Assigned( IB_Connection ) then
    Result := IB_Transaction <> IB_Connection.DefaultTransaction;
//  if Result and IB_Session.AllowDefaultTransaction then
//    Result := not UsingDefaultTransaction;
end;

function TIB_Statement.UsingDefaultConnection: boolean;
begin
  Result := ( IB_Connection = GetDefaultConnection );
end;

function TIB_Statement.UsingDefaultTransaction: boolean;
begin
  Result := ( IB_Transaction = GetDefaultTransaction );
end;

function TIB_Statement.GetDomainName( const ARelName,
                                            ASQLName: string ): string;
begin
  Result := '';
  if Assigned( IB_Connection ) and
     ( RetrieveDomainNames ) and
     ( IsSelectSQL ) and
     ( not ( SQLIsSelectProc )) then
    Result := IB_Connection.SchemaCache.GetDomainName( ARelName, ASQLName );
end;

procedure TIB_Statement.UpdateDesigner;
begin
  if ( csDesigning in ComponentState ) and
     ( not ( csUpdating in ComponentState )) and
     ( Owner is TForm ) then
    with Owner as TForm do
      if Assigned( Designer ) then
        Designer.Modified;
end;

function TIB_Statement.CreateBlobStream( AColumn: TIB_Column;
                                    AMode: TIB_BlobStreamMode ): TIB_BlobStream;
begin
  Result := TIB_BlobStream.CreateForColumn( AColumn, AMode );
end;

function TIB_Statement.GetColAttributeParams( const ACol,
                                                    AParam: string ): string;
begin
  Result := ColumnAttributes.LinkParamValue[ ACol, AParam ];
  if ( Result = '' ) and Assigned( IB_Connection ) then
    with IB_Connection do
      Result := ColumnAttributes.LinkParamValue[ ACol, AParam ];
end;

procedure TIB_Statement.SetColAttributeParams( const ACol, AParam: string;
                                                     AValue: string );
begin
  ColumnAttributes.LinkParamValue[ ACol, AParam ] := AValue;
end;

function TIB_Statement.GetColIsAttributeSet( const ACol,
                                                   AParam: string ): boolean;
begin
  Result := ColumnAttributes.LinkParamIsSet[ ACol, AParam ];
  if ( not Result ) and Assigned( IB_Connection ) then
    with IB_Connection do
      if ( ColumnAttributes.Count > 0 ) then
        Result := ColumnAttributes.LinkParamIsSet[ ACol, AParam ];
end;

procedure TIB_Statement.SetColIsAttributeSet( const ACol, AParam: string;
                                                    AValue: boolean );
begin
  ColumnAttributes.LinkParamIsSet[ ACol, AParam ] := AValue;
end;

function TIB_Statement.GetMacroBegin: string;
begin
  if Assigned(IB_Connection) then
  begin
    if FMacroBegin = '' then
      Result := IB_Connection.MacroBegin
    else
      Result := FMacroBegin;
  end
  else
    Result := FMacroBegin;
end;

function TIB_Statement.GetMacroEnd: string;
begin
  if Assigned(IB_Connection) then
  begin
    if FMacroEnd = '' then
      Result := IB_Connection.MacroEnd
    else
      Result := FMacroEnd;
  end
  else
    Result := FMacroEnd;
end;

procedure TIB_Statement.SetMacroBegin( AValue: string );
begin
  if (AValue <> FMacroBegin) then
  begin
    if Assigned(IB_Connection) then
    begin
      if AValue = IB_Connection.MacroBegin then
        FMacroBegin := ''
      else
        FMacroBegin := Trim(AValue);
    end
    else
      FMacroBegin := Trim(AValue);
  end;
end;

procedure TIB_Statement.SetMacroEnd( AValue: string );
begin
  if (AValue <> FMacroEnd) then
  begin
    if Assigned(IB_Connection) then
    begin
      if AValue = IB_Connection.MacroEnd then
        FMacroEnd := ''
      else
        FMacroEnd := Trim(AValue);
    end
    else
      FMacroEnd := Trim(AValue);
  end;
end;

function TIB_Statement.IsMacroBeginStored: boolean;
begin
  Result := (FMacroBegin <> '');
  if Result and Assigned(IB_Connection) then
  begin
    Result := (FMacroBegin <> IB_Connection.MacroBegin)
  end;
end;

function TIB_Statement.IsMacroEndStored: boolean;
begin
  Result := (FMacroEnd <> '');
  if Result and Assigned(IB_Connection) then
  begin
    Result := (FMacroEnd <> IB_Connection.MacroEnd)
  end;
end;

{------------------------------------------------------------------------------}

function TIB_SQLStrings.GetBegPos( AVal: integer ): integer;
begin Result := FBegPos[ AVal ]; end;
function TIB_SQLStrings.GetEndPos( AVal: integer ): integer;
begin Result := FEndPos[ AVal ]; end;

procedure TIB_SQLStrings.Changing;
var
  ii: integer;
begin
  inherited Changing;
  for ii := ssLowSQLSection to ssHighSQLSection do
  begin
    FBegPos[ii] := ParseLineInvalid;
    FEndPos[ii] := ParseLineInvalid;
  end;
end;

function TIB_SQLStrings.GetSQLSection( Index: integer ): string;
var
  tmpBeg,
  tmpEnd,
  UnionLevel: integer;
begin
  if BegPos[ Index ] = ParseLineInvalid then
  begin
    tmpBeg := ParseLineInvalid;
    tmpEnd := ParseLineInvalid;
    UnionLevel := -1;
    case Index of
      ssSelect:    GetSQLSelect   ( Text, Result, tmpBeg, tmpEnd, UnionLevel );
      ssFrom:      GetSQLFrom     ( Text, Result, tmpBeg, tmpEnd, UnionLevel );
      ssWhere:     GetSQLWhere    ( Text, Result, tmpBeg, tmpEnd, UnionLevel );
      ssGroup:     GetSQLGroup    ( Text, Result, tmpBeg, tmpEnd );
      ssHaving:    GetSQLHaving   ( Text, Result, tmpBeg, tmpEnd );
      ssUnion:     GetSQLUnion    ( Text, Result, tmpBeg, tmpEnd, UnionLevel );
      ssPlan:      GetSQLPlan     ( Text, Result, tmpBeg, tmpEnd, UnionLevel );
      ssOrder:     GetSQLOrder    ( Text, Result, tmpBeg, tmpEnd );
      ssForUpdate: GetSQLForUpdate( Text, Result, tmpBeg, tmpEnd );
    end;
    if tmpBeg = ParseLineInvalid then
    begin
      FBegPos[ Index ] := ParseLineAbsent;
      FEndPos[ Index ] := ParseLineAbsent;
    end
    else
    begin
      FBegPos[ Index ] := tmpBeg;
      FEndPos[ Index ] := tmpEnd;
    end;
  end
  else
  begin
    SubStrings( Text, Result, FBegPos[ Index ], FEndPos[ Index ] );
    Result := Trim( Result );
  end;
end;

function TIB_SQLStrings.GetUnionSection( Index, UnionLevel: integer ): string;
begin
end;

procedure TIB_SQLStrings.Assign( APersistent: TPersistent );
var
  TmpSQLStrings: TIB_SQLStrings absolute APersistent;
begin
  FStatement.Prepared := false;
  if APersistent is TIB_SQLStrings then
  begin
    with TmpSQLStrings.FStatement do
    begin
      if Prepared then
        SysStoreParamValueLinks;
      FStatement.FOldParamValueLinks.Assign( FOldParamValueLinks );
    end;
    if TmpSQLStrings.FStatement is TIB_Dataset then
    begin
      with TmpSQLStrings.FStatement as TIB_Dataset do
      begin
        if SQLForUpdate.Count > 0 then
          FRequestLive := true;
      end;
    end;
  end;
  if not Assigned( APersistent ) then
    Clear
  else
    inherited Assign( APersistent );
  if ( APersistent is TIB_SQLStrings ) then
  begin
    with TmpSQLStrings.FStatement do
    begin
      FStatement.CalculatedFields.Assign(CalculatedFields);
      FStatement.ColumnAttributes.Assign(ColumnAttributes);
      FStatement.FieldsAlignment.Assign(FieldsAlignment);
      FStatement.FieldsCharCase.Assign(FieldsCharCase);
      FStatement.FieldsDisplayFormat.Assign(FieldsDisplayFormat);
      FStatement.FieldsDisplayLabel.Assign(FieldsDisplayLabel);
      FStatement.FieldsGridLabel.Assign(FieldsGridLabel);
      FStatement.FieldsGridTitleHint.Assign(FieldsGridTitleHint);
      FStatement.FieldsDisplayWidth.Assign(FieldsDisplayWidth);
      FStatement.FieldsEditMask.Assign(FieldsEditMask);
      FStatement.FieldsIndex.Assign(FieldsIndex);
      FStatement.FieldsReadOnly.Assign(FieldsReadOnly);
      FStatement.FieldsTrimming.Assign(FieldsTrimming);
      FStatement.FieldsVisible.Assign(FieldsVisible);
      FStatement.OnMacroSubstitute := OnMacroSubstitute;
      FStatement.OnCalculateField := OnCalculateField;
    end;
  end
  else
  begin
    with FStatement do
    begin
      CalculatedFields.Clear;
      ColumnAttributes.Clear;
      FieldsAlignment.Clear;
      FieldsCharCase.Clear;
      FieldsDisplayFormat.Clear;
      FieldsDisplayLabel.Clear;
      FieldsGridLabel.Clear;
      FieldsGridTitleHint.Clear;
      FieldsDisplayWidth.Clear;
      FieldsEditMask.Clear;
      FieldsIndex.Clear;
      FieldsReadOnly.Clear;
      FieldsTrimming.Clear;
      FieldsVisible.Clear;
// JLW - Putting this here is not a good idea.
//       People's setting are getting wiped out when the SQL.Assign() is called.
//       Seems the rule of thumb here is to avoid things the IB_Script component
//       will need in this area where settings are cleared out.
//      OnMacroSubstitute := nil;
    end;
  end;
  if FStatement is TIB_Dataset then
  begin
    if ( APersistent is TIB_SQLStrings ) and
       ( TmpSQLStrings.FStatement is TIB_Dataset ) then
    begin
      with TmpSQLStrings.FStatement as TIB_Dataset do
      begin
        TIB_Dataset(FStatement).GeneratorLinks.Assign(GeneratorLinks);
        TIB_Dataset(FStatement).DefaultValues.Assign(DefaultValues);
        TIB_Dataset(FStatement).GetServerDefaults := GetServerDefaults;
        TIB_Dataset(FStatement).Hints.Assign(Hints);
        TIB_Dataset(FStatement).JoinLinks.Assign(JoinLinks);
        TIB_Dataset(FStatement).KeyLinks.Assign(KeyLinks);
        TIB_Dataset(FStatement).KeyDescLinks.Assign(KeyDescLinks);
        TIB_Dataset(FStatement).MasterLinks.Assign(MasterLinks);
        TIB_Dataset(FStatement).MasterSearch := MasterSearch;
        TIB_Dataset(FStatement).OrderingItems.Assign(OrderingItems);
        TIB_Dataset(FStatement).OrderingItemNo := OrderingItemNo;
        TIB_Dataset(FStatement).RequestLive := RequestLive;
        TIB_Dataset(FStatement).SQLWhereLow.Assign(SQLWhereLow);
        TIB_Dataset(FStatement).SQLWhereMed.Assign(SQLWhereMed);
        TIB_Dataset(FStatement).SQLWhereHigh.Assign(SQLWhereHigh);
        TIB_Dataset(FStatement).ParamValueLinks.Assign(ParamValueLinks);
        TIB_Dataset(FStatement).CallbackInc := CallbackInc;
        TIB_Dataset(FStatement).CallbackInitInt := CallbackInitInt;
        TIB_Dataset(FStatement).CallbackRefreshInt := CallbackRefreshInt;
        TIB_Dataset(FStatement).OnCallback := OnCallback;
        TIB_Dataset(FStatement).EditSQL.Assign(EditSQL);
        TIB_Dataset(FStatement).LockSQL.Assign(LockSQL);
        TIB_Dataset(FStatement).InsertSQL.Assign(InsertSQL);
        TIB_Dataset(FStatement).DeleteSQL.Assign(DeleteSQL);
        TIB_Dataset(FStatement).PreparedEdits := PreparedEdits;
        TIB_Dataset(FStatement).SearchedDeletes := SearchedDeletes;
        TIB_Dataset(FStatement).SearchedEdits := SearchedEdits;
      end;
      with TIB_Dataset(FStatement) do
        if Assigned( FBindingCursor ) then
        begin
          OrderingItemNo := 0;
          SQLWhere.Clear;
          SQLOrder.Clear;
        end;
      if FStatement is TIB_BDataset then
        with TIB_BDataset(FStatement) do
        begin
          if Assigned( FIBODataset ) then
          begin
            MasterSource := nil;
            KeySource := nil;
            MasterLinks.Clear;
          end;
        end;
    end
    else
    if not Assigned( APersistent ) then
    begin
      with FStatement as TIB_Dataset do
      begin
        GeneratorLinks.Clear;
        DefaultValues.Clear;
        GetServerDefaults := false;
        Hints.Clear;
        JoinLinks.Clear;
        KeyLinks.Clear;
        KeyDescLinks.Clear;
        MasterLinks.Clear;
        MasterSearch := false;
        MasterSource := nil;
        OrderingItems.Clear;
        OrderingItemNo := 0;
        RequestLive := false;
        SearchingLinks.Clear;
        EditSQL.Clear;
        LockSQL.Clear;
        InsertSQL.Clear;
        DeleteSQL.Clear;
        PreparedEdits := true;
        SearchedDeletes := true;
        SearchedEdits := true;
      end;
    end;
  end;
end;

procedure TIB_SQLStrings.AssignTo( APersistent: TPersistent );
begin
  if Assigned( APersistent ) then
    APersistent.Assign( Self );
end;

procedure TIB_SQLStrings.AssignWithSearch( ASQLStrings: TIB_SQLStrings );
var
  NewSQLWhere: string;
  ii: integer;
  tmpPos: longint;
  tmpStr: string;
  Bg,
  En,
  Ul: integer;
begin
  Assign( ASQLStrings );
  if ASQLStrings.FStatement.IsSelectSQL then
  begin
    if FStatement is TIB_Dataset then
      with FStatement as TIB_Dataset do
      begin
        FAssigningSQL := true;
        try
          Ul := -1;
          GetSQLWhere( ASQLStrings.FStatement.ClientSQL,
                       NewSQLWhere, Bg, En, Ul );
          if NewSQLWhere <> EmptyStr then
          begin
            // Remove MasterLinks stuff because it will be added in again.
            for ii := 0 to MasterLinks.Count - 1 do
            begin
              tmpStr := IBO_AND + MasterLinks.IndexNames[ ii ] +
                        '=?' + GetMasterLinkParamName( ii );
              tmpPos := Pos( UpperCase( tmpStr ), UpperCase( NewSQLWhere ));
              if tmpPos = 0 then
              begin
                tmpStr := MasterLinks.IndexNames[ ii ] +
                          '=?' + GetMasterLinkParamName( ii ) + IBO_AND;
                tmpPos := Pos( UpperCase( tmpStr ), UpperCase( NewSQLWhere ));
              end;
              if tmpPos = 0 then
              begin
                tmpStr := 'WHERE ' + MasterLinks.IndexNames[ ii ] +
                          '=?' + GetMasterLinkParamName( ii );
                tmpPos := Pos( UpperCase( tmpStr ), UpperCase( NewSQLWhere ));
              end;
              if tmpPos > 0 then
                System.Delete( NewSQLWhere, tmpPos, Length( tmpStr ));
            end;
            // Remove JoinLinks stuff because it will be added in again.
            for ii := 0 to JoinLinks.Count - 1 do
            begin
              tmpStr := IBO_AND + JoinLinks[ ii ];
              tmpPos := Pos( UpperCase( tmpStr ), UpperCase( NewSQLWhere ));
              if tmpPos = 0 then
              begin
                tmpStr := JoinLinks[ ii ] + IBO_AND;
                tmpPos := Pos( UpperCase( tmpStr ), UpperCase( NewSQLWhere ));
              end;
              if tmpPos = 0 then
              begin
                tmpStr := 'WHERE ' + JoinLinks[ ii ];
                tmpPos := Pos( UpperCase( tmpStr ), UpperCase( NewSQLWhere ));
              end;
              if tmpPos > 0 then
                System.Delete( NewSQLWhere, tmpPos, Length( tmpStr ));
            end;
            SQLWhere.Text := NewSQLWhere;
          end;
        finally
          FAssigningSQL := false;
        end;
      end;
  end;
end;

// IBA_StatementLink.INT

destructor TIB_StatementLink.Destroy;
begin
  Statement := nil;
  inherited Destroy;
end;

procedure TIB_StatementLink.SetStatement( AValue: TIB_Statement );
var
  LastVal: longint;
begin
  if ( AValue <> Statement ) then
  begin
    ProcessEvent( setBeforeAssignment, longint( AValue ));
    if Assigned( Statement ) then
      with Statement do
        if Assigned( FIB_StatementLinkList ) then
          FIB_StatementLinkList.Remove( Self );
    LastVal := longint( FStatement );
    FStatement := AValue;
    if Assigned( Statement ) then
      with Statement do
        if Assigned( FIB_StatementLinkList ) then
          FIB_StatementLinkList.Add( Self );
    ProcessEvent( setAfterAssignment, LastVal );
  end;
end;

function TIB_StatementLink.GetPrepared: boolean;
begin
  if Assigned( Statement ) then
    Result := Statement.Prepared
  else
    Result := false;
end;

function TIB_StatementLink.GetActive: boolean;
begin
  if Assigned( Statement ) then
    Result := Statement.Active
  else
    Result := false;
end;

function TIB_StatementLink.GetStatementType: TIB_StatementType;
begin
  if Assigned( Statement ) then
    Result := Statement.StatementType
  else
    Result := stUnknown;
end;

{------------------------------------------------------------------------------}

procedure TIB_StatementLink.SysPrepareSQL;
begin
  if Assigned( OnPrepareSQL ) then
    OnPrepareSQL( Self, Statement );
end;

procedure TIB_StatementLink.SysBeforeUpdateDescriptors;
begin
// Abstract.
end;

procedure TIB_StatementLink.SysAfterUpdateDescriptors;
begin
// Abstract.
end;

procedure TIB_StatementLink.ProcessEvent( AEvent: TIB_StatementEventType;
                                          Info: longint );
begin
  if AEvent = setPreparedChanged then 
    if Assigned( OnPreparedChanged ) then
      OnPreparedChanged( Self, Statement );
end;

// IBA_Dataset.INT
// IBA_BDataset.INT

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  16-Feb-2002                                                                 }
{     Fixed KeyToChildAction to so that tmpKeyFlds and tmpKeyVals corresponded }
{     properly in the call to Lookup.                                          }
{                                                                              }
{  Marco Menardi <mmenaz@lycosmail.com>                                        }
{  10-Feb-02                                                                   }
{     in SysGetServerDefaults TrimLeftPrevOrdChar is applied to DEFAULT to    }
{     remove chars lower than #32 included, since server bug allows spaces and }
{     CR/LF chars be embedded in front of the keyword                          }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  07-Nov-2001                                                                 }
{     RefreshBuffers in APINext function to prevent columns always showing     }
{     as modified (and therefore blob columns as loaded).                      }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  13-Sep-2001                                                                 }
{     Reset Eof in APIFirst function.                                          }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  10-Aug-2001                                                                 }
{     Altered the calls to GetGeneratorValue so that they are consistent       }
{     with other uses of that function - by converting to a quoted identifier  }
{     if necessary.                                                            }
{                                                                              }
{******************************************************************************}
{                                                                              }
{  Wassim Haddad <lobolo2000@yahoo.com>                                        }
{  22-Aug-2001                                                                 }
{     Added support to import column defaults from the server upon request.    }
{                                                                              }
{  29-Aug-2001                                                                 }
{    Added OnKeySourceStateChanged event which fires whenever the keysource    }
{    state changes.                                                            }
{                                                                              }
{******************************************************************************}

constructor TIB_Dataset.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FIB_KeyDataLink := TIB_KeyDataLink.Create( Self );
  FIB_KeyDataLink.FChildDataset := Self;
  FIB_KeyDataLink.OnDataChange := IB_KeyDataChange;
  FIB_KeyDataLink.OnStateChanged := IB_KeyStateChanged;
  FIB_MasterDataLink := TIB_MasterDataLink.Create( Self );
  FIB_MasterDataLink.FChildDataset := Self;
  FIB_MasterDataLink.OnDataChange := IB_MasterDataChange;
  FUpdateSQL := TIB_UpdateSQL.Create( Self );
  FUpdateSQL.FDataset := Self;
  FDataSourceList := TList.Create;
  FDefaultValues := TIB_StringProperty.Create;
  FGeneratorLinks := TIB_StringProperty.Create;
  FKeyLinks := TIB_StringProperty.Create;
  FKeyLinksAutoDefine := false;
  FKeyDescLinks := TIB_StringProperty.Create;
  FMasterLinks := TIB_StringProperty.Create;
  FOrderingItems := TIB_StringProperty.Create;
  FOrderingLinks := TIB_StringProperty.Create;
  FMasterParamLinks := TIB_StringProperty.Create;
  FSearchingLinks := TIB_StringProperty.Create;
  FSQLSelect := TIB_StringList.Create;
  FSQLFrom := TIB_StringList.Create;
  FSQLWhere := TIB_StringList.Create;
  FSQLGroup := TIB_StringList.Create;
  FSQLHaving := TIB_StringList.Create;
  FSQLUnion := TIB_StringList.Create;
  FSQLPlan := TIB_StringList.Create;
  FSQLOrder := TIB_StringList.Create;
  FSQLForUpdate := TIB_StringList.Create;
  FSQLOrderLinks := TIB_StringProperty.Create;
  FSQLWhereLow := TIB_StringProperty.Create;
  FSQLWhereMed := TIB_StringProperty.Create;
  FSQLWhereHigh := TIB_StringProperty.Create;
  (FKeyLinks as TIB_StringList).OnChange := OnSQLChange;
  (FMasterLinks as TIB_StringList).OnChange := OnSQLChange;
  (FMasterParamLinks as TIB_StringList).OnChange := OnSQLChange;
  (FOrderingItems as TIB_StringList).OnChange := OnSQLChange;
  (FOrderingLinks as TIB_StringList).OnChange := OnSQLChange;
  (FSearchingLinks as TIB_StringList).OnChange := OnSQLChange;
  FSQLSelect.OnChange := SQLSectionChange;
  FSQLFrom.OnChange := SQLSectionChange;
  FSQLWhere.OnChange := SQLSectionChange;
  FSQLGroup.OnChange := SQLSectionChange;
  FSQLHaving.OnChange := SQLSectionChange;
  FSQLUnion.OnChange := SQLSectionChange;
  FSQLPlan.OnChange := SQLSectionChange;
  FSQLOrder.OnChange := SQLSectionChange;
  FSQLForUpdate.OnChange := SQLSectionChange;
  FCallbackInc := 5;
  FCallbackInitInt := 3000;
  FCallbackRefreshInt := 250;
  FAutoFetchAll := false;
  FAutoFetchFirst := true;
  FCursorName := '';
  FParamChar := ':';
  FAutoPostDelete := true;
  FKeySeeking := true;
  FConfirmDeletePrompt := TIB_StringList.Create;
  FCheckRequired := true;
  FControlsDisabledLevel := 0;
  FHasPostRetained := false;
  FMasterSearchFlags := [ msfOpenMasterOnOpen,
                          msfSearchAppliesToMasterOnly ];
  flag_keylinks := 0;
  FRefreshDML := False;
  FColorScheme := false;
end;

destructor TIB_Dataset.Destroy;
begin
  if Fetching then
    AbortFetching;
  try
    SysClose;
  except
  end;
  try
    SysDeallocate( true );
  except
  end;
  KeySource := nil;
  MasterSource := nil;
  IB_Transaction := nil;
  IB_Connection := nil;
  while DataSourceCount > 0 do
    DataSources[ 0 ].Dataset := nil;
  with FIB_KeyDataLink do
  begin
    OnStateChanged := nil;
    OnDataChange := nil;
    FChildDataset := nil;
  end;
  with FIB_MasterDataLink do
  begin
    OnPrepareSQL := nil;
    OnStateChanged := nil;
    OnDataChange := nil;
    FChildDataset := nil;
  end;
  InvalidateKeyLinksMaps;    // Make sure they are destroyed.
  InvalidateMasterLinksMaps; // Make sure they are destroyed.
  (FSQLSelect as TIB_StringList).OnChange := nil;
  (FSQLFrom as TIB_StringList).OnChange := nil;
  (FSQLWhere as TIB_StringList).OnChange := nil;
  (FSQLGroup as TIB_StringList).OnChange := nil;
  (FSQLHaving as TIB_StringList).OnChange := nil;
  (FSQLUnion as TIB_StringList).OnChange := nil;
  (FSQLPlan as TIB_StringList).OnChange := nil;
  (FSQLOrder as TIB_StringList).OnChange := nil;
  (FSQLForUpdate as TIB_StringList).OnChange := nil;
  inherited Destroy;
  FDataSourceList.Free;
  FDataSourceList := nil;
  FDefaultValues.Free;
  FDefaultValues := nil;
  FGeneratorLinks.Free;
  FGeneratorLinks := nil;
  FKeyLinks.Free;
  FKeyLinks := nil;
  FKeyDescLinks.Free;
  FKeyDescLinks := nil;
  FMasterLinks.Free;
  FMasterLinks := nil;
  FOrderingItems.Free;
  FOrderingItems := nil;
  FOrderingLinks.Free;
  FOrderingLinks := nil;
  FMasterParamLinks.Free;
  FMasterParamLinks := nil;
  FSearchingLinks.Free;
  FSearchingLinks := nil;
  FSQLWhereLow.Free;
  FSQLWhereLow := nil;
  FSQLWhereMed.Free;
  FSQLWhereMed := nil;
  FSQLWhereHigh.Free;
  FSQLWhereHigh := nil;
  FSQLSelect.Free;
  FSQLSelect := nil;
  FSQLFrom.Free;
  FSQLFrom := nil;
  FSQLWhere.Free;
  FSQLWhere := nil;
  FSQLGroup.Free;
  FSQLGroup := nil;
  FSQLHaving.Free;
  FSQLHaving := nil;
  FSQLUnion.Free;
  FSQLUnion:= nil;
  FSQLPlan.Free;
  FSQLPlan := nil;
  FSQLOrder.Free;
  FSQLOrder := nil;
  FSQLOrderLinks.Free;
  FSQLOrderLinks := nil;
  FSQLForUpdate.Free;
  FSQLForUpdate := nil;
  FFetchingAborted := true;
  FConfirmDeletePrompt.Free;
  FConfirmDeletePrompt := nil;
end;

procedure TIB_Dataset.Loaded;
begin
  inherited Loaded;
  if not ( csFixups in ComponentState ) then
    SysOpenAfterLoad;
end;

procedure TIB_Dataset.DefineProperties(Filer: TFiler);
begin
  inherited DefineProperties( Filer );
end;

procedure TIB_Dataset.SysOpenAfterLoad;
begin
  try
    if flag_open_after_load and ( not Active ) then
    begin
      Active := true;
      flag_open_after_load := not Active;
    end;
  except
    on E: Exception do
      Application.ShowException( E );
  end;
end;

procedure TIB_Dataset.AssignSQLWithSearch( ADataset: TIB_Dataset );
begin
  FSQL.AssignWithSearch( ADataset.FSQL );
end;

procedure TIB_Dataset.AssignSQLWhere( ADataset: TIB_Dataset );
var
  tmpStr: string;
  Bg,
  En,
  Ul: integer;
begin
  if Assigned( ADataset ) then
  begin
    Ul := -1;
    GetSQLWhere( ADataset.ClientSQL, tmpStr, Bg, En, Ul );
    tmpStr := Trim( Copy( tmpStr, 6, MaxInt ));
  end
  else
    tmpStr := '';
  if FAssignedSQLWhere <> tmpStr then
  begin
    FAssignedSQLWhere := tmpStr;
    if tmpStr <> '' then
    begin
      ParamValueLinks.AddStrings( ADataset.ParamValueLinks );
      OldParamValueLinks.AddStrings( ADataset.ParamValueLinks );
    end;
    InvalidateSQL;
  end;
end;

function TIB_Dataset.DoMacroSubstitute( const ASQL: string ): string;
var
  tmp: string;
  //~TC~ Optimising the process a bit by only assigning values
  // if macro subst caused a change (else way confusing during debug!)
  // Separate procedure just makes it easier to manage below.
  procedure DoStringsSubst( AStrings: TStrings );
  begin
    tmp := IB_Parse.SubstMacros( AStrings.Text, SysSubstituteMacros,
                                 MacroBegin, MacroEnd );
    if tmp <> AStrings.Text then
      AStrings.Text := tmp;
  end;
begin
  if FAlwaysCallMacroSubstitute or Assigned(FOnMacroSubstitute) then
  begin
    Result := inherited DoMacroSubstitute( ASQL );
    if not PreparingSQL then
    begin
      tmp := IB_Parse.SubstMacros( KeyRelation, SysSubstituteMacros,
                                   MacroBegin, MacroEnd );
      if tmp <> KeyRelation then
        KeyRelation := tmp;
      DoStringsSubst( KeyLinks );
      DoStringsSubst( KeyDescLinks );
      DoStringsSubst( JoinLinks );
      DoStringsSubst( MasterLinks );
      DoStringsSubst( MasterParamLinks );
      DoStringsSubst( OrderingItems );
      DoStringsSubst( OrderingLinks );
      DoStringsSubst( SearchingLinks );
    end;
  end
  else
    Result := ASQL;
end;

procedure TIB_Dataset.SysPrepareSQL;
var
  tmpStr: string;
  ii: integer;
begin
  inherited SysPrepareSQL;
  if ( FAssignedSQLWhere <> '' ) and IsSelectSQL then
  begin
    tmpStr := FAssignedSQLWhere;
    for ii := 1 to Length( tmpStr ) do
    begin
      if Ord( tmpStr[ii] ) < Ord( ' ' ) then
        tmpStr[ ii ] := ' ';
    end;
    SQLWhereItems.Add( '( ' + tmpStr + ' )' );
  end;
end;

procedure TIB_Dataset.CancelQuery;
begin
  inherited CancelQuery;
  if Assigned( FUpdateSQL ) then FUpdateSQL.CancelQuery;
end;

procedure TIB_Dataset.CheckActive;
begin
  if not BufferActive then
    raise EIB_DatasetError.CreateWithSender( Self, E_DatasetClosed );
end;

procedure TIB_Dataset.CheckBrowseMode;
begin
  if ( State <> dssInsert ) or ( not Unidirectional ) then
    CheckActive;
  if State in [ dssSearch, dssEdit, dssInsert ] then
  // This will be redundant in SysPost but this needs to be here.
  // I may eventually have the setCheckBrowseMode treated like an UpdateData.
    ProcessLinkEvent( setFieldsUpdateData, 0 );
  ProcessLinkEvent( setCheckBrowseMode, 0 );
  if NeedToPost then
    SysPost( true, false )
  else
  if FCachedUpdates then
    {TODO:Check to see if changed blobs need to be cached};
end;

procedure TIB_Dataset.CheckCanScroll;
begin
  if Active and ( not CanScroll ) then
    raise EIB_DatasetError.CreateWithSender( Self, E_DatasetCannotScroll );
end;

procedure TIB_Dataset.CheckPrepared;
begin
  if not Prepared then
    raise EIB_DatasetError.CreateWithSender( Self, E_NOT_PREPARED );
end;

function TIB_Dataset.BufferActive: boolean;
begin
  Result := Active or ( Prepared and ( BufferRowCount > 0 ));
end;

function TIB_Dataset.GetModified: boolean;
begin
  Result := ( Fields.RowState = rsModified ) or
            ( Fields.RowData <> Fields.OldRowData );
end;

function TIB_Dataset.GetSysKeyRelation: string;
//var
//  tmpPos: integer;
//  tmpLen: longint;
begin
  Result := inherited GetSysKeyRelation;
  if KeyRelation = '' then
  begin
    RetrieveRelAliasList;
    if FRelAliasList.Count = 1 then
      Result := FRelAliasList.IndexNames[0]
    else
    begin
      Result := '';
      Exit;
    end;
  end;
  CheckConnection( false );
  if FAlwaysCallMacroSubstitute or Assigned( FOnMacroSubstitute ) then
    Result := IB_Parse.SubstMacros( Result,
                                    SysSubstituteMacros,
                                    MacroBegin,
                                    MacroEnd );
  if Assigned( IB_Connection ) and IB_Connection.Connected then
    Result := IB_Connection.mkIdent( Result );
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SysBeforeParamsDataChange( Sender: TIB_Row;
                                                 AField: TIB_Column );
begin
//  if Active and Fetching and
//    ( RefreshOnParamChange or ( MasterDataChangeLevel > 0 )) then
//    AbortFetching;
  inherited SysBeforeParamsDataChange( Sender, AField );
end;

procedure TIB_Dataset.SysAfterParamsDataChange( Sender: TIB_Row;
                                                AField: TIB_Column );
begin
  inherited SysAfterParamsDataChange( Sender, AField );
  FParamWasChanged := true;
  try
    if RefreshOnParamChange or ( MasterDataChangeLevel > 0 ) then
    begin
      if Active and
         ( not NeedToPost ) and
         ( not IsPosting ) and
         ( not ( csDestroying in ComponentState )) and
         ( not ( IB_Connection.ConnectionStatus in [ csDisconnectPending,
                                                     csDropPending ] )) then
      begin
        if Fetching then
          AbortFetching;
        RefreshKeys;
      end;
    end;
    FCursorRecordCountValid := false;
  finally
    FParamWasChanged := false;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SysKeyDataChange( AField: TIB_Column );
begin
  if KeyDataFreezeLevel = 0 then
  begin
    try
      Inc( FKeyChildUpdatingLevel );
      try
        DoKeyDataChange( AField );
      except
        on E: Exception do
        begin
          if E is EIB_StatementError then
          // Who cares!
          else
            Application.HandleException( E );
        end;
      end;
    finally
      Dec( FKeyChildUpdatingLevel );
    end;
  end;
end;

procedure TIB_Dataset.DoKeyDataChange;
begin
// Abstract.
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.IB_MasterDataChange( ADataLink: TIB_DataLink;
                                           ADataSource: TIB_DataSource;
                                           AField: TIB_Column );
begin
  SysMasterDataChange( AField );
end;

procedure TIB_Dataset.IB_KeyDataChange( ADataLink: TIB_DataLink;
                                        ADataSource: TIB_DataSource;
                                        AField: TIB_Column );
begin
  SysKeyDataChange( AField );
end;

function TIB_Dataset.GetMasterLinkParamName( AIndex: integer ): string;
var
  tmpStr: string;
  maxLen: integer;
begin
  tmpStr := MasterLinks.IndexNamesCol[ AIndex ];
  if IsLitCriteria( tmpStr, '"' ) then
    tmpStr := stLitCriteria( tmpStr )
  else
    tmpStr := UpperCase( tmpStr );
  maxLen := 32 - Length( IBO_MASTERLINK ) - 1 - Length( IntToStr( AIndex ));
  if Length( tmpStr ) > maxLen then
    SetLength( tmpStr, maxLen );
  tmpStr := IBO_MASTERLINK + tmpStr + '_' + IntToStr( AIndex );
  CheckConnection( false );
  if Assigned( IB_Connection ) then
    Result := IB_Connection.mkVARIdent( tmpStr );
end;

procedure TIB_Dataset.SysMasterDataChange( AField: TIB_Column );
var
  MaxIterate: integer;

  procedure SysMDChange( AField: TIB_Column );
  var
    tmpParam: TIB_Column;
    tmpField: TIB_Column;
    procedure MakeAssignment;
    begin
      if ( tmpParam <> pointer(-1)) then
      begin
        try
         if ( tmpField <> pointer(-1)) then
           tmpParam.Assign( tmpField )
         else
           tmpParam.Clear;
        except
          tmpParam.Clear;
        end;
      end;
    end;
  var
    ii: integer;
    md: boolean;
    wasactive: boolean;
  begin
    if Active then
    begin
      md := MasterDataset.State = dssInsert;
      if not md and ( MasterDataset is TIB_BDataset ) then
        with MasterDataset as TIB_BDataset do
            md := ( Assigned( Fields.RowNode )) and
                  ( rfInserted in Fields.RowNode.RowFlags );
      if ( md ) then
      begin
        if ( State = dssInsert ) then
        begin
          for ii := 0 to MasterLinks.Count - 1 do
          begin
            tmpParam := TIB_Column( MasterLinks.Objects[ii]   );
            tmpField := TIB_Column( FMasterLinksFieldsMap[ii] );
            MakeAssignment;
          end;
        end;
        // Handle all the cached updates that are here.
        if ( Self is TIB_BDataset ) then
          with Self as TIB_BDataset do
            SynchronizeCachedUpdates( scuMasterDataChanged, AField );
      end;
    end;
    Inc( MaxIterate );
    if MasterDataChangeLevel = 0 then
    begin
      FMasterDataChangeMaxLevel := 0;
      Params.BeginUpdate;
    end;
    Inc( FMasterDataChangeMaxLevel );
    Inc( FMasterDataChangeLevel );
    try
      try
        for ii := 0 to MasterLinks.Count - 1 do
        begin
          tmpParam := TIB_Column( FMasterLinksParamsMap[ii] );
          tmpField := TIB_Column( FMasterLinksFieldsMap[ii] );
          MakeAssignment;
        end;
        for ii := 0 to MasterParamLinks.Count - 1 do
        begin
          tmpParam := TIB_Column( FMasterParamLinksParamsMap[ii] );
          tmpField := TIB_Column( FMasterParamLinksFieldsMap[ii] );
          MakeAssignment;
        end;
        // Generate a datachange event if we are in search mode, since none
        // will be generated by actual changes to data...
        if State = dssSearch then
          ProcessLinkEvent( setFieldsDataChange, 0 );
      finally
        if ( MasterDataChangeLevel = 1 ) and
           Assigned( MasterDataset ) and
           MasterDataset.Prepared then
        begin
          try
          // I made it so that this will only generate change notifications if
          // actual values were changed at the binary level instead of relying
          // solely upon the Assign() method to suppress a change notification
          // if values were actually the same.
          // The reason I had to make it go to the binary level is because it
          // was possible for an infinite loop to be kicked off if the way one
          // column's value compared to another column's value as different
          // even after it was assigned. This happened when assigning from a
          // VARCHAR based column to a CHAR based column.
            wasactive := (Active and ( not Eof ) and ( not Bof ));
            Params.EndUpdate( true );
            if wasactive and (( Eof or Bof ) and ( BufferRowCount > 0 )) then
            begin
              Inc( FKeyChildUpdatingLevel );
              try
                First;
              finally
                Dec( FKeyChildUpdatingLevel );
              end;
            end;
          except
          end;
        end;
      end;
    finally
      Dec( FMasterDataChangeLevel );
    end;
// Recurse until it is for sure on the last record it should be.
// Note comments above about avoiding infinite looping.
    if ( FMasterDataChangeLevel = 0 ) and
       ( FMasterDataChangeMaxLevel > 1 ) and
       ( MaxIterate < 100 ) then
      SysMDChange( AField );
  end;
begin
  MaxIterate := 0;
  if Prepared and
     Assigned( MasterDataset ) and MasterDataset.Prepared then
  begin
    CheckMasterLinksMaps;
    SysMDChange( AField );
  end;
end;

procedure TIB_Dataset.SysMasterDataUpdate( AField: TIB_Column );
var
  ii: integer;
  tmpField: TIB_Column;
  tmpMFld: TIB_Column;
begin
  if Prepared and Assigned( MasterDataset ) and MasterDataset.Prepared then
  begin
    if (               State = dssInsert ) and
       ( MasterDataset.State = dssInsert ) and
       ( IsPosting ) and
       ( not MasterDataset.FCachedUpdates ) and
       ( not MasterDataset.HasPostRetained ) then
    begin
      MasterDataset.PostRetaining;
    end;
    CheckMasterLinksMaps;
    for ii := 0 to MasterLinks.Count - 1 do
    begin
      tmpField := TIB_Column( MasterLinks.Objects[ ii ] );
      tmpMFld  := TIB_Column( FMasterLinksFieldsMap[ ii ] );
      if ( tmpField <> pointer(-1) ) and ( tmpMFld <> pointer(-1) ) then
      begin
        if (not tmpField.IsModified) or (State = dssInsert) then
          tmpField.Assign( tmpMFld );
      end;
    end;
    for ii := 0 to MasterParamLinks.Count - 1 do
    begin
      tmpField := TIB_Column( MasterParamLinks.Objects[ ii ] );
      tmpMFld  := TIB_Column( FMasterParamLinksFieldsMap[ ii ] );
      if ( tmpField <> pointer(-1) ) and ( tmpMFld <> pointer(-1) ) then
      begin
        if (not tmpField.IsModified) or (State = dssInsert) then
          tmpField.Assign( tmpMFld );
      end;
    end;
  end;
end;

{------------------------------------------------------------------------------}

function TIB_Dataset.IsKeyLinkedTo( DataSource: TIB_DataSource ): Boolean;
var
  tmpDataset: TIB_Dataset;
begin
  Result := True;
  while DataSource <> nil do
  begin
    tmpDataset := DataSource.Dataset;
    if tmpDataset = nil then Break;
    if tmpDataset = Self then Exit;
    DataSource := tmpDataset.KeySource;
  end;
  Result := False;
end;

function TIB_Dataset.IsMasterLinkedTo( DataSource: TIB_DataSource ): Boolean;
var
  tmpDataset: TIB_Dataset;
begin
  Result := True;
  while DataSource <> nil do
  begin
    tmpDataset := DataSource.Dataset;
    if tmpDataset = nil then Break;
    if tmpDataset = Self then Exit;
    DataSource := tmpDataset.MasterSource;
  end;
  Result := False;
end;

procedure TIB_Dataset.SetRequestLive( AValue: boolean );
begin
  if RequestLive <> AValue then
  begin
    if Active and ( not ( csLoading in ComponentState )) then
    begin
      raise EIB_DatasetError.CreateWithSender( Self, E_REQUEST_LIVE_ACTIVE );
    end
    else
    begin
      FRequestLive := AValue;
      if AValue then
      begin
        if ( SQLForUpdate.Count = 0 ) and
           (( not SearchedEdits ) or ( not SearchedDeletes )) then
        begin
          InvalidateSQL;
        end;
      end
      else
      if SQLForUpdate.Count > 0 then
        SQLForUpdate.Clear;
    end;
  end;
end;

procedure TIB_Dataset.SetCallbackCaption( const AValue: string );
begin
  if CallbackCaption <> AValue then
  begin
    FCallbackCaption := AValue;
    if Assigned( dlgCancelQuery ) then
      if CallbackCaption <> '' then
        dlgCancelQuery.Caption := CallbackCaption
      else
        dlgCancelQuery.Caption := Application.Title;
  end;
end;

procedure TIB_Dataset.SetConfirmDeletePrompt(AValue: TStrings);
begin
  FConfirmDeletePrompt.Assign( AValue );
end;

procedure TIB_Dataset.SetAutoPostDelete( AValue: boolean );
begin
  if AutoPostDelete <> AValue then
  begin
    FAutoPostDelete := AValue;
    if AutoPostDelete and ( State = dssDelete ) then
      Post;
  end;
end;

procedure TIB_Dataset.SetKeySeeking( AValue: boolean );
begin
  if KeySeeking <> AValue then
  begin
    FKeySeeking := AValue;
    if KeySeeking and Assigned( KeySource ) then
      SysKeyDataChange( nil );
  end;
end;

procedure TIB_Dataset.SetKeySource( AValue: TIB_DataSource );
begin
  if IsMasterLinkedTo( AValue ) or
     IsKeyLinkedTo( AValue ) then
    raise EIB_DatasetError.CreateWithSender( Self, E_CIRCULAR_REFERENCE );
  FIB_KeyDataLink.DataSource := AValue;
end;

procedure TIB_Dataset.IB_KeyStateChanged(Sender: TIB_DataLink;
                               ADataSource: TIB_DataSource );
begin
  if ( not ( csLoading    in ComponentState )) and
     ( not ( csDestroying in ComponentState )) then
    if Assigned( FOnKeySourceStateChanged ) then
      FOnKeySourceStateChanged( Self, ADataSource );
end;
procedure TIB_Dataset.SetMasterSource( AValue: TIB_DataSource );
begin
  if IsMasterLinkedTo( AValue ) or
     IsKeyLinkedTo( AValue ) then
    raise EIB_DatasetError.CreateWithSender( Self, E_CIRCULAR_REFERENCE );
  FIB_MasterDataLink.DataSource := AValue;
end;

function TIB_Dataset.GetKeySource: TIB_DataSource;
begin
  Result := FIB_KeyDataLink.DataSource;
end;

function TIB_Dataset.GetMasterSource: TIB_DataSource;
begin
  if Assigned( FIB_MasterDataLink ) then
    Result := FIB_MasterDataLink.DataSource
  else
    Result := nil;
end;

function TIB_Dataset.GetKeyDataset: TIB_Dataset;
begin
  if KeySource <> nil then
    Result := KeySource.Dataset
  else
    Result := nil;
end;

function TIB_Dataset.GetMasterDataset: TIB_Dataset;
begin
  if Assigned( MasterSource ) then
    Result := MasterSource.Dataset
  else
    Result := nil;
end;

procedure TIB_Dataset.SetMasterParamLinks( Value: TIB_StringList );
begin
  FMasterParamLinks.Text := Trim( Value.Text );
end;

procedure TIB_Dataset.SetMasterLinks( Value: TIB_StringList );
begin
  FMasterLinks.Text := Trim( Value.Text );
end;

procedure TIB_Dataset.GetColumnIsReadOnly(     AColumn: TIB_Column;
                                           var AReadOnly: boolean );
var
  tmpCol: TIB_Column;
  tmpState: TIB_DatasetState;
begin
  if not AReadOnly then
  begin
    tmpState := State;
    if ( tmpState = dssBrowse ) and
       ( AColumn.Row.RowState = rsNone ) then
      tmpState := dssPrepared;
    case tmpState of
      dssBrowse,
      dssEdit: if Assigned( FOnUpdateRecord ) then
      else
      if EditSQL.Count > 0 then
      begin
        tmpCol := FUpdateSQL.EditDSQL.FindParam( AColumn.FieldName );
        AReadOnly := not Assigned( tmpCol );
      end
      else
        inherited GetColumnIsReadOnly( AColumn, AReadOnly );
      dssPrepared,
      dssInsert: if Assigned( FOnUpdateRecord ) then
      else
      if InsertSQL.Count > 0 then
      begin
        tmpCol := FUpdateSQL.InsertDSQL.FindParam( AColumn.FieldName );
        AReadOnly := not Assigned( tmpCol );
      end
      else
        inherited GetColumnIsReadOnly( AColumn, AReadOnly );
      else AReadOnly := true;
    end;
  end;
end;

procedure TIB_Dataset.GetControlIsReadOnly(     AColumn: TIB_Column;
                                            var AReadOnly: boolean );
begin
  if not AReadOnly then
    case State of
      dssPrepared,
      dssBrowse: AReadOnly := (( Fields.RowState = rsNone ) and
                                 AColumn.PreventInserting ) or
                              (( Fields.RowState <> rsNone ) and
                                 AColumn.PreventEditing );
      dssSearch: AReadOnly := AColumn.PreventSearching;
      dssEdit: AReadOnly := AColumn.PreventEditing;
      dssInsert: AReadOnly := AColumn.PreventInserting;
      else AReadOnly := true;
    end;
  if not AReadOnly and ( State <> dssSearch ) then
    AReadOnly := AColumn.GetReadOnly;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.ProcessTransactionEvent(
  ATransactionLink: TIB_TransactionLink;
  AEvent: TIB_TransactionEventType );
begin
  if Active and ( AEvent = tetBeforeAssignment ) then
    Close;
  inherited ProcessTransactionEvent( ATransactionLink, AEvent );
  if AEvent in [ tetAfterEnd,
                 tetAfterCommitRetaining,
                 tetAfterSavePoint,
                 tetAfterRollbackRetaining,
                 tetAfterLosePoint ] then
  begin
    if FIsRowLocked then
    begin
      FIsRowLocked := false;
      Dec( IB_Transaction.FPessimisticLockCount );
    end;
  end;
end;

procedure TIB_BDataset.ProcessTransactionEvent(
  ATransactionLink: TIB_TransactionLink;
  AEvent: TIB_TransactionEventType );
begin
  inherited ProcessTransactionEvent( ATransactionLink, AEvent );
  if AEvent in [ tetAfterRollback,
                 tetAfterRollbackRetaining,
                 tetAfterLosePoint ] then
  begin
    if NodeList.GetUpdatesPending then
      SysProcessUpdates( cupRollback );
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SetOrderingItems( Value: TIB_StringList );
begin
  FOrderingItems.Text := Trim( Value.Text );
end;

procedure TIB_Dataset.SetOrderingLinks( Value: TIB_StringList );
begin
  FOrderingLinks.Text := Trim( Value.Text );
end;

procedure TIB_Dataset.SetSearchingLinks( Value: TIB_StringList );
begin
  FSearchingLinks.Text := Trim( Value.Text );
end;

procedure TIB_Dataset.ClearSearchingLinks;
var
  ii: integer;
  tmpStr: string;
  tmpPrm: TIB_Column;
begin
  for ii:= 0 to SearchingLinks.Count - 1 do
  begin
    tmpStr := SearchingLinks[ ii ];
    tmpStr := Copy( tmpStr, getLitSafePos( '=', tmpStr, 1 ) + 1, maxint );
    tmpPrm := FindParam( tmpStr );
    if Assigned( tmpPrm ) then
      tmpPrm.AsString := '';
  end;
end;

function TIB_Dataset.GetSearchingLinksActive: boolean;
var
  ii: integer;
  tmpStr: string;
  tmpPrm: TIB_Column;
begin
  Result := false;
  for ii:= 0 to SearchingLinks.Count - 1 do
  begin
    tmpStr := SearchingLinks[ ii ];
    tmpStr := Copy( tmpStr, getLitSafePos( '=', tmpStr, 1 ) + 1, maxint );
    tmpPrm := FindParam( tmpStr );
    if Assigned( tmpPrm ) then
      if tmpPrm.AsString <> '' then
      begin
        Result := true;
        Break;
      end;
  end;
end;

procedure TIB_Dataset.GetOrderingItemsList( const OrderingItemsList: TStrings );
var
  ii: integer;
begin
  if OrderingItemsList = nil then Exit;
  OrderingItemsList.Clear;
  for ii := 0 to OrderingItems.Count - 1 do
    OrderingItemsList.Add( OrderingItems.IndexNames[ ii ] );
end;

procedure TIB_Dataset.GetBufferFieldList( AList: TList; const
                                          FieldNames: string );
begin
  BufferFields.GetColumnList( AList, FieldNames );
end;

procedure TIB_Dataset.GetCreateTableSQL( AStrings: TStrings;
                                         DomainInfo,
                                         ConstraintInfo: boolean );
var
  ii, jj: integer;
  ss: string;
  tmpS: string;
  tmpCol: TIB_Column;
  tmpStr: TIB_StringList;
begin
  if not Assigned( AStrings ) then Exit;
  Prepare;
  with AStrings do
  begin
    BeginUpdate;
    try
      Clear;
      tmpS := GetRelNameByRelAlias( SysKeyRelation );
      Add( 'CREATE TABLE ' + tmpS + ' (' );
      jj := 0;
      for ii := 0 to FieldCount - 1 do
      begin
        tmpCol := Fields[ ii ];
        if tmpCol is TIB_ColumnDB_Key then
          Continue;
        if jj > 0 then
          ss := ', '
        else
          ss := '  ';
        Add( ss + tmpCol.GetFieldSource( DomainInfo ));
        Inc( jj );
      end;
      if ConstraintInfo then
      begin
        tmpStr := IB_Connection.SchemaCache.IndexDefs;
        for ii := 0 to tmpStr.Count - 1 do
          if tmpStr.IndexParamValue[ ii, 'REL' ] = tmpS then
            if tmpStr.IndexParamIsSet[ ii, 'PK' ] then
            begin
              Add( ', CONSTRAINT ' +
                   IB_Connection.mkIdent(
                     tmpStr.IndexParamValue[ ii,'CNST' ] ));
              Add( '    PRIMARY KEY ( ' +
                   IB_Connection.mkFldLst(
                     tmpStr.IndexParamValue[ ii,'SEGS' ] ) + ' )' );
            end else if ( tmpStr.IndexParamIsSet[ ii, 'UN' ] ) then
              if Trim( tmpStr.IndexParamValue[ ii, 'CNST' ] ) <> '' then
              begin
                Add( ', CONSTRAINT ' +
                     IB_Connection.mkIdent(
                       tmpStr.IndexParamValue[ ii, 'CNST' ] ));
                Add( '    UNIQUE ( ' +
                     IB_Connection.mkFldLst(
                       tmpStr.IndexParamValue[ ii, 'SEGS' ] ) + ' )' );
              end;
      end;
      Add( ')' );
    finally
      EndUpdate;
    end;
  end;
end;

procedure TIB_Dataset.GetAlterTableSQL( AStrings: TStrings );
var
  ii, jj, kk: integer;
  ss: string;
  tmpStr: TIB_StringList;
  str: string;
  tmpS: string;
begin
  if not Assigned( AStrings ) then
    Exit;
  kk := 0;
  Prepare;
  with AStrings do
  begin
    BeginUpdate;
    try
      Clear;
      tmpS := GetRelNameByRelAlias( SysKeyRelation );
      Add( 'ALTER TABLE ' + tmpS );
      tmpStr := IB_Connection.SchemaCache.IndexDefs;
      for ii := 0 to tmpStr.Count - 1 do
      begin
        if tmpStr.IndexParamValue[ii,'REL'] = tmpS then
        begin
          str := tmpStr.IndexParamValue[ ii,'FK' ];
          if str <> EmptyStr then
          begin
            jj := tmpStr.LinkIndex[ str ];
            if kk = 0 then
              ss := '  '
            else
              ss := ', ';
            Add( ss + 'ADD CONSTRAINT ' +
                 IB_Connection.mkIdent(
                          tmpStr.IndexParamValue[ ii,'CNST' ] ));
            Add( '        FOREIGN KEY ( ' +
                 IB_Connection.mkFldLst(
                                   tmpStr.IndexParamValue[ ii, 'SEGS' ] ) +
                 ' ) ' );
            Add( '        REFERENCES ' + tmpStr.IndexParamValue[ jj, 'REL' ] +
                 ' ( ' +
                 IB_Connection.mkFldLst(
                                   tmpStr.IndexParamValue[ jj, 'SEGS' ] ) +
                 ' )' );
            str := tmpStr.IndexParamValue[ ii,'UR' ];
            if str <> EmptyStr then
              Add( '          ON UPDATE ' + stLitCriteria( str ));
            str := tmpStr.IndexParamValue[ ii,'DR' ];
            if str <> EmptyStr then
              Add( '          ON DELETE ' + stLitCriteria( str ));
            Inc( kk );
          end;
        end;
      end;
    finally
      if kk = 0 then
        Clear;
      EndUpdate;
    end;
  end;
end;

function TIB_Dataset.GetOrderingItemNoChanging: boolean;
begin Result := FOrderingItemNoChanging > 0; end;
function TIB_Dataset.GetOrderingLinkChanging: boolean;
begin Result := FOrderingLinkChanging > 0; end;
function TIB_Dataset.GetSearchingLinkChanging: boolean;
begin Result := FSearchingLinkChanging > 0; end;

procedure TIB_Dataset.SetOrderingItemNo( AValue: integer );
begin
  if OrderingItemNo <> AValue then
  begin
    FOrderingItemNo := AValue;
    Inc( FOrderingItemNoChanging );
    try
      if not PreparingSQL then
        InvalidateSQL;
      ProcessLinkEvent( setOrderingChanged, 0 );
      SysCheckOrderingLink;
      if Active and ( not OrderingLinkChanging ) and
                    ( not SearchingLinkChanging ) then
        RefreshKeys;
    finally
      Dec( FOrderingItemNoChanging );
    end;
    if FOrderingItemNoChanging = 0 then
      DoOrderingChanged;
  end;
end;

procedure TIB_Dataset.SetOrderingLink( const AValue: string );
var
  tmpNo: string;
  OldOrderingLink: string;
begin
  if ( OrderingLink <> AValue ) and ( not OrderingLinkChanging ) then
  begin
    Inc( FOrderingLinkChanging );
    try
      OldOrderingLink := OrderingLink;
      tmpNo := OrderingLinks.LinkParamValue[ AValue, 'ITEM' ];
      if tmpNo = '' then
        tmpNo := OrderingLinks.LinkValues[ AValue ];
      if tmpNo <> '' then
        FOrderingLink := AValue
      else
        FOrderingLink := '';
      if ( tmpNo <> '' ) and
         ( StrToInt( tmpNo ) <> Abs( OrderingItemNo )) and
         ( not OrderingItemNoChanging ) then
        OrderingItemNo := StrToInt( tmpNo );
      if OldOrderingLink <> OrderingLink then
        ProcessLinkEvent( setOrderingLinkChanged, 0 );
      if OrderingLink = '' then
        FOrderingField := nil
      else
        FOrderingField := FindField( OrderingLink );
      SysCheckSearchingLink;
      if Active and ( not SQLIsValid ) and
                    ( not OrderingItemNoChanging ) and
                    ( not SearchingLinkChanging ) then
        RefreshKeys;
    finally
      Dec( FOrderingLinkChanging );
    end;
  end;
end;

procedure TIB_Dataset.SetSearchingLink( const AValue: string );
var
  OldSearchingLink: string;
begin
  if ( SearchingLink <> AValue ) and ( not SearchingLinkChanging ) then
  begin
    Inc( FSearchingLinkChanging );
    try
      OldSearchingLink := SearchingLink;
      FSearchingParamName := SearchingLinks.LinkValues[ AValue ];
      if FSearchingParamName <> '' then
        FSearchingLink := AValue
      else
        FSearchingLink := '';
      FSearchingParam := FindParam( SearchingParamName );
      if FSearchingParamName <> '' then
      begin
        if not OrderingLinkChanging then
          OrderingLink := SearchingLink;
        if OrderingLink = '' then
        begin
          FSearchingLink := '';
          FSearchingParamName := '';
        end;
      end;
      if OldSearchingLink <> SearchingLink then
        ProcessLinkEvent( setSearchingLinkChanged, 0 );
      if Active and ( not SQLIsValid ) and
                    ( not OrderingItemNoChanging ) and
                    ( not OrderingLinkChanging ) then
        RefreshKeys;
    finally
      Dec( FSearchingLinkChanging );
    end;
  end;
end;

procedure TIB_Dataset.SysCheckOrderingLink;
var
  ii: integer;
  tmpStr: string;
  NoOrderingLink: boolean;
begin
  if not OrderingLinkChanging then
  begin
    NoOrderingLink := true;
    for ii := 0 to OrderingLinks.Count - 1 do
    begin
      tmpStr := OrderingLinks.IndexParamValue[ ii, 'ITEM' ];
      if tmpStr = '' then
        tmpStr := OrderingLinks.IndexValues[ ii ];
      if StrToInt( tmpStr ) = Abs( OrderingItemNo ) then
      begin
        OrderingLink := OrderingLinks.IndexNames[ ii ];
        NoOrderingLink := false;
        Break;
      end;
    end;
    if NoOrderingLink then
      OrderingLink := '';
  end;
end;

procedure TIB_Dataset.SysCheckSearchingLink;
var
  ii: integer;
  tmpStr: string;
  NoSearchingLink: boolean;
begin
  if not SearchingLinkChanging then
  begin
    NoSearchingLink := true;
    for ii := 0 to SearchingLinks.Count - 1 do
    begin
      tmpStr := SearchingLinks.IndexNames[ ii ];
      if CompareText( tmpStr, OrderingLink ) = 0 then //!! CompareIdent()
      begin
        SearchingLink := tmpStr;
        NoSearchingLink := false;
        Break;
      end;
    end;
    if NoSearchingLink then
      SearchingLink := '';
  end;
end;

procedure TIB_Dataset.SetGeneratorLinks( Value: TIB_StringList );
begin
  FGeneratorLinks.Text := Trim( Value.Text );
end;

function TIB_Dataset.GetGeneratorLinks: TIB_StringList;
begin
  Result := FGeneratorLinks;
end;

function TIB_Dataset.GetBookmark: string;
begin
  Result := KeyFields.RowData;
end;

procedure TIB_Dataset.SetBookmark( const ABookMark: string );
begin
  raise EIB_DatasetError.CreateWithSender( Self, E_Not_implemented );
end;

function TIB_Dataset.GetBufferBookmark: string;
begin
  Result := '';
end;

procedure TIB_Dataset.SetBufferBookmark( const ABookmark: string );
begin
  raise EIB_DatasetError.CreateWithSender( Self, E_Not_implemented );
end;

{------------------------------------------------------------------------------}

function TIB_Dataset.GetMasterRelation( LinkNo: integer ): string;
var
  Link: string;
begin
  if ( LinkNo >= 0 ) and ( LinkNo < MasterLinks.Count ) then
  begin
    Link := MasterLinks.IndexValues[ LinkNo ];
    Result := Trim( Copy( Link, 1, getLitSafePos( '.', Link, 1 ) - 1 ));
  end
  else
    Result := '';
end;

function TIB_Dataset.GetMasterFieldName( LinkNo: integer ): string;
var
  Link: string;
begin
  if ( LinkNo >= 0 ) and ( LinkNo < MasterLinks.Count ) then
  begin
    Link := MasterLinks.IndexValues[ LinkNo ];
    Result := Trim( Copy( Link, getLitSafePos( '.', Link, 1 ) + 1, maxint ));
  end
  else
    Result := '';
end;

procedure TIB_Dataset.InvalidateSQL;
begin
  if MasterSearch then
    if Assigned( MasterDataset ) and
       ( MasterDataset.State = dssSearch ) then
      MasterDataset.InvalidateSQL;
  inherited InvalidateSQL;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SysPreparedChanged;
begin
  if Prepared then FOrderingField := FindField( OrderingLink ) else
                   FOrderingField := nil;
  if Prepared then
    SetState( dssPrepared )
  else
    SetState( dssInactive );
  InvalidateKeyLinksMaps;
  InvalidateMasterLinksMaps;
  inherited SysPreparedChanged;
end;

procedure TIB_Dataset.SysActiveChange;
begin
  FCursorRowNum := 0;
  FCursorEof := false;
  if WasSingleton then
  begin
    CursorFields.CleanBuffers( true );
    if CursorFields <> Fields then
      Fields.CleanBuffers( true );
  end
  else
  begin
    CursorFields.ClearBuffers( rsNone );
    if CursorFields <> Fields then
      Fields.ClearBuffers( rsNone );
  end;
  SysUpdateState;
  if not Refreshing then
    inherited SysActiveChange;
end;

{------------------------------------------------------------------------------}

function TIB_Dataset.GetState: TIB_DatasetState;
begin
  Result := FState;
end;

procedure TIB_Dataset.SetState( AValue: TIB_DatasetState );
var
  flag: boolean;
begin
  if State <> AValue then
  begin
    flag := State = dssSearch;
    if NeedToPost then
      IB_Transaction.SysAdjustPostPendingCount( -1 );
    FState := AValue;
    try
      try
        StateChanged;
      finally
        if NeedToPost then
          IB_Transaction.SysAdjustPostPendingCount( 1 );
      end
    except
      on E: Exception do
        Application.HandleException( E );
    end;
    if flag or ( State = dssSearch ) or
       (( csDesigning in ComponentState ) and
        ( State in [ dssInactive, dssPrepared ] )) then
      ProcessLinkEvent( setFieldsDataChange, 0 );
  end;
end;

procedure TIB_Dataset.StateChanged;
begin
  SysStateChanged;
end;

procedure TIB_Dataset.SysKeyStateChanged;
begin
  if FStateChangeLevel < 2 then
  begin
    Inc( FStateChangeLevel );
    try
      SysStateChanged;
    finally
      Dec( FStateChangeLevel );
    end;
  end;
end;

procedure TIB_Dataset.SysMasterStateChanged;
begin
  if FStateChangeLevel < 2 then
  begin
    Inc( FStateChangeLevel );
    try
      SysStateChanged;
    finally
      Dec( FStateChangeLevel );
    end;
  end;
end;

{------------------------------------------------------------------------------}

function TIB_Dataset.GetSQLSection( Index: integer ): TStrings;
var
  OldNotif: TNotifyEvent;
  NewText: string;
begin
  Result := nil;
  case Index of
    ssSelect:    Result := FSQLSelect;
    ssFrom:      Result := FSQLFrom;
    ssWhere:     Result := FSQLWhere;
    ssGroup:     Result := FSQLGroup;
    ssHaving:    Result := FSQLHaving;
    ssUnion:     Result := FSQLUnion;
    ssPlan:      Result := FSQLPlan;
    ssOrder:     Result := FSQLOrder;
    ssForUpdate: Result := FSQLForUpdate;
  end;
  if Assigned( Result ) then
  begin
    NewText := TIB_SQLStrings( SQL ).Section[ Index ];
    OldNotif := (Result as TIB_StringList).OnChange;
    try
      (Result as TIB_StringList).OnChange := nil;
      Result.Text := NewText;
    finally
      (Result as TIB_StringList).OnChange := OldNotif;
    end;
  end;
end;

function TIB_Dataset.IsKeyLinksStored: boolean;
begin
  Result := ( not Prepared ) or ( not KeyLinksAutoDefined );
end;

function TIB_Dataset.GetKeyLinks: TIB_StringList;
var
  TableName: string;
  SQLSelectStr: string;
  tmpStr: string;
  tmpPos: integer;
  ii: integer;
  FOldNotify: TNotifyEvent;
begin
  Result := FKeyLinks;
  if flag_keylinks = 0 then
  begin
  // For some reason Delphi 5 Object inspector causes a reentrance problem here.
    Inc( flag_keylinks );
    FOldNotify := FKeyLinks.OnChange;
    try
      FKeyLinks.OnChange := nil;
      if ( PreparingSQL ) and
         ( KeyLinksAutoDefine ) and
         ( not KeyLinksExist ) and
         ( not Assigned( FBindingCursor )) and
         ( not SQLIsAggregate ) and
         ( not SQLIsExecuteBlock ) and
         ( not SQLIsSelectProc ) and
         ( not ( SQLUnion.Count > 0 )) then
      begin
        TableName := GetRelNameByRelAlias( SysKeyRelation );
        if TableName = '' then TableName := SysKeyRelation;
        if ( TableName <> '' ) and
           ( TableName[1] <> '(' ) then
        begin
          IB_Connection.SchemaCache.GetTableKeys( TableName, FKeyLinks );
          if ( not KeyLinksExist ) and
             ( TableName = UpperCase( TableName )) then
            IB_Connection.SchemaCache.GetTableKeys( stLitCriteria( TableName ),
                                                    FKeyLinks );
          if KeyLinksExist then
          begin
            SQLSelectStr := UpperCase(
                            StripLitsAndRounds(
                            StripComments( SQLSelect.Text ), SQLDialect >= 3 ));
            if getLitSafeStrPos( '*', SQLSelectStr, 1 ) = 0 then
            begin
              for ii := 0 to FKeyLinks.Count - 1 do
              begin
                tmpStr := FKeyLinks.IndexNamesCol[ii];
                tmpPos := getLitSafeStrPos( tmpStr, SQLSelectStr, 1 );
                while tmpPos > 0 do
                begin
                  if (( tmpPos = 1 ) or
                      ( CharIsDelimiter( SQLSelectStr[ tmpPos - 1 ] ))) and
                     (( tmpPos = Length( SQLSelectStr ) - Length( tmpStr )) or
                      ( CharIsDelimiter( SQLSelectStr[ tmpPos +
                                                       Length(tmpStr) ] ))) then
                    Break;
                  Inc( tmpPos, Length( tmpStr ));
                  tmpPos := getLitSafeStrPos( tmpStr, SQLSelectStr, tmpPos );
                end;
                if tmpPos = 0 then
                begin
                  FKeyLinks.Clear;
                  Break;
                end
                else
                begin
                end;
              end;
            end;
            if TableName <> SysKeyRelation then
            begin
              for ii := 0 to FKeyLinks.Count - 1 do
              begin
                tmpStr := FKeyLinks.IndexNamesRel[ii];
                if tmpStr = TableName then
                begin
                  tmpStr := FKeyLinks.IndexNamesCol[ii];
                  FKeyLinks.IndexNames[ii] := SysKeyRelation + '.' + tmpStr;
                end;
              end;
            end;
          end;
          if not KeyLinksExist then
          begin
            FKeyLinks.Add( SysKeyRelation + '.' + IBO_RDB + IBO_DB_KEY );
            FKeyLinksAreDBKEY := true;
          end;
          FKeyLinksAutoDefined := KeyLinksExist;
        end;
      end;
    finally
      Dec( flag_keylinks );
      FKeyLinks.OnChange := FOldNotify;
    end;
  end;
end;

function TIB_Dataset.GetKeyLinksExist: boolean;
begin
  Result := FKeyLinks.Count > 0;
end;

procedure TIB_Dataset.SetKeyLinks( AKeyLinks: TIB_StringList );
var
  ii: integer;
begin
  with FKeyLinks do
  begin
    BeginUpdate;
    try
      Assign( AKeyLinks );
      for ii := Count - 1 downto 0 do
        if Trim( Strings[ ii ] ) = '' then
          Delete( ii );
    finally
      EndUpdate;
    end;
  end;
end;

procedure TIB_Dataset.SetKeyDescLinks( AKeyDescLinks: TIB_StringList );
begin
  FKeyDescLinks.Assign( AKeyDescLinks );
end;

procedure TIB_Dataset.SetJoinLinks( AJoinLinks: TIB_StringList );
begin
  FJoinLinks.Assign( AJoinLinks );
end;

procedure TIB_Dataset.SetSQLSection( Index: integer; Value: TStrings );
var
  NewText: string;
  OldSection: integer;
begin
  NewText := SQL.Text;
  case Index of
    ssSelect:    SetSQLSelect   ( NewText, Value.Text, -1{!} );
    ssFrom:      SetSQLFrom     ( NewText, Value.Text, -1{!} );
    ssWhere:     SetSQLWhere    ( NewText, Value.Text, -1{!} );
    ssGroup:     SetSQLGroup    ( NewText, Value.Text );
    ssHaving:    SetSQLHaving   ( NewText, Value.Text );
    ssUnion:     SetSQLUnion    ( NewText, Value.Text, -1{!} );
    ssPlan:      SetSQLPlan     ( NewText, Value.Text, -1{!} );
    ssOrder:     SetSQLOrder    ( NewText, Value.Text );
    ssForUpdate: SetSQLForUpdate( NewText, Value.Text );
  end;
  OldSection := FSQLSectionChanging;
  FSQLSectionChanging := Index;
  try
    SQL.Text := NewText;
  finally
    FSQLSectionChanging := OldSection;
  end;
end;

procedure TIB_Dataset.SQLSectionChange( Sender: TObject );
var
  AStrings: TStrings absolute Sender;
begin
  if Sender = FSQLSelect then
    SetSQLSection( ssSelect, AStrings )
  else
  if Sender = FSQLFrom then
    SetSQLSection( ssFrom, AStrings )
  else
  if Sender = FSQLWhere then
  begin
    FSQLWhereChanged := true;
    SetSQLSection( ssWhere, AStrings );
  end
  else
  if Sender = FSQLGroup then
    SetSQLSection( ssGroup, AStrings )
  else
  if Sender = FSQLHaving then
    SetSQLSection( ssHaving, AStrings )
  else
  if Sender = FSQLUnion then
    SetSQLSection( ssUnion, AStrings )
  else
  if Sender = FSQLPlan then
    SetSQLSection( ssPlan, AStrings )
  else
  if Sender = FSQLOrder then
  begin
    FSQLOrderChanged := true;
    SetSQLSection( ssOrder, AStrings );
  end
  else
  if Sender = FSQLForUpdate then
    SetSQLSection( ssForUpdate, AStrings );
end;

function TIB_Dataset.GetDeleteSQL: TIB_StringList;
begin Result := FUpdateSQL.DeleteSQL; end;
function TIB_Dataset.GetEditSQL: TIB_StringList;
begin Result := FUpdateSQL.ModifySQL; end;
function TIB_Dataset.GetLockSQL: TIB_StringList;
begin Result := FUpdateSQL.LockSQL; end;
function TIB_Dataset.GetInsertSQL: TIB_StringList;
begin Result := FUpdateSQL.InsertSQL; end;
procedure TIB_Dataset.SetDeleteSQL( AValue: TIB_StringList );
begin FUpdateSQL.DeleteSQL.Assign( AValue ); end;
procedure TIB_Dataset.SetEditSQL( AValue: TIB_StringList );
begin FUpdateSQL.ModifySQL.Assign( AValue ); end;
procedure TIB_Dataset.SetLockSQL( AValue: TIB_StringList );
begin FUpdateSQL.LockSQL.Assign( AValue ); end;
procedure TIB_Dataset.SetInsertSQL( AValue: TIB_StringList );
begin FUpdateSQL.InsertSQL.Assign( AValue ); end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.AddSQLWhereClause( const WhereClause: string );
var
  NewText: string;
begin
  NewText := SQLWhere.Text;
  AddWhereClause( NewText, WhereClause );
  SQLWhere.Text := NewText;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SetFocus;
begin
  if Assigned( IB_Session ) then
    IB_Session.FocusedDataset := Self;
end;

procedure TIB_Dataset.Execute;
begin
  if Active and IsSelectSQL then
    raise EIB_StatementError.CreateWithSender( Self, E_OPENED )
  else
    inherited Execute;
end;

function TIB_Dataset.FetchSingle: boolean;
begin
  BeginBusy( false );
  try
    Result := SysFetchSingle;
  finally
    EndBusy;
  end;
end;

procedure TIB_Dataset.Open;
begin
  if not Active then
  begin
    BeginBusy( false );
    try
      DisableControls;
      try
        SysJustBeforeOpen;
        if SysOpen then
          SysJustAfterOpen;
      finally
        EnableControls;
      end;
    finally
      EndBusy;
    end;
  end;
end;


procedure TIB_Dataset.SysJustBeforeOpen;
begin
// Abstract.
end;

procedure TIB_Dataset.SysJustAfterOpen;
begin
  if ( AutoFetchFirst ) and
     ( Bof ) and
     ( KeySource = nil ) then
    SysMoveBy( 1 );
end;

function TIB_Dataset.Refresh: boolean;
begin
  Result := RefreshAll;
end;

function TIB_Dataset.RefreshAll: boolean;
begin
  if not Active then
  begin
    if BufferActive then
      Result := RefreshRows
    else
      Result := false;
  end
  else
  begin
    BeginBusy( true );
    try
      Result := SysRefresh( true, true );
    finally
      EndBusy;
    end;
  end;
end;

function TIB_Dataset.RefreshKeys: boolean;
begin
  if not Active then
    Result := false
  else
  begin
    BeginBusy( true );
    try
      Result := SysRefresh( false, true );
    finally
      EndBusy;
    end;
  end;
end;

function TIB_Dataset.RefreshRows: boolean;
begin
  if not BufferActive then
    Result := false
  else
  begin
    BeginBusy( true );
    try
      Result := SysRefresh( true, false );
    finally
      EndBusy;
    end;
  end;
end;

procedure TIB_Dataset.Close;
begin
  if BufferActive then
  begin
    BeginBusy( false );
    try
      if Fetching and ( not FetchingAborted ) and ( not Refreshing ) then
        raise EIB_DatasetError.CreateWithSender( Self, E_DatasetIsFetching );
      SysClose;
    finally
      EndBusy;
    end;
  end;
end;

function TIB_Dataset.BufferFieldByName( const AFieldName: string ): TIB_Column;
begin Result := BufferFields.ByName( AFieldName ); end;
procedure TIB_Dataset.BufferFirst;
begin
  if not BufferHasBof then
    FetchAll;
  BufferRowNum := BofRowNum + 1;
end;
procedure TIB_Dataset.BufferLast;
begin
  if not BufferHasEof then
    FetchAll;
  BufferRowNum := EofRowNum - 1;
end;

function TIB_Dataset.BufferMoveBy( Increment: longint ):longint;
var
  OldRow: longint;
begin
  OldRow := BufferRowNum;
  BufferRowNum := BufferRowNum + Increment;
  Result := BufferRowNum - OldRow;
end;

procedure TIB_Dataset.BufferNext;
begin BufferRowNum := BufferRowNum + 1; end;
procedure TIB_Dataset.BufferPrior;
begin BufferRowNum := BufferRowNum - 1; end;

{------------------------------------------------------------------------------}

function TIB_Dataset.SysFetchSingle: boolean;
var
  tmpErr: longint;
begin
  if Active then
    SysClose
  else
    SysPrepare;
  CursorFields.ClearBuffers( rsNone );
  FCursorEof := true;
  FCursorRowNum := 0;
  tmpErr := API_QuickFetch( false );
  FActive := tmpErr = 0;
  if Active then
  begin
    FCursorRowNum := 1;
    SetState( dssBrowse );
    CursorFields.RefreshBuffers( true, true, true );
    if SysAfterFetchCursorRow then
      inherited SysActiveChange
    else
    begin
      FActive := false;
      FCursorRowNum := 0;
      SetState( dssPrepared );
      CursorFields.ClearBuffers( rsNone );
      SysAfterFetchCursorEof;
    end;
  end
  else
  if tmpErr = 100 then
    SysAfterFetchCursorEof;
  Result := Active;
end;

function TIB_Dataset.SysOpen: boolean;
var
  NewWasSearching: boolean;
begin
  NewWasSearching := FWasSearching;
  if ( not Active ) and ( not FOpenPending ) then
  begin
    FOpenPending := true;
    try
      if ( csLoading in ComponentState ) or
         ( csReading in ComponentState ) or
         ( csFixups  in ComponentState ) then
        flag_open_after_load := true
      else
      begin
        CheckTransaction( false );
        DisableControls;
        try
          NewWasSearching := ( State = dssSearch ) or
                             ( NewWasSearching and Refreshing );
          if MasterSearch and
             ( State = dssSearch ) and
             Assigned( MasterDataset ) and
             ( MasterDataset.State = dssSearch ) and
             ( msfOpenMasterOnOpen in MasterSearchFlags ) then
            MasterDataset.SysPostSearch( true );
          SysExecute;
        finally
          FOpenPending := false;
          EnableControls;
        end;
      end;
    finally
      FOpenPending := false;
    end;
    FWasSearching := NewWasSearching;
  { Moved from SysAfterExecuteForOutput 12/08/1999}
    if Active and Assigned( KeySource ) then
      if ( FKeyChildUpdatingLevel = 0 ) and
         ( FRefiningIncSearch = 0 ) then
        SysKeyDataChange( nil );
  end;
  Result := Active;
end;

function TIB_Dataset.GetRefreshing: boolean;
begin
  Result := FRefreshCurLevel > 0;
end;

function TIB_Dataset.GetCallbackFreezeLevel: integer;
begin
  Result := IB_Session.CallbackFreezeLevel
end;

function TIB_Dataset.SysRefresh( Rows, Keys: boolean ): boolean;
begin
  Result := false;
  if Rows and ( not Keys ) then
  begin
    InvalidateRows;
    Result := true;
  end
  else
  if Active then
  begin
    if ( IB_Transaction.TransactionState in [ tsCommitPending,
                                              tsRollbackPending ] ) and
       ( (( CommitAction = caClose ) and ( not IB_Transaction.FOATPending )) or
         IB_Transaction.FClosePending ) then
    begin
      if Fetching then
        AbortFetching;
      Close;
    end
    else
    begin
      if FRefreshCurLevel = 0 then
      begin
        FRefreshMaxLevel := 0;
        FRefreshWasScrolled := false;
      end;
      Inc( FRefreshCurLevel );
      Inc( FRefreshMaxLevel );
      try
        DisableControls;
        if Fetching then
        begin
          if FKeyLookupCurLevel = 0 then
            AbortFetching;
        end
        else
          SysBeforeScroll;
        if FRefreshCurLevel = 1 then
        begin
// If this only does a close on the first one then it is possible for the
// master-detail relationship to get messed up because the detail datasets
// won't get executed again with the appropriate input parameter values.
          SysClose; // Move above so it will always close?
          FRefreshEof := FClosedEof;
          FRefreshRowNum := FClosedRowNum;
          FRefreshBookmark := FClosedBookmark;
          FRefreshResult := false;
        end;
        if Rows then
          InvalidateRows;
        if FParamWasChanged or
           ( FMasterDataChangeLevel > 0 ) then
        begin
          Open;
          if FRefreshCurLevel = FRefreshMaxLevel then
          begin
            FRefreshResult := Active and ( not Eof ) and ( not Bof );
            if Bookmark <> FRefreshBookmark then
              FRefreshWasScrolled := true;
          end;
        end
        else
// I added a check of the KeyLookupCurLevel because doing anything but a raw
// open can lead to a problem if it is doing the "rarely" needed refresh
// when looking up a key that exists on the server but not in the buffer
// and all records are indicated to already be in the buffer.
// Thus, just doing the raw open here will refresh the dataset buffer and
// allow the needed record it knows on the server to enter the buffer.
        if FKeyLookupCurLevel > 0 then
        begin
// Refinement criteria shouldn't be reset with a call to Open!
          SysOpen;
          FRefreshResult := true;
        end
        else
        if ( not Unidirectional ) and
           ( FRefreshBookmark <> '' ) and
           (( Assigned( OrderingParam )) or
            ( Assigned( KeySource ) and KeySeeking ) or
            ( RefreshAction in [ raKeepDataPos, raKeepDataPosOrRowNum ] )) then
        begin
          SysOpen;
          if FRefreshCurLevel = FRefreshMaxLevel then
          begin
            Bookmark := FRefreshBookmark;
            if FRefreshCurLevel = FRefreshMaxLevel then
            begin
              FRefreshResult := ( Bookmark = FRefreshBookmark ) and
                                ( not Eof ) and ( not Bof );
              if ( not FRefreshResult ) and ( not FRefreshEof ) then
              begin
                if Assigned( OrderingParam ) then
                  RowNum := BofRowNum + 1
                else
                  RowNum := FRefreshRowNum;
                if FRefreshCurLevel = FRefreshMaxLevel then
                  if Bookmark = FRefreshBookmark then
                  begin
                    FRefreshResult := true;
                    FRefreshWasScrolled := false;
                  end;
              end;
              if ( not FRefreshResult ) and ( not FRefreshEof ) then
              begin
                SysFirst;
                if FRefreshCurLevel = FRefreshMaxLevel then
                begin
                  if Bookmark = FRefreshBookmark then
                  begin
                    FRefreshResult := true;
                    FRefreshWasScrolled := false;
                  end
                  else
                  if RefreshAction = raKeepDataPosOrRowNum then
                  begin
                    BufferRowNum := FRefreshRowNum;
                    if BufferEof then
                      RowNum := EofRowNum - 1
                    else
                      RowNum := FRefreshRowNum;
                    FRefreshResult := false;
                    FRefreshWasScrolled := true;
                  end;
                end;
              end;
              if FRefreshCurLevel = FRefreshMaxLevel then
                if Bookmark <> FRefreshBookmark then
                  FRefreshWasScrolled := true;
            end;
          end;
        end
        else
        if ( KeySource = nil ) and
           ( RefreshAction = raKeepRowNum ) then
        begin
          SysOpen;
          if FRefreshCurLevel = FRefreshMaxLevel then
          begin
            RowNum := FRefreshRowNum;
            if FRefreshCurLevel = FRefreshMaxLevel then
            begin
              if Eof and ( not FRefreshEof ) and ( not Unidirectional ) then
                SysLast;
              if FRefreshCurLevel = FRefreshMaxLevel then
              begin
                FRefreshResult := ( RowNum = FRefreshRowNum ) and
                                  ( not Eof ) and ( not Bof );
                if Bookmark <> FRefreshBookmark then
                  FRefreshWasScrolled := true;
              end;
            end;
          end;
        end
        else
        begin
          Open;
          if FRefreshCurLevel = FRefreshMaxLevel then
            FRefreshResult := Active and ( not Eof ) and ( not Bof );
        end;
      finally
        try
          EnableControls;
        finally
          Dec( FRefreshCurLevel );
        end;
      end;
      if FRefreshCurLevel = 0 then
        if ( not Fetching ) and FRefreshWasScrolled then
          SysAfterScroll;
      Result := FRefreshResult;
    end;
  end;
  Include( FDatasetFlags, dsfWasRefreshed );
end;

procedure TIB_Dataset.SysExecute;
begin
  if Active then
    raise EIB_Error.CreateWithSender( Self, E_DatasetOpen )
  else
  begin
    if State = dssSearch then
      SysPost( false, false );
    inherited SysExecute;
  end;
end;

procedure TIB_Dataset.SysClose;
begin
  if BufferActive and ( not ClosePending ) then
  begin
    FClosePending := true;
    try
      SysBeforeClose;
{ TODO : Need to clear buffers if dssPrepared records exist (Not w/CU).}
      if Assigned( IB_Transaction ) and
         ( IB_Transaction.TransactionState in [ tsRollbackRetainingPending,
                                                tsRollbackPending,
                                                tsRollbackRefreshPending ] ) or
         ( csDestroying in ComponentState ) then
      begin
        if NeedToPost then
          Cancel;
      end
      else
        CheckBrowseMode;
      if BufferRowCount > 0 then
        SysPost( false, false );
      if ( not FOpenPending ) and ( FRefiningIncSearch = 0 ) then
      begin
        try
          FClosedEof := Eof or Bof;
          FClosedRowNum := RowNum;
        except
          FClosedRowNum := 0;
        end;
        try
          FClosedBookmark := Bookmark;
        except
          FClosedBookmark := EmptyStr;
        end;
      end;
      if FCursorIsOpen then
      begin
        API_CloseCursor;
        if Assigned( IB_Transaction ) then
          IB_Transaction.CheckOAT;
      end;
	  if FCursorGen = High(DWORD)then
	    FCursorGen := Low(DWORD)
	  else
	    Inc( FCursorGen );
      inherited SysClose;
      SysAfterClose;
    finally
      FClosePending := false;
    end;
  end;
end;

procedure TIB_Dataset.SysExecSelect;
begin
  API_Execute;
  API_OpenCursor( FCursorName );
  if FCursorGen = High(DWORD)then
    FCursorGen := Low(DWORD)
  else
    Inc( FCursorGen );
end;

procedure TIB_Dataset.SysBeforeExecuteForOutput;
begin
  if IsSelectSQL then
    SysBeforeOpen;
  if Assigned( MasterSource ) and
     (( MasterDataChangeLevel = 0 ) or
      ( FMasterDataChangeMaxLevel <= 1 )) then
    SysMasterDataChange( nil );
  inherited SysBeforeExecuteForOutput;
end;

procedure TIB_Dataset.SysAfterExecuteForOutput;
begin
  if FParamWasChanged then
  begin
    FParamWasChanged := false;
    FCursorRecordCountValid := false;
  end;
  inherited SysAfterExecuteForOutput;
  if AutoFetchAll and ( StatementType in [ stSelect, stSelectForUpdate ] ) then
  begin
    if Unidirectional then
      SysLast
    else
      SysFetchAll( 0 );
  end;
  if IsSelectSQL then
    SysAfterOpen;
end;

procedure TIB_Dataset.SysUpdateDescriptors;
var
  tmpPos: integer;
  prmStr: string;
  ii: integer;
  tmpEvent1: TNotifyEvent;
  tmpEvent2: TNotifyEvent;
begin
  inherited SysUpdateDescriptors;
  if RepreparingSQL then
  begin
    InvalidateKeyLinksMaps;    {ParamsOnly}
    InvalidateMasterLinksMaps; {ParamsOnly}
  end
  else
  begin
    FCursorKeyFieldCount := 0;
    if ( FCursorFieldCount > 0 ) and
       ( not Assigned( FBindingCursor )) and
       ( IsSelectSQL ) then
    begin
      FCursorKeyFieldCount := KeyLinks.Count;
      if FCursorKeyFieldCount = 0 then
        FCursorKeyFieldCount := 1; // Will use the KeyRowNum as the key.
    end;
    FCursorKeyFields.SysUpdate( FCursorKeyFieldCount );
    if not FUnpreparing then
      CheckKeyLinksMaps;  // Need this pulled in right away to keep the
                          // relation alias info in sync in the KeyFields.
  end;
  if Prepared then
    FCursorRecordCountValid := false;
  if FUnpreparing then
  begin
    FSearchingParam := nil;
    FOrderingParam := nil;
    tmpEvent1 := KeyLinks.OnChange;
    tmpEvent2 := KeyDescLinks.OnChange;
    try
      for ii := 0 to KeyLinks.Count - 1 do
        KeyLinks.Objects[ii] := nil;
      for ii := 0 to KeyDescLinks.Count - 1 do
        KeyDescLinks.Objects[ii] := nil;
    finally
      KeyLinks.OnChange := tmpEvent1;
      KeyDescLinks.OnChange := tmpEvent2;
    end;
  end
  else
  begin
    FSearchingParam := FindParam( SearchingParamName );
    prmStr := OrderingLink;
    if prmStr = '' then
      FOrderingParam := nil
    else
    begin
      tmpPos := getLitSafePos( '.', prmStr, 1 );
      if tmpPos > 0 then
        System.Delete( prmStr, 1, tmpPos );
      prmStr := stLitCriteria( prmStr );
      prmStr := IB_Connection.mkVarIdent( Copy( IBO_ORDLINK + prmStr, 1, 31 ));
      FOrderingParam := FindParam( prmStr );
    end;
  end;
end;

function TIB_Dataset.FindBufferField( const FieldName: string ): TIB_Column;
begin
  BufferFields.GetByName( FieldName, Result );
end;

function TIB_Dataset.FindKeyField( const FieldName: string ): TIB_Column;
begin
  KeyFields.GetByName( FieldName, Result );
end;

procedure TIB_Dataset.FreeServerResources;
begin
  if Active and ( not WasSingleton ) then
    FetchAll;
  if FCursorIsOpen then
    API_CloseCursor;
  FCursorName := '';
  FCursorFields.FCursorName := '';
  inherited FreeServerResources;
end;

procedure TIB_Dataset.SysDescribeVARList( ARow: TIB_Row );
var
  ii: smallint;
  tmpCol: TIB_Column;
begin
  with ARow, FPSQLDA^ do
  begin
    if RowType = rtKey then
    begin
      if KeyLinksExist then
      begin
        for ii := 0 to sqln - 1 do
        begin
          if CursorFields.GetByName( KeyLinks.IndexNames[ii], tmpCol ) then
          begin
            sqlvar[ii] := tmpCol.PSQLVAR^;
            sqlvar[ii].SQLInd := nil;
            sqlvar[ii].SQLData := nil;
            if sqlvar[ii].SQLName = IBO_DB_KEY then
            begin
              sqlvar[ii].SQLName := IBO_RDB + IBO_DB_KEY;
              sqlvar[ii].SQLName_length := Length( IBO_RDB + IBO_DB_KEY );
            end;
          end
          else
            raise EIB_Error.CreateWithSender( Self, Format( E_Invalid_KeyLinks,
                                                           [ KeyLinks[ ii ]] ));
        end;
      end
      else
      if sqln = 1 then
      begin
        sqlvar[0].SQLScale := 0;
        sqlvar[0].SQLType := SQL_LONG;
        sqlvar[0].SQLLen := 4;
        sqlvar[0].SQLInd := nil;
        sqlvar[0].SQLData := nil;
        FillChar( sqlvar[0].ownname,   32, #0 );
        FillChar( sqlvar[0].relname,   32, #0 );
        FillChar( sqlvar[0].sqlname,   32, #0 );
        FillChar( sqlvar[0].aliasname, 32, #0 );
        sqlvar[0].aliasname := IBO_KEYROWNUM;
        sqlvar[0].ownname_length := -1;
        sqlvar[0].relname_length := -1;
        sqlvar[0].sqlname_length := -1;
        sqlvar[0].aliasname_length := Length( IBO_KEYROWNUM );
      end
      else
        raise EIB_Error.CreateWithSender( Self,
                                     Format( E_Invalid_KeyLinks, ['<BLANK>'] ));
    end
    else
      inherited SysDescribeVARList( ARow );
  end;
end;

procedure TIB_Dataset.SysPrepareFailed;
begin
  inherited SysPrepareFailed;
  SQLWhereLow.Clear;
  SQLWhereMed.Clear;
  SQLWhereHigh.Clear;
  ParamValueLinks.Clear;
  FSearchingParam := nil;
  if ( not RepreparingSQL ) then
  begin
    if FKeyLinksAutoDefined then
    begin
      FKeyLinks.Clear;
      FKeyLinksAutoDefined := false;
      FKeyLinksAreDBKEY := false;
    end;
    Fields.FRelationList.Clear;
    Params.FRelationList.Clear;
    FCursorKeyFieldCount := 0;
    FCursorKeyFields.SysUpdate( FCursorKeyFieldCount );
  end;
end;

procedure TIB_Dataset.SysUnprepare;
begin
  FUpdateSQL.SysDeallocate;
  inherited SysUnprepare;
  if not ( csDestroying in ComponentState ) then
  begin
    ClearSearch;
    SaveSearch;
  end;
end;

function TIB_Dataset.SysPrepare: boolean;
var
  ii: integer;
begin
  if Prepared and ( State = dssSearch ) then
  begin
    ProcessLinkEvent( setFieldsUpdateData, 0 );
    for ii := 0 to IB_Session.Session_Datasets.Count - 1 do
      with TIB_Dataset( IB_Session.Session_Datasets.Items[ ii ] ) do
        if Assigned( MasterSource ) then
          if MasterSource.Dataset = Self then
            if MasterSearch then
              SysPrepare;
  end;
  Result := inherited SysPrepare;
end;

function TIB_Dataset.SysNeedToRefineSQL: boolean;
begin
  Result := inherited SysNeedToRefineSQL;
  if ( not Result ) and ( OrderingItemNo <> 0 ) then
    with OrderingLinks do
      Result := (( OrderingItemNo > 0 ) and
                 ( LinkParamValue[ OrderingLink, 'PLANASC' ] <> '' )) or
                (( OrderingItemNo < 0 ) and
                 ( LinkParamValue[ OrderingLink, 'PLANDESC' ] <> '' ));
end;

procedure TIB_Dataset.SysRefineSQL;
var
  tmpStr: string;
begin
  inherited SysRefineSQL;
  if OrderingItemNo <> 0 then
    with OrderingLinks do
    begin
      tmpStr := '';
      if OrderingItemNo > 0 then
        tmpStr := LinkParamValue[ OrderingLink, 'PLANASC' ]
      else
      if OrderingItemNo < 0 then
        tmpStr := LinkParamValue[ OrderingLink, 'PLANDESC' ];
      if tmpStr <> '' then
        SQLPlan.Text := 'PLAN ' + tmpStr;
    end;
end;

procedure TIB_Dataset.SysInitRawSQL;
begin
  inherited SysInitRawSQL;
  SQLWhereLow.Clear;
  SQLWhereMed.Clear;
  SQLWhereHigh.Clear;
  if not RefiningSQL then
  begin
    FSQLWhereChanged := false;
    FSQLOrderChanged := false;
    if ( FReadOnly or
         FCachedUpdates or
       ( PreventEditing and PreventDeleting )) and
       ( StatementType = stSelectForUpdate ) then
      SQLForUpdate.Clear
    else
    if RequestLive and ( not ( SearchedEdits and SearchedDeletes )) and
       IsSelectSQL and ( SQLForUpdate.Count = 0 ) then
      SQLForUpdate.Text := 'FOR UPDATE';
  end;
end;

procedure TIB_Dataset.SysFinishRawSQL;
var
  ii: integer;
  OldWhereClause: string;
  NewWhereClause: string;
  BindingParam: string;
  tmpStr: string;
  tmpName: string;
  tmpInt: integer;
  tmpPos: integer;
  newOrd: string;
  tmpBool: boolean;
  tmpNulls: boolean;
  tmpNullOpt: string;
begin
  inherited SysFinishRawSQL;
  OldWhereClause := SQLWhere.Text;
  NewWhereClause := OldWhereClause;
  if not RefiningSQL then
  begin
    FMasterWhere := NewWhereClause;
    if JoinLinks.Count > 0 then
      ProcessSQLWhereStrings( JoinLinks, NewWhereClause, true );
  end;
  if SQLWhereHigh.Count > 0 then
  begin
    if RefiningSQL then
      ProcessSQLWhereStrings( SQLWhereHigh, NewWhereClause, true )
    else
    begin
      if ( not Assigned( MasterSource )) or
         ( not MasterSearch ) or
         ( not ( MasterDataset.State = dssSearch )) or
         ( not ( msfSearchAppliesToMasterOnly in MasterSearchFlags )) then
        ProcessSQLWhereStrings( SQLWhereHigh, NewWhereClause, true );
      ProcessSQLWhereStrings( SQLWhereHigh, FMasterWhere, true );
      FSQLWhereChanged := true;
    end;
  end;
  if ( not RefiningSQL ) and
     ( not ( Self is TIB_LocateCursor )) and
     ( not ( Self is TIB_FilterCursor )) then
  begin
    for ii := MasterLinks.Count - 1 downto 0 do
    begin
      if Assigned( FBindingCursor ) then
        BindingParam := '=?' + IBO_BINDLINK + IntToStr( ii )
      else
        BindingParam := '=?' + GetMasterLinkParamName( ii );
      AddWhereClauseHigh( NewWhereClause,
                          MasterLinks.IndexNames[ ii ] + BindingParam );
    end;
  end;
  if SQLWhereMed.Count > 0 then
  begin
    if RefiningSQL then
      ProcessSQLWhereStrings( SQLWhereMed, NewWhereClause, false )
    else
    begin
      if ( not Assigned( MasterSource )) or
         ( not MasterSearch ) or
         ( not ( MasterDataset.State = dssSearch )) or
         ( not ( msfSearchAppliesToMasterOnly in MasterSearchFlags )) then
        ProcessSQLWhereStrings( SQLWhereMed, NewWhereClause, false );
      ProcessSQLWhereStrings( SQLWhereMed, FMasterWhere, false );
      FSQLWhereChanged := true;
    end;
  end;
  if SQLWhereLow.Count > 0 then
  begin
    if RefiningSQL then
      ProcessSQLWhereStrings( SQLWhereLow, NewWhereClause, false )
    else
    begin
      if ( not Assigned( MasterSource )) or
         ( not MasterSearch ) or
         ( not ( MasterDataset.State = dssSearch )) or
         ( not ( msfSearchAppliesToMasterOnly in MasterSearchFlags )) then
        ProcessSQLWhereStrings( SQLWhereLow, NewWhereClause, false );
      ProcessSQLWhereStrings( SQLWhereLow, FMasterWhere, false );
      FSQLWhereChanged := true;
    end;
  end;

  if RefiningSQL then
  begin
    if Trim( OldWhereClause ) <> Trim( NewWhereClause ) then
    begin
      tmpBool := FSQLWhereChanged;
      try
        SQLWhere.Text := Trim( NewWhereClause );
      finally
        FSQLWhereChanged := tmpBool;
      end;
    end;
  end
  else
  begin

    if FSQLWhereChanged then
    begin
      SQLWhere.Text := NewWhereClause;
      FMasterWhere := Copy( FMasterWhere, 7, MaxInt );
    end
    else
    begin
      if Trim( OldWhereClause ) <> Trim( NewWhereClause ) then
        SQLWhere.Text := NewWhereClause;
      FMasterWhere := '';
      FSQLWhereChanged := false; // Resets the value.
    end;

    if IsSelectSQL then
    begin
      if OrderingItemNo = 0 then
        tmpStr := Trim( SQLOrder.Text )
      else
        tmpStr := GetOrderingSQL( OrderingItemNo );
      tmpStr := Trim( Copy( tmpStr, 6, MaxInt ));
      tmpStr := Trim( Copy( tmpStr, 3, MaxInt ));
      MakeServerSQL( tmpStr, nil, nil, nil,
                     tmpStr, ParamChar, false, tmpPos, false );
      for tmpPos := 1 to Length( tmpStr ) do
        if tmpStr[ tmpPos ] < ' ' then
          tmpStr[ tmpPos ] := ' ';
      tmpPos := Length( tmpStr );
      while ( tmpPos > 0 ) and ( tmpStr[ tmpPos ] = ';' ) do
      begin
        System.Delete( tmpStr, tmpPos, 1 );
        tmpStr := Trim( tmpStr );
        tmpPos := Length( tmpStr );
      end;
      if ( tmpPos > 0 ) then with FSQLOrderLinks do
      begin
        BeginUpdate;
        try
          Clear;
          repeat
            tmpInt := getLitsRoundSafePos( ',', tmpStr, 1 );
            if tmpInt = 0 then
            begin
              if Trim( tmpStr) <> '' then
              begin
                swap_chars( tmpStr, #10#13, #32 );
                Add( tmpStr );
              end;
            end
            else
            begin
              Add( Copy( tmpStr, 1, tmpInt - 1 ));
              tmpStr := Trim( Copy( tmpStr, tmpInt + 1, MaxInt ));
            end;
          until tmpInt = 0;
          newOrd := 'ORDER BY ';
          for tmpInt := 0 to Count - 1 do
          begin
            tmpNulls := false;
            tmpNullOpt := '';
            tmpStr := Strings[tmpInt];
            tmpPos := getLitSafeStrPos( ' NULLS', UpperCase( tmpStr ), 1 );
            if tmpPos > 1 then
            begin
              tmpNulls := true;
              System.Delete( tmpStr, tmpPos, 6 );
              tmpPos := getLitSafeStrPos( ' FIRST', UpperCase( tmpStr ), 1 );
              if tmpPos > 1 then
              begin
                tmpNullOpt := 'FIRST';
                System.Delete( tmpStr, tmpPos, 6 );
              end
              else
              begin
                tmpPos := getLitSafeStrPos( ' LAST', UpperCase( tmpStr ), 1 );
                if tmpPos > 1 then
                begin
                  tmpNullOpt := 'LAST';
                  System.Delete( tmpStr, tmpPos, 5 );
                end;
              end;
            end;
            tmpPos := getLitSafeStrPos( ' ASC', UpperCase( tmpStr ), 1 );
            if tmpPos > 1 then
              tmpStr := Copy( tmpStr, 1, tmpPos - 1 );
            tmpPos := getLitSafeStrPos( ' DESC', UpperCase( tmpStr ), 1 );
            if tmpPos > 1 then
              tmpStr := Copy( tmpStr, 1, tmpPos - 1 );
            tmpStr := Trim( StripComments( tmpStr ));
            tmpName := tmpStr;
            if IsColAttributeSet[ tmpStr, IBO_NOCASE ] then
            begin
              tmpStr := Trim( ColAttributeParams[ tmpStr, IBO_NOCASE ] );
              if tmpStr = '' then
//                tmpStr := 'UPPER( ' + tmpName + ' )';
                tmpStr := '' + tmpName + ''; // I should issue a warning here.
            end;
            if tmpInt > 0 then
              newOrd := newOrd + #13#10'       , ';
            if tmpPos > 0 then
            begin
              newOrd := newOrd + tmpStr + ' DESC';
              Strings[tmpInt] := tmpName + '=DESC';
            end
            else
            begin
              newOrd := newOrd + tmpStr + ' ASC';
              Strings[tmpInt] := tmpName + '=ASC';
            end;
            if tmpNulls then
              newOrd := newOrd + ' NULLS ' + tmpNullOpt;
          end;
          SQLOrder.Text := newOrd;
        finally
          EndUpdate;
        end;
      end;
      if ( SearchedEdits or ( EditSQL.Count > 0 )) and
         ( SearchedDeletes or ( DeleteSQL.Count > 0 )) then
        SQLForUpdate.Clear;
    end;
  end;
end;

procedure TIB_Dataset.SysExecPrepare;
var
  ii: integer;
begin
  inherited SysExecPrepare;
  for ii := 0 to Fields.ColumnCount - 1 do
  begin
    BufferFields[ii].OnAfterModify  := Fields[ii].OnAfterModify;
    BufferFields[ii].OnBeforeModify := Fields[ii].OnBeforeModify;
    BufferFields[ii].OnGetText      := Fields[ii].OnGetText;
    BufferFields[ii].OnSetText      := Fields[ii].OnSetText;
    BufferFields[ii].OnValidate     := Fields[ii].OnValidate;
  end;
end;

function TIB_Dataset.GetOrderingSQL( ItemNo: integer ): string;
begin
  if ItemNo = 0 then
    Result := SQLOrder.Text
  else
  with OrderingItems do
  begin
    Result := '';
    if ( Abs( ItemNo ) <= Count ) then
    begin
      Result := IndexValues[ Abs( ItemNo ) - 1 ];
      if getLitSafePos( ';', Result, 1 ) > 0 then
      begin
        if ItemNo > 0 then
          Result := Copy( Result, 1, getLitSafePos( ';', Result, 1 ) - 1 )
        else
          Result := Copy( Result, getLitSafePos( ';', Result, 1 ) + 1, maxint );
      end
      else
      if ItemNo < 0 then
        Result := '';  // Invalid ItemNo value.
    end;
    Result := Trim( Result );
    if Result <> '' then
      Result := 'ORDER BY ' + Result;
  end;
end;

procedure TIB_Dataset.ProcessSQLWhereStrings( const AStrings: TStrings;
                                              var   NewWhereClause: string;
                                                    High: boolean );
var
  tmpStr: string;
  tmpClause: string;
  ii: integer;
  tmpCnt: integer;
  NeedOper: boolean;
begin
  tmpCnt := 0;
  tmpClause := '';
  NeedOper := false;
  for ii := 0 to AStrings.Count - 1 do
  begin
    tmpStr := Trim( AStrings.Strings[ii] );
    if tmpStr = '(' then
    begin
      Inc( tmpCnt );
      if NeedOper then
      begin
        tmpStr := ' AND ' + tmpStr;
        NeedOper := false;
      end;
    end
    else
    if tmpStr = ')' then
    begin
      Dec( tmpCnt );
      NeedOper := true;
    end
    else
    if tmpCnt > 0 then
    begin
      if AnsiCompareText( tmpStr, 'AND' ) = 0 then
      begin
        tmpStr := ' AND ';
        NeedOper := false;
      end
      else
      if AnsiCompareText( tmpStr, 'OR' ) = 0 then
      begin
        tmpStr := ' OR ';
        NeedOper := false;
      end
      else
      if NeedOper then
        tmpStr := ' AND ' + tmpStr
      else
        NeedOper := true;
    end;
    tmpClause := tmpClause + tmpStr;
    if tmpCnt = 0 then
    begin
      if High then
        AddWhereClauseHigh( NewWhereClause, tmpClause )
      else
        AddWhereClause( NewWhereClause, tmpClause );
      tmpClause := '';
      NeedOper := false;
    end;
  end;
  if tmpCnt <> 0 then
    raise EIB_Error.CreateWithSender( Self,
                              E_Invalid_Syntax + ':' + #13#10 + AStrings.Text );
end;

{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - }

procedure TIB_Dataset.FetchAll;
begin
  BeginBusy( true );
  try
    SysOpen;
    SysFetchAll( 0 );
  finally
    EndBusy;
  end;
end;

procedure TIB_Dataset.SysFetchAll( ATicks: DWORD );
var
  tmpTicks: DWORD;
  FAGen: integer;
begin
  if CanScroll or ( not Unidirectional ) then
  begin
    if Unidirectional and NeedToPost then
      CheckBrowseMode;
    FAGen := FFetchingAbortedGen;
    if ATicks = 0 then
      try
        BeginCallbackFetching;
        while ( Active ) and
              ( not CursorEof ) and
              ( FAGen = FFetchingAbortedGen ) do
          SysFetchNext;
      finally
        EndCallbackFetching;
      end
    else
    begin
      tmpTicks := GetTickCount + ATicks; // this does roll over every so often..
      BeginCallbackFreeze;
      try
        while Active and
              ( not CursorEof ) and
              ( FAGen = FFetchingAbortedGen ) and ( tmpTicks > GetTickCount ) do
          SysFetchNext;
      finally
        EndCallbackFreeze;
      end;
    end;
  end;
end;

procedure TIB_Dataset.DoDMLCacheAnnounceItem(
                                      ADMLCacheItemType: TIB_DMLCacheItemType );
begin
  if Assigned( OnDMLCacheAnnounceItem ) then
    OnDMLCacheAnnounceItem( Self, ADMLCacheItemType )
  else
    DefaultDMLCacheAnnounceItem( ADMLCacheItemType );
end;

procedure TIB_Dataset.DefaultDMLCacheAnnounceItem(
                                      ADMLCacheItemType: TIB_DMLCacheItemType );
var
  ii: integer;
  tmpStr: string;
begin
  tmpStr := '';
  for ii := 0 to KeyFields.ColumnCount - 1 do
  begin
    if ii > 0 then
      tmpStr := tmpStr + ';';
    tmpStr := tmpStr + KeyFields[ii].FieldName;
  end;
  case ADMLCacheItemType of
    ditEdit, ditDelete:
      IB_Transaction.AddDMLCacheItem( IB_Connection,
                                      Self,
                                      tmpStr,
                                      Fields.OldValues[ tmpStr ],
                                      ADMLCacheItemType );
    ditInsert:
      IB_Transaction.AddDMLCacheItem( IB_Connection,
                                      Self,
                                      tmpStr,
                                      Fields.Values[ tmpStr ],
                                      ADMLCacheItemType );
  end;
end;

procedure TIB_Dataset.DefaultDMLCacheReceivedItem(
                                        const ADMLCacheItem: TIB_DMLCacheItem );
begin
// Abstract here.
end;

procedure TIB_Dataset.DoDMLCacheReceiveItem(
                                        const ADMLCacheItem: TIB_DMLCacheItem );
begin
  if Assigned( OnDMLCacheReceivedItem ) then
    OnDMLCacheReceivedItem( Self, ADMLCacheItem )
  else
    DefaultDMLCacheReceivedItem( ADMLCacheItem );
end;

procedure TIB_Dataset.DoAppCallback;
begin
  if CallbackInc >= 0 then
  begin
    if ( CallbackFreezeLevel = 0 ) and
       ( not FetchingAborted ) and
       ( not ( csDestroying in ComponentState )) then
    begin
      if not IB_Session.DoAppCallback then
        AbortFetching;
    end;
  end;
end;

procedure TIB_Dataset.DoCallback;
var
  CurTick: DWORD;
begin
  if Fetching or ( MasterDataChangeLevel > 0 ) then
  begin
    CurTick := GetTickCount;
    if ( CallbackInc < 0 ) or ( CallbackFreezeLevel <> 0 ) or
       ( csDestroying in ComponentState ) or
       (( Owner is TIB_Dataset ) and
        ( TIB_Dataset( Owner ).CallbackFreezeLevel <> 0 )) then
    else
    if CallbackInc = 0 then
      DoAppCallback
    else
    if CursorRowNum mod CallbackInc = 0 then
    begin
      if CurTick > CallbackInitTick + CallbackInitInt then
        if CurTick >= CallbackRefreshTick + CallbackRefreshInt then
        begin
          CallbackRefreshTick := CurTick;
          SysProcessCallback( csRefresh );
        end;
      DoAppCallback;
    end;
  end;
end;

procedure TIB_Dataset.SysFetchNext;
var
  ARow: TIB_Row;
  procedure DoCloseOffToEof;
  begin
    FCursorEof := true;
    API_CloseCursor;
    IB_Transaction.CheckOAT;
    ARow.ClearBuffers( rsNone );
    SysAfterFetchCursorEof;
    FCallbackInitTick := 0;
  end;
var
  SaveCW: word;
  cur_tick: DWORD;
begin
  ARow := CursorFields;
  with IB_Session do
  begin
    while not FetchingAborted do
    begin
      cur_tick := GetTickCount;
      if ( CallbackInitTick = 0 ) or
         ( CallbackInitTick > cur_tick ) then
        FCallbackInitTick := cur_tick;
      if (( MaxRows > 0 ) and
          ( MaxRows <= FCursorRowNum )) or
         (( MaxTicks > 0 ) and
          ( MaxTicks <= ( cur_tick - CallbackInitTick ))) or
         ( ARow.PSQLDA.SQLd <= 0 ) then
        errcode := 100
      else
      begin
        asm fstcw [SaveCW] end;
        errcode := isc_dsql_fetch( @status,
                                   PstHandle,
                                   SQLDialect,
                                   ARow.PSQLDA );
        asm fldcw [SaveCW] end;
      end;
      if errcode = 0 then
      begin
        Inc( FCursorRowNum );
        ARow.RefreshBuffers( true, true, true );
        if not FetchingAborted then
        begin
          if SysAfterFetchCursorRow then
          begin
            if Fetching then
              DoCallback;
            Break;
          end
          else
          begin
            Dec( FCursorRowNum );
            if Fetching then
              DoCallback;
            if FCursorEof then
              Break;
          end;
        end
        else
        begin
          if Fetching then
            DoCallback;
          Break;
        end;
      end
      else
      if errcode = 100 then
      begin
        DoCloseOffToEof;
        if Fetching then
          DoCallback;
        Break;
      end
      else
      if errcode = ISC_DEADLOCK then
        try
          HandleException( Self );
          if Fetching then
            DoCallback;
        except
          DoCloseOffToEof;
          raise;
        end
      else
        try
          HandleException( Self );
        finally
          DoCloseOffToEof;
        end;
    end;
  end;
end;

function TIB_Dataset.SysAfterFetchCursorRow: boolean;
begin
  Result := true;
  if Assigned( FAfterFetchRow ) then
    FAfterFetchRow( Self );
  if Unidirectional and ( ControlsDisabledLevel > 0 ) then
    inherited ProcessLinkEvent( setFieldsDataChange, 0 );
end;

procedure TIB_Dataset.SysAfterFetchCursorEof;
begin
  if Assigned( FAfterFetchEof ) then
    FAfterFetchEof( Self );
end;

procedure TIB_Dataset.SysProcessCallback( Status: TIB_CallbackStatus );
begin
  if Assigned( FOnCallback ) then
  begin
    FOnCallback( Self, Status, CursorRowNum, FFetchingAborted );
    if FFetchingAborted then
      Inc( FFetchingAbortedGen );
  end
  else
    DefaultProcessCallback( Status );
end;

procedure TIB_Dataset.DefaultProcessCallback( Status: TIB_CallbackStatus );
begin
  case Status of
    csRefresh:
    begin
      if not Assigned( dlgCancelQuery ) then
      begin
        dlgCancelQuery := TdlgCancelQuery.Create( Application );
        with dlgCancelQuery as TdlgCancelQuery do
        begin
          DatasetToAbort := Self;
          if CallbackCaption <> '' then
            Caption := CallbackCaption
          else
            Caption := Application.Title;
          WindowState := wsNormal;
        end;
      end;
      with dlgCancelQuery as TdlgCancelQuery do
      begin
        lbRowNum.Caption := Format( M_Row_Num, [ CursorRowNum ] );
        Visible := true;
      end;
    end;
    csFinal:
    begin
      if Assigned( dlgCancelQuery ) then
      begin
        dlgCancelQuery.WindowState := wsNormal;
        dlgCancelQuery.Visible := false;
      end;
    end;
  end;
end;

procedure TIB_Dataset.BeginCallbackFreeze;
begin
  IB_Session.BeginCallbackFreeze;
end;

procedure TIB_Dataset.EndCallbackFreeze;
begin
  IB_Session.EndCallbackFreeze;
end;

procedure TIB_Dataset.BeginKeyDataFreeze;
begin
  Inc( FKeyDataFreezeLevel );
end;

procedure TIB_Dataset.EndKeyDataFreeze;
begin
  if KeyDataFreezeLevel > 0 then
    Dec( FKeyDataFreezeLevel );
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SysEditCursorRow;
begin
  if  (State = dssEdit) or
     ((State = dssInsert) and (IsPostRetaining or FHasPostRetained)) then
  begin
    if Assigned( OnCustomEdit ) then
      OnCustomEdit( Self )
    else
      SQL_EditRow;
  end
  else
    SysUtils.Abort;
end;

procedure TIB_Dataset.SysInsertCursorRow;
begin
  if State = dssInsert then
  begin
    if Assigned( OnCustomInsert ) then
      OnCustomInsert( Self )
    else
      SQL_InsertRow;
  end
  else
    SysUtils.Abort;
end;

procedure TIB_Dataset.SysDeleteCursorRow;
var
  CanDoDelete: boolean;
  SkipDelete: boolean;
begin
  CanDoDelete := false;
  SkipDelete := false;
  if State = dssDelete then
    CanDoDelete := true
  else
  if ( State = dssInsert ) and FHasPostRetained then
  begin
    CanDoDelete := true;
    if IB_Transaction.TransactionState in [ tsRollbackRetainingPending,
                                            tsRollbackPending,
                                            tsRollbackRefreshPending ] then
      SkipDelete := true;
  end;
  if CanDoDelete then
  begin
    if not SkipDelete then
    begin
      if Assigned( OnCustomDelete ) then
        OnCustomDelete( Self )
      else
        SQL_DeleteRow;
    end;
  end
  else
    SysUtils.Abort;
end;

procedure TIB_Dataset.SQL_DeleteRow;
begin
  FUpdateSQL.SQL_Delete;
end;

procedure TIB_Dataset.SQL_InsertRow;
begin
  FUpdateSQL.SQL_Insert;
end;

procedure TIB_Dataset.SQL_EditRow;
begin
  FUpdateSQL.SQL_Edit;
end;

function TIB_Dataset.SQL_LockRow: boolean;
begin
  if Assigned( OnCustomLockRow ) then
  begin
    Result := true;
    try
      OnCustomLockRow( Self );
    except
      on E: Exception do
      begin
        Result := false;
        Application.HandleException( E );
      end;
    end;
  end
  else
    Result := FUpdateSQL.SQL_Lock;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SysBeforeFieldDataChange( Sender: TIB_Row;
                                                AField: TIB_Column);
begin
  if Sender.RowType = rtField then
  begin
    case State of
      dssInactive: raise EIB_DatasetError.CreateWithSender( Self,
                                                            E_DatasetClosed );
      dssPrepared,
      dssBrowse:
      begin
        if ReadOnly then
          raise EIB_DatasetError.CreateWithSender( Self, E_DatasetReadOnly )
        else
        if Assigned( AField ) and AField.ReadOnly then
        begin
// I have decided to allow alteration of fields that the DML generating process
// will ignore anyway. This allows the KeyDescLinks assignments to be performed
// without causing an exception or taking the dataset into an editing mode.
          if ( not AField.Computed ) and ( not AField.IsCalculated ) then
            raise EIB_DatasetError.CreateWithSender( Self,
                                                     Format( E_FLD_READONLY,
                                                      [AField.FullFieldName] ));
        end
        else
        if Fields.RowState = rsNone then
          SysInsert
        else
          SysEdit;
      end;
    end;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SysLayoutChange( Sender: TObject );
begin
  inherited SysLayoutChange( Sender );
  if Sender = FieldsReadOnly then
  begin
    FUpdateSQL.SysUnprepare;
    SysStateChanged;
  end;
end;

procedure TIB_Dataset.API_OpenCursor( const ACursorName: string );
var
  SaveCW: word;
begin
  if FCursorIsOpen then
    raise EIB_DatasetError.CreateWithSender( Self, E_Cursor_is_already_open )
  else
  begin
    with IB_Session do
    begin
      asm fstcw [SaveCW] end;
      errcode := isc_dsql_set_cursor_name( @status,
                                           PstHandle,
                                           PChar( ACursorName ),
                                           0 {Unused Input } );
      asm fldcw [SaveCW] end;
      FCursorIsOpen := errcode = 0;
      if errcode <> 0 then
        HandleException( Self );
    end;
    if FCursorIsOpen then
      Inc( IB_transaction.FOpenCursors );
  end;
end;

procedure TIB_Dataset.API_CloseCursor;
var
  SaveCW: word;
begin
  if IB_Session <> nil then
    with IB_Session do
    begin
      if Psthandle^ <> FakePointer then
      begin
        asm fstcw [SaveCW] end;
        errcode := isc_dsql_free_statement( @status,
                                            PstHandle,
                                            DSQL_CLOSE );
        asm fldcw [SaveCW] end;
      end;
      if FCursorIsOpen then
      begin
        FCursorIsOpen := false;
        Dec( IB_transaction.FOpenCursors );
      end;
    end;
end;

function TIB_Dataset.API_FetchRow: isc_long;
var
  SaveCW: word;
begin
  with IB_Session do
  begin
    if CursorFields.PSQLDA.SQLd > 0 then
    begin
      asm fstcw [SaveCW] end;
      errcode := isc_dsql_fetch( @status,
                                 PstHandle,
                                 SQLDialect,
                                 CursorFields.PSQLDA );
      asm fldcw [SaveCW] end;
    end
    else
      errcode := 100;
    Result := errcode;
    if ( errcode <> 0 ) and ( errcode <> 100 ) then
      HandleException( Self );
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SetTransaction( AValue: TIB_Transaction );
begin
  if IB_Transaction <> AValue then
  begin
    if ( IB_Transaction <> nil ) then
      if IB_Transaction.FDatasetList <> nil then
        IB_Transaction.FDatasetList.Remove( Self );
    inherited SetTransaction( AValue );
    if ( IB_Transaction <> nil ) then
      if IB_Transaction.FDatasetList <> nil then
        IB_Transaction.FDatasetList.Add( Self );
  end;
end;

procedure TIB_Dataset.SetConnection( AValue: TIB_Connection );
begin
  if IB_Connection <> AValue then
  begin
    if ( IB_Connection <> nil ) then
      if IB_Connection.FDatasetList <> nil then
        IB_Connection.FDatasetList.Remove( Self );
    inherited SetConnection( AValue );
    if ( IB_Connection <> nil ) then
      if IB_Connection.FDatasetList <> nil then
        IB_Connection.FDatasetList.Add( Self );
  end;
end;

procedure TIB_Dataset.SetActive( Value: boolean );
begin
  if Value <> Active then begin
    if Value then begin
      Open;
    end else begin
      Close;
    end;
  end;
end;

procedure TIB_Dataset.CheckCursorName;
var
  Hour, Min, Sec, MSec: Word;
begin
  if FCursorName = '' then
  begin
    try
      Inc( IB_Session.FCursorNameSeed );
    except
      IB_Session.FCursorNameSeed := 1;
    end;
    DecodeTime( now, Hour, Min, Sec, MSec );
    FCursorname := 'C' +
                   IntToStr(cardinal(Self)) +
                   IntToStr(MSec) +
                   IntToStr(Sec) +
                   IntToStr(Min) +
                   IntToStr(IB_Session.FCursorNameSeed);
    FCursorFields.FCursorName := FCursorname;
  end;
end;

procedure TIB_Dataset.SysAfterPrepare;
var
  AStrings: TIB_StringList;
  ii: integer;
begin
  CheckCursorName;
  if ( not Assigned( FBindingCursor )) and RequestLive and ( not ReadOnly ) then
  begin
    AStrings := TIB_StringList.Create;
    try
      GetSQLForUpdateColumns( SQLForUpdate.Text, AStrings );
      if AStrings.Count > 0 then
        for ii := 0 to Fields.ColumnCount - 1 do
          with Fields.Columns[ii] do
//!!! Handle the case of table aliases used in the FOR UPDATE clause.
            FNotInForUpdate := AStrings.LinkIndex[ FullFieldName ] = -1;
    finally
      AStrings.Free;
    end;
  end;
  inherited SysAfterPrepare;
  if not Refreshing then
    ClearSearchingLinks;
  DoOrderingChanged;
end;

procedure TIB_Dataset.SysAfterUnprepare;
begin
  if FKeyLinksAutoDefined then
  begin
    FKeyLinks.Clear;
    FKeyLinksAutoDefined := false;
    FKeyLinksAreDBKEY := false;
  end;
  Fields.FRelationList.Clear;
  Params.FRelationList.Clear;
  FSQLOrderLinks.Clear;
  inherited SysAfterUnprepare;
  FCursorName := '';
  FCursorFields.FCursorName := '';
end;

function TIB_Dataset.GetCursorBof: boolean; begin Result := FCursorRowNum = 0;
                                                                          end;
function TIB_Dataset.GetBof:       boolean; begin Result := CursorBof;    end;
function TIB_Dataset.GetEof:       boolean; begin Result := CursorEof;    end;
function TIB_Dataset.GetRowNum:    longint; begin Result := CursorRowNum; end;
function TIB_Dataset.GetBofRowNum: longint; begin Result := 0; end;
function TIB_Dataset.GetEofRowNum: longint; begin Result := RowNum + 1; end;
procedure TIB_Dataset.SetRowNum( AValue: longint );
begin
  raise EIB_DatasetError.CreateWithSender( Self, E_ROWNUM_NOT_IMPLEMENTED );
end;
function TIB_Dataset.GetRecNo: longint;
begin Result := RowNum; end;
procedure TIB_Dataset.SetRecNo( AValue: longint );
begin RowNum := AValue; end;
function TIB_Dataset.GetSQLWhereChanged: boolean;
begin
  if Prepared and ( not Active ) and ( not SQLIsValid ) then
    Prepare;
  Result := Prepared and FSQLWhereChanged;
end;

{------------------------------------------------------------------------------}

function TIB_Dataset.IsUsingManualDML( UpdateKind: TIB_UpdateKind ): boolean;
begin
  Result := false;
  case UpdateKind of
    ukiModify: Result := Assigned( OnCustomEdit   ) or ( EditSQL.Count   > 0 );
    ukiInsert: Result := Assigned( OnCustomInsert ) or ( InsertSQL.Count > 0 );
    ukiDelete: Result := Assigned( OnCustomDelete ) or ( DeleteSQL.Count > 0 );
  end;
end;

function TIB_Dataset.GetCanModify: boolean;
begin
  Result := Prepared and ( not ( ReadOnly or ( PreventEditing and
                                               PreventInserting and
                                               PreventDeleting )));
  if Result and ( not ( State in [ dssEdit, dssInsert ] )) then
  begin
    if Fields.RowState = rsNone then
      Result := CanInsert
    else
      Result := CanEdit;
  end;
end;

function TIB_Dataset.GetCanEdit: boolean;
begin
  Result := Prepared and
            ( not ReadOnly ) and
            ( not PreventEditing ) and
            IsSelectSQL and
            ( Fields.RowState <> rsNone ) and
            ( State <> dssInsert );
  if Result then
  begin
    if not IsUsingManualDML( ukiModify ) then
    begin
      Result := RequestLive;
      if Result and SearchedEdits then
        Result := GetCanDoSearchedSQL;
    end;
  end;
  // Allow the developer to adjust the result.
  if Result and Assigned( OnGetCanModify ) then
    OnGetCanModify( Self, cmEdit, Result );
end;

function TIB_Dataset.GetCanInsert: boolean;
begin
  Result := Prepared and
            ( not ReadOnly ) and
            ( not PreventInserting ) and
            IsSelectSQL and
            (( MasterSource = nil ) or
            (( MasterSource.RowState <> rsNone ) and
             ( MasterSource.State <> dssDelete )));
  // It is possible for an insert to be performed.
  if Result then
  begin
    if not IsUsingManualDML( ukiInsert ) then
      Result := SysKeyRelation <> '';
  end;
  // Allow the developer to adjust the result.
  if Result and Assigned( OnGetCanModify ) then
    OnGetCanModify( Self, cmInsert, Result );
end;

function TIB_Dataset.GetCanDelete: boolean;
begin
  Result := Prepared and ( not ReadOnly ) and
                         ( not PreventDeleting ) and IsSelectSQL;
  if Result and ( State = dssInsert ) then
    Result := false;
  if Result and ( Fields.RowState = rsNone ) then
    Result := false;
  if Result then
  begin
    if not IsUsingManualDML( ukiDelete ) then
    begin
      Result := RequestLive and ( SysKeyRelation <> '' );
      if Result and SearchedDeletes then
        Result := GetCanDoSearchedSQL;
    end;
  end;
  if Result and Assigned( OnGetCanModify ) then
    OnGetCanModify( Self, cmDelete, Result );
end;

function TIB_Dataset.GetCanSearch: boolean;
begin
  Result := not PreventSearching;
end;

function TIB_Dataset.GetNeedToPost: boolean;
begin
  Result := State in [ dssEdit, dssInsert, dssDelete ];
end;

{------------------------------------------------------------------------------}

function TIB_Dataset.GetDataSourceCount: integer;
begin
  if FDataSourceList <> nil then
    Result := FDataSourceList.Count
  else
    Result := 0;
end;

function TIB_Dataset.GetDataSources( Index: integer ): TIB_DataSource;
begin
  if ( Index < 0 ) or ( Index >= DataSourceCount ) then
    Result := nil
  else
    Result := TIB_DataSource( FDataSourceList.Items[ Index ] );
end;

function TIB_Dataset.GetUnidirectional: boolean;
begin
  Result := true;
end;

procedure TIB_Dataset.SetReadOnly( AValue: boolean );
begin
  if FReadOnly <> AValue then
  begin
    FReadOnly := AValue;
    SysStateChanged;
  end;
end;

function TIB_Dataset.GetReadOnly: boolean;
begin
  Result := FReadOnly or
            ( not Assigned( IB_Transaction )) or
            IB_Transaction.ReadOnly;
  if Result and ( csDesigning in ComponentState ) then
    Result := FReadOnly;
end;

procedure TIB_Dataset.SetPreventEditing( AValue: boolean );
begin
  if PreventEditing <> AValue then
  begin
    FPreventEditing := AValue;
    SysStateChanged;
  end;
end;

procedure TIB_Dataset.SetPreventInserting( AValue: boolean );
begin
  if PreventInserting <> AValue then
  begin
    FPreventInserting := AValue;
    SysStateChanged;
  end;
end;

procedure TIB_Dataset.SetPreventDeleting( AValue: boolean );
begin
  if PreventDeleting <> AValue then
  begin
    FPreventDeleting := AValue;
    SysStateChanged;
  end;
end;

procedure TIB_Dataset.SetPreventSearching( AValue: boolean );
begin
  if PreventSearching <> AValue then
  begin
    FPreventSearching := AValue;
    SysStateChanged;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.DoBeforeOpen;
begin
  if not ( csLoading in ComponentState ) and
     not ( csDestroying in ComponentState ) then
    if Assigned( BeforeOpen ) then
      BeforeOpen( Self );
end;

procedure TIB_Dataset.DoAfterOpen;
begin
  if not ( csLoading in ComponentState ) and
     not ( csDestroying in ComponentState ) then
    if Assigned( AfterOpen ) then
      AfterOpen( Self );
end;

procedure TIB_Dataset.DoBeforeClose;
begin
  if not ( csLoading in ComponentState ) and
     not ( csDestroying in ComponentState ) then
    if Assigned( BeforeClose ) then
      BeforeClose( Self );
end;

procedure TIB_Dataset.DoAfterClose;
begin
  if not ( csLoading in ComponentState ) and
     not ( csDestroying in ComponentState ) then
    if Assigned( AfterClose ) then
      AfterClose( Self );
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.DoLinkStateChanged;
var
  ii: integer;
begin
  for ii := 0 to DataSourceCount - 1 do
     DataSources[ ii ].SysStateChanged;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.First;
begin
  BeginBusy( false );
  try
    DisableControls;
    try
      if Active then
        CheckBrowseMode
      else
        Prepared := true;
      SysFirst;
    finally
      EnableControls;
    end;
  finally
    EndBusy;
  end;
end;

procedure TIB_Dataset.KillCursor;
begin
  BeginBusy( false );
  try
    if not FCursorEof then
    begin
      API_CloseCursor;
      FCursorEof := true;
      IB_Transaction.CheckOAT;
      SysAfterFetchCursorEof;
    end;
  finally
    EndBusy;
  end;
end;

procedure TIB_Dataset.Last;
begin
  if Eof and Unidirectional then
    raise EIB_DatasetError.CreateWithSender( Self, E_AT_END_OF_Dataset )
  else
  begin
    BeginBusy( false );
    try
      DisableControls;
      try
        if Active then
          CheckBrowseMode
        else
          Prepared := true;
        SysLast;
      finally
        EnableControls;
      end;
    finally
      EndBusy;
    end;
  end;
end;

function TIB_Dataset.MoveBy( JumpRecs: longint ): longint;
begin
  BeginBusy( false );
  try
    DisableControls;
    try
      if not Prepared then
        Prepare;
      Result := SysMoveBy( JumpRecs );
    finally
      EnableControls;
    end;
  finally
    EndBusy;
  end;
end;

procedure TIB_Dataset.Next;
begin
  if Eof then
    raise EIB_DatasetError.CreateWithSender( Self, E_END_OF_Dataset )
  else
    MoveBy( 1 )
end;

procedure TIB_Dataset.Prior;
begin
  if Bof then
    raise EIB_DatasetError.CreateWithSender( Self, E_BEGINNING_OF_Dataset )
  else
    MoveBy( -1 )
end;

procedure TIB_Dataset.Edit;
begin
  if not Prepared then
    SysPrepare;
  if (( Fields.RowState = rsNone ) and CanInsert ) then
    SysInsert
  else
  if CanEdit then
    SysEdit
  else
  begin
    SysStateChanged;
    raise EIB_DatasetError.CreateWithSender( Self, E_Cannot_Edit_Row );
  end;
end;

procedure TIB_Dataset.Append;
begin
  if not Prepared then
    SysPrepare;
  if CanInsert then
  begin
    if not Unidirectional then
    begin
      BeginCallbackFetching;
      try
        if not BufferHasEof then
          FetchAll;
        if FetchingAborted then
          Exit;
      finally
        EndCallbackFetching;
      end;
      if not FetchingAborted then
        RowNum := BufferRowCount + 1;
    end;
    if not FetchingAborted then
      SysInsert;
  end
  else
  begin
    SysStateChanged;
    raise EIB_DatasetError.CreateWithSender( Self, E_Cannot_Insert_Row );
  end;
end;

procedure TIB_Dataset.Insert;
begin
  if not Prepared then
    SysPrepare;
  if CanInsert then
    SysInsert
  else
  begin
    SysStateChanged;
    raise EIB_DatasetError.CreateWithSender( Self, E_Cannot_Insert_Row );
  end;
end;

procedure TIB_Dataset.Delete;
begin
  if CanDelete then
    SysDelete
  else
  begin
    SysStateChanged;
    raise EIB_DatasetError.CreateWithSender( Self, E_Cannot_Delete_Row );
  end;
end;

procedure TIB_DataSet.CheckOperation( Operation: TIB_DataOperation;
                                      ErrorEvent: TIB_DataSetErrorEvent );
var
  Done: Boolean;
  Action: TIB_DataAction;
begin
  Done := False;
  repeat
    try
      Operation;
      Done := True;
    except
      on E: EIB_Error do
      begin
        Action := dacFail;
        if Assigned( ErrorEvent ) then
          ErrorEvent( Self, E, Action );
        if Action = dacFail then
          raise;
        if Action = dacAbort then
          SysUtils.Abort;
      end;
    end;
  until Done;
end;

procedure TIB_Dataset.Post;
begin
  if NeedToPost or ( State = dssSearch ) then
  begin
    BeginBusy( false );
    try
      SysPost( false, false );
    finally
      EndBusy;
    end;
  end
  else
  begin
    SysStateChanged;
    raise EIB_DatasetError.CreateWithSender( Self, E_CANNOT_POST_ROW );
  end;
end;

procedure TIB_Dataset.PostRetaining;
begin
  if NeedToPost or ( State = dssSearch ) then
  begin
    BeginBusy( false );
    try
      SysPost( false, true );
    finally
      EndBusy;
    end;
  end;
end;

procedure TIB_Dataset.Cancel;
begin
  if NeedToPost or ( State = dssSearch ) then
  begin
    BeginBusy( false );
    try
      SysCancel;
    finally
      EndBusy;
    end;
  end
  else
  begin
    SysStateChanged;
    raise EIB_DatasetError.CreateWithSender( Self, E_CANNOT_CANCEL_ROW );
  end;
end;

procedure TIB_Dataset.UpdateData;
begin
  ProcessLinkEvent( setParamsUpdateData, 0 );
  ProcessLinkEvent( setFieldsUpdateData, 0 );
end;

procedure TIB_Dataset.DataChange;
begin
  ProcessLinkEvent( setParamsDataChange, 0 );
  ProcessLinkEvent( setFieldsDataChange, 0 );
end;

procedure TIB_Dataset.ShowNearest( ARowNum: longint );
begin
  ProcessLinkEvent( setShowNearest, ARowNum )
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.AbortFetching;
begin
  if Fetching then
  begin
    try
      Inc( FFetchingAbortedGen );
    except
      FFetchingAbortedGen := -(MaxInt-1);
    end;
    FFetchingAborted := true;
  end;
end;

procedure TIB_Dataset.CheckRequiredFields;
var
  ii: Integer;
begin
  for ii := 0 to Fields.ColumnCount - 1 do
    with Fields[ii] do
      if IsNull and ( not ReadOnly ) and ( not Computed ) and Required then
      begin
        if ( State = dssInsert ) and
           ( not PreparedInserts ) and
           ( InsertSQL.Count = 0 ) and
           ( IsDefaulted ) then
          Continue
        else
        begin
          FocusControl;
          raise EIB_DatasetError.CreateWithSender( Self,
                                                   Format( E_Required_Field,
                                                   [DisplayName] ));
        end;
      end;
end;

function TIB_Dataset.RecordCount: longint;
begin
  BeginBusy( false );
  try
    Result := SysRecordCount;
  finally
    EndBusy;
  end;
end;

procedure TIB_Dataset.Search;
begin
  if CanSearch then
  begin
    BeginBusy( false );
    try
      if Fetching then
        AbortFetching;
      SysSearch;
    finally
      EndBusy;
    end;
  end
  else
    raise EIB_DatasetError.CreateWithSender( Self, E_Cannot_Search );
end;

procedure TIB_Dataset.SysSearch;
begin
  if State <> dssSearch then
  begin
    CheckTransaction( false );
    DisableControls;
    try
      SysClose;
      if State <> dssSearch then
      begin
        SysPrepare;
        SysBeforeSearch;
        SetState( dssSearch );
        if MasterSearch and
           Assigned( MasterDataset ) and
           ( MasterDataset.State <> dssSearch ) and
           ( msfSearchMasterOnSearch in MasterSearchFlags ) then
            MasterDataset.SysSearch;
        MasterToChildAction( mcaMasterSearchChanged );
        SysAfterSearch;
      end;
    finally
      EnableControls;
    end;
  end;
end;

procedure TIB_Dataset.SysMasterSearchChanged;
begin
  if MasterSearch and
     Assigned( MasterSource ) and
     ( MasterSource.State = dssSearch ) then
  begin
    if State <> dssSearch then
    begin
      FMasterSearchWasActive := Active;
      if CanSearch then
        Search;
    end;
  end
  else
  begin
    if State = dssSearch then
      SysPostSearch( true );
    if FMasterSearchWasActive and
       MasterSource.Active and
       ( not MasterSource.Eof ) and
       ( not MasterSource.Bof ) then
    begin
      Open;
      FMasterSearchWasActive := false;
    end;
  end;
end;

procedure TIB_Dataset.SysBeforeSearch;
begin
  DoBeforeSearch;
end;

procedure TIB_Dataset.SysAfterSearch;
begin
  DoAfterSearch;
end;

procedure TIB_Dataset.MasterToChildAction( AAction: TIB_MasterChildAction );
var
  ii: integer;
begin
  with IB_Session do if Assigned( Session_Datasets ) then
  begin
    for ii := 0 to Session_Datasets.Count - 1 do
    begin
      with TIB_Dataset( Session_Datasets.Items[ ii ] ) do
      begin
        if Assigned( MasterSource ) then
        begin
          if MasterSource.Dataset = Self then
          begin
            case AAction of
              mcaSaveSearch:
                if MasterSearch then
                  SaveSearch;
              mcaClearSearch:
                if MasterSearch then
                  ClearSearch;
              mcaRecallSearch:
                if MasterSearch then
                  RecallSearch;
              mcaRecallLastSearch:
                if MasterSearch then
                  RecallLastSearch;
              mcaWriteSearch:
                if MasterSearch then
                  WriteSearch( Self.FSearchCriteria );
              mcaReadSearch:
                if MasterSearch then
                  ReadSearch( Self.FSearchCriteria );
              mcaMasterSearchChanged:
                if MasterSearch then
                  SysMasterSearchChanged;
              mcaPost:
                SysPost( false, false );
              mcaCancelInsert:
                if State = dssInsert then
                  SysCancel;
            end;
          end;
        end;
      end;
    end;
  end;
end;

procedure TIB_Dataset.KeyToChildAction( AAction: TIB_KeyChildAction;
                                        Info: longint );
var
  ii, jj: integer;
  ADataset: TIB_Dataset;
  tmpKeyFlds: string;
  tmpKeyVals: string;
  tmpFlds: string;
  tmpVals: string;
begin
  with IB_Session do if Assigned( Session_Datasets ) then
  begin
    for ii := 0 to Session_Datasets.Count - 1 do
    begin
      ADataset := TIB_Dataset( Session_Datasets.Items[ ii ] );
      with ADataset do
      begin
        if Assigned( KeySource ) then
        begin
          if KeySource.Dataset = Self then
          begin
            case AAction of
              kcaUpdateKeyDescCalcFields:
              if ( KeyDescLinks.Count > 0 ) and
                 ( ADataset is TIB_BDataset ) then
              begin
                tmpKeyFlds := '';
                tmpKeyVals := '';
                for jj := 0 to KeyLinks.Count - 1 do
                begin
                  if jj > 0 then
                  begin
                    tmpKeyFlds := tmpKeyFlds + ';';
                    tmpKeyVals := tmpKeyVals + ';';
                  end;
                  tmpKeyFlds := tmpKeyFlds + KeyLinks.IndexNames[jj];
                  tmpKeyVals := tmpKeyVals + KeyLinks.IndexValues[jj];
                end;
                tmpFlds := '';
                tmpVals := '';
                for jj := 0 to KeyDescLinks.Count - 1 do
                begin
                  if jj > 0 then
                  begin
                    tmpFlds := tmpFlds + ';';
                    tmpVals := tmpVals + ';';
                  end;
                  tmpFlds := tmpFlds + KeyDescLinks.IndexNames[jj];
                  tmpVals := tmpVals + KeyDescLinks.IndexValues[jj];
                end;
                ADataset.Prepare;
                TIB_Row( Info ).Values[ tmpVals ] :=
                  TIB_BDataset( ADataset ).Lookup(
                    tmpKeyFlds, TIB_Row( Info ).Values[ tmpKeyVals ], tmpFlds );
              end;
            end;
          end;
        end;
      end;
    end;
  end;
end;

procedure TIB_Dataset.SaveSearch;
begin
  ProcessLinkEvent( setUpdateSearchCriteria, integer( setSaveCriteria ));
  MasterToChildAction( mcaSaveSearch );
end;

procedure TIB_Dataset.ClearSearch;
begin
  FAssignedSQLWhere := '';
  ProcessLinkEvent( setUpdateSearchCriteria, integer( setClearCriteria ));
  MasterToChildAction( mcaClearSearch );
end;

procedure TIB_Dataset.RecallSearch;
begin
  ProcessLinkEvent( setUpdateSearchCriteria, integer( setRecallCriteria ));
  MasterToChildAction( mcaRecallSearch );
end;

procedure TIB_Dataset.RecallLastSearch;
begin
  ProcessLinkEvent( setUpdateSearchCriteria, integer( setRecallLastCriteria ));
  MasterToChildAction( mcaRecallLastSearch );
end;

procedure TIB_Dataset.WriteSearch( AStrings: TStrings );
begin
  if not Assigned( AStrings ) then
    Exit;
  FSearchCriteria := AStrings;
  AStrings.BeginUpdate;
  try
    if ( not Assigned( MasterSource )) or
       ( not Assigned( MasterSource.Dataset.FSearchCriteria )) then
      AStrings.Clear;
    ProcessLinkEvent( setUpdateSearchCriteria, integer( setWriteCriteria ));
    MasterToChildAction( mcaWriteSearch );
  finally
    FSearchCriteria := nil;
    AStrings.EndUpdate;
  end;
end;

procedure TIB_Dataset.ReadSearch( AStrings: TStrings );
begin
  FSearchCriteria := AStrings;
  try
    ProcessLinkEvent( setUpdateSearchCriteria, integer( setReadCriteria ));
    MasterToChildAction( mcaReadSearch );
  finally
    FSearchCriteria := nil;
  end;
end;

procedure TIB_Dataset.SysSQLChange( Sender: TObject; var Unprepare: boolean );
var
  OldOrderingItemNo: integer;
  ii: integer;
begin
  inherited SysSQLChange( Sender, Unprepare );
  if Sender = FOrderingItems then
  begin
    if OrderingItemNo <> 0 then
    begin
      OldOrderingItemNo := OrderingItemNo;
      FOrderingItemNo := 0;
      OrderingItemNo := OldOrderingItemNo;
    end;
    Unprepare := false;
  end
  else
  if Sender = FOrderingLinks then
  begin
    if OrderingItemNo <> 0 then
      SysCheckOrderingLink;
    Unprepare := false;
    for ii := 0 to FieldCount - 1 do
      Fields[ii].FOrderingLinkItemNo := -999;
  end
  else
  if Sender = FSearchingLinks then
  begin
    if OrderingItemNo <> 0 then
    begin
      SysCheckSearchingLink;
    end;
    Unprepare := false;
  end
  else
  if ( Sender = FSQL ) and ( SQLSectionChanging in [ ssWhere, ssOrder ] ) then
  begin
    Unprepare := false;
    if not PreparingSQL then
      InvalidateSQL;
  end;
  if ( Sender = FSQL ) and
     ( not PreparingSQL ) and
     ( SQLForUpdate.Count > 0 ) then
    FRequestLive := true;
end;

{------------------------------------------------------------------------------}

function TIB_Dataset.SysRecordCount: longint;
begin
  Result := -1;
  if not Active then
    SysPrepare;
  if Prepared then
  begin
    if ( State = dssSearch ) and
       ( MasterSearch ) and
       ( MasterDataset <> nil ) and
       ( MasterDataset.State = dssSearch ) then
      Result := MasterDataset.SysRecordCount
    else
    begin
      if not FCursorRecordCountValid then
      begin
        FCursorRecordCount := SysGetCursorRecordCount;
        FCursorRecordCountValid := false;
        //IB_Transaction.Isolation <> tiCommitted; {Fix this optimization.}
      end;
      Result := FCursorRecordCount;
    end;
  end;
end;

function TIB_Dataset.SysGetCursorRecordCount: longint;
var
  PIn_DA,
  PCount_DA: PXSQLDA;
  CountSQL: string;
  nullind: smallint;
begin
  Result := -1;
  nullind := 0;
  if Prepared then
  begin
    CheckTransaction( false );
    SysBeforeExecute;
    SysPostSearch( false );
    if Assigned( FOnGetRecordCount ) then
      FOnGetRecordCount( Self, Result )
    else
    begin
      MakeCountSQL( ServerSQL, CountSQL );
      if ParamCount > 0 then
        PIn_DA := Params.PSQLDA
      else
        PIn_DA := nil;
      PCount_DA := AllocMem( XSQLDA_LENGTH( 1 ));
      try
        with PCount_DA^ do
        begin
          version := SQLDA_VERSION1;
          sqln := 1;
          sqld := 1;
          with sqlvar[ 0 ] do
          begin
            sqltype  := SQL_LONG;
            sqlscale := 0;
            sqllen   := 4;
            sqldata  := @Result;
            sqlind   := @nullind;
          end;
        end;
        SysExecImmed2( CountSQL, PIn_DA, PCount_DA );
      finally
        FreeMem( PCount_DA );
      end;
    end;
  end;
end;

procedure TIB_Dataset.SysEdit;
begin
  if CanEdit and ( not ( State in [ dssInsert, dssEdit ] )) then
  begin
    SysBeforeEdit;
    SysPost( false, false );
    CheckOperation( SysLockRow, FOnEditError );
    try
      SetState( dssEdit );
      SysMasterDataUpdate( nil );
      SysAfterEdit;
    except
      SysCancel;
      raise;
    end;
  end;
end;

procedure TIB_Dataset.SysLockRow;
begin
  if PessimisticLocking and ( not FCachedUpdates ) then
  begin
    if Assigned( IB_Transaction ) and
      ( IB_Transaction.LockWait or
        IB_Transaction.ServerAutoCommit ) then
      raise EIB_DatasetError.CreateWithSender( Self,
                                               E_Cannot_Pessimistic_Lock );
    if not IsRowLocked then
    begin
      FIsRowLocked := SQL_LockRow;
      if IsRowLocked then
        Inc( IB_Transaction.FPessimisticLockCount )
      else
        raise EIB_DatasetError.CreateWithSender( Self,
                                          '[' + Name + '] ' + E_Record_Locked );
    end;
  end;
end;

procedure TIB_Dataset.SysInsert;
begin
  if CanInsert and ( not FInsertPending ) then
  begin
    SysBeforeInsert;
    FInsertPending := true;
    FWasSearching := ( State = dssSearch ) or ( Active and FWasSearching );
    try
      SysPost( false, false );
      MasterToChildAction( mcaPost );
      try
        with Fields do
        try
          BeginUpdate;
          SysInsertRow;
          Fields.ClearBuffers( rsUnmodified );
          SetState( dssInsert );
          DoNewRecord;
          RefreshBuffers( false, false, true );
        finally
          EndUpdate( true );
        end;
        SysAfterInsert;
      except
        SysCancel;
        raise;
      end;
    finally
      FInsertPending := false;
    end;
  end;
end;

procedure TIB_Dataset.DoNewRecord;
begin
  SysGetDefaultValues;
  SysMasterDataUpdate( nil );
  SysGetGeneratorValues;
  if GetServerDefaults then
    SysGetServerDefaults( False );
  if Assigned( FOnNewRecord ) then
    FOnNewRecord( Self );
end;

procedure TIB_Dataset.DoOrderingChanged;
begin
  if Assigned( FOnOrderingChanged ) then
    FOnOrderingChanged( Self );
end;

procedure TIB_Dataset.SysGetDefaultValues;
var
  ii: integer;
  tmpCol: TIB_Column;
  tmpDef: string;
begin
  for ii := 0 to FieldCount - 1 do
  begin
    tmpCol := Fields[ii];
    if tmpCol.IsNull then
    begin
      if GetColumnDefaultValue( tmpCol, tmpDef ) then
        tmpCol.AsString := tmpDef;
    end;
  end;
end;

function TIB_Dataset.GetColumnDefaultValue(     AColumn: TIB_Column;
                                            var DefStr: string ): boolean;
var
  tmpIdx: integer;
  tmpStr: string;
begin
  Result := false;
  tmpStr := '';
  // First try for a dataset based default
  tmpIdx := DefaultValues.LinkIndex[ AColumn.FullFieldName ];
  if (tmpIdx < 0) and ( AColumn.FullFieldName <> AColumn.FullSQLName ) then
    tmpIdx := DefaultValues.LinkIndex[ AColumn.FullSQLName ];
  if (tmpIdx >= 0) then
  begin
    tmpStr := DefaultValues.IndexValues[tmpIdx];
    Result := true;
  end;
  // if not found try for a connection based default
  if not Result then
  begin
    with AColumn.Statement.IB_Connection do
    begin
      tmpIdx := DefaultValues.LinkIndex[ AColumn.FullFieldName ];
      if (tmpIdx < 0) and ( AColumn.FullFieldName <> AColumn.FullSQLName ) then
        tmpIdx := DefaultValues.LinkIndex[ AColumn.FullSQLName ];
      if (tmpIdx < 0) and (fetDomainName in IB_Connection.FieldEntryTypes) then
        tmpIdx := DefaultValues.LinkIndex[ AColumn.DomainName ];
      if tmpIdx >= 0 then
      begin
        tmpStr := DefaultValues.IndexValues[tmpIdx];
        Result := true;
      end;
    end;
  end;
  // if we found a default value, parse it for macros
  if Result then
  begin
    if (FAlwaysCallMacroSubstitute or Assigned( FOnMacroSubstitute )) then
      DefStr := IB_Parse.SubstMacros( tmpStr,
                                      SysSubstituteMacros,
                                      MacroBegin,
                                      MacroEnd )
    else
      DefStr := tmpStr;
  end;
end;

(*
function IsValidNumeric( const Default_Source: string;
                               Limit: integer ): boolean;
var
    iterator, DotLocation: smallint;
begin
   Result := True;
   DotLocation := 0;
   // The server always uses dot as decimal indicator
   for iterator := Length( Default_Source ) downto 1 do
   begin
      if Default_Source[ Iterator ] = '.' then
        if DotLocation > 0 then
          DotLocation := -1
        else
          DotLocation := Iterator;
      if not ( Default_Source[ Iterator ] in ['0'..'9'] ) then
        DotLocation := -1;
      if DotLocation = -1 then
        Break;
   end;
   {TODO: Compare with the precision of the field in dialect 3}
   if (( DotLocation < 0 )) or
      (( Length( Default_Source ) > Limit ) and ( DotLocation > 0 )) or
      (( Length( Default_Source ) > Limit-1 ) and ( DotLocation = 0 )) then
     Result := False
end;
*)

procedure TIB_Dataset.SysGetGeneratorValues;
var
  ii: integer;
  ColName, GenName: string;
  GenValue: ISC_INT64;
  tmpCol: TIB_Column;
begin
  with GeneratorLinks do
    for ii := 0 to Count - 1 do
    begin
      ColName := IndexNames[ ii ];
      GenName := IndexValues[ ii ];
      tmpCol := FieldByName( ColName );  //Could optimize this.
      if tmpCol.IsNull and GetGeneratorValue( IB_Connection,
                                              IB_Transaction,
                                              IB_Connection.mkIdent(GenName),
                                              1,
                                              GenValue ) then
      begin
        tmpCol.AsInt64 := GenValue;
        // Doing this just to be sure.
        if tmpCol.IsNull then
        begin
          tmpCol.IsNull := false;
          tmpCol.AsInt64 := GenValue;
        end;
      end;
    end;
  if IB_Connection.GeneratorLinks.Count > 0 then
    for ii := 0 to FieldCount - 1 do
    begin
      tmpCol := Fields[ii];
      if tmpCol.IsNull then
        with IB_Connection.GeneratorLinks do
        begin
          GenName := LinkValues[ tmpCol.FullFieldName ];
          if GenName = '' then
            GenName := LinkValues[ tmpCol.FullSQLName ];
          if GenName <> '' then
            if GetGeneratorValue( IB_Connection,
                                  IB_Transaction,
                                  IB_Connection.mkIdent(GenName),
                                  1,
                                  GenValue ) then
              tmpCol.AsInt64 := GenValue;
        end;
    end;
end;

procedure TIB_Dataset.SysGetServerDefaults( PreSetDefaults: boolean );
var
  ii: integer;
begin
  for ii := 0 to FieldCount - 1 do
  begin
    with Fields[ ii ] do
    begin
      if ( IsNull or (csDesigning in ComponentState) or PreSetDefaults ) and
         ( IsDefaulted ) then
      begin
        if FDefaultValue = '' then
          FDefaultValue := IB_Connection.
                             SchemaCache.
                               Defaults.LinkValues[ RelName + '.' + SQLName ];
        if ( not (csDesigning in Self.ComponentState)) and
           ( not PreSetDefaults ) then
        begin
          if FDefaultValue = 'NULL' then
            Clear
          else
            AsString := FDefaultValue;
        end
        else
        if PreSetDefaults then
          DefaultValues.LinkValues[ RelName + '.' + SQLName ] := FDefaultValue;
      end;
    end;
  end;
end;

procedure TIB_Dataset.ImportServerDefaults;
var
  PreparedState: boolean;
begin
  PreparedState := Prepared;
  if not Prepared then
    Prepare;
  DefaultValues.Clear;
  SysGetServerDefaults( True );
  if not PreparedState then
    Unprepare;
end;

procedure TIB_Dataset.SysInsertRow;
begin
  if Unidirectional then
    SysClose;
end;

procedure TIB_Dataset.SysDelete;
begin
  if CanDelete then
  begin
    SysBeforeDelete;
    SysCancel; // Clear out any pending edits.
    MasterToChildAction( mcaCancelInsert );
    SetState( dssDelete );
    if AutoPostDelete then
    begin
      try
        Post;
      except
        SysUpdateState;
        raise;
      end;
      SysAfterDelete;
    end
    else
      try
        SysAfterDelete;
      except
        SysCancel;
        raise;
      end;
  end;
end;

procedure TIB_Dataset.SysPostSearch( EndSearchMode: boolean );
begin
  if not IsPosting then
  begin
    Inc( FIsPostingLevel );
    try
      ProcessLinkEvent( setFieldsUpdateData, 0 );
      if Assigned( IB_Transaction ) and IB_Transaction.Started then
        SysPrepare;
      if IsSelectSQL and EndSearchMode and SQLWhereChanged then
        ProcessLinkEvent( setUpdateSearchCriteria,
                          integer( setSaveLastCriteria ));
      if EndSearchMode then
      begin
        if ( not FInsertPending ) and ( not FOpenPending ) then
        begin
          DisableControls;
          try
            Open;
            if not Unidirectional then
            begin
              ValidateRows( 1, 1 );
              if ( not Assigned( MasterSource )) and
                 BufferHasEof and
                 ( BufferRowCount = 0 ) then
              begin
                Search;
                raise EIB_DatasetError.CreateWithSender( Self,
                                                         E_NoRecordsFound );
              end;
            end;
          finally
            EnableControls;
          end;
        end
        else
          SetState( dssPrepared );
        MasterToChildAction( mcaMasterSearchChanged );
      end;
    finally
      Dec( FIsPostingLevel );
    end;
  end;
end;

procedure TIB_Dataset.SysCancelSearch;
begin
  ClearSearch;
  if Assigned( IB_Transaction ) and IB_Transaction.Started then
    SysPrepare;
  SetState( dssPrepared );
end;

procedure TIB_Dataset.SysPost( CancelUnmodified, IsRetaining: boolean );
begin
  if State = dssSearch then
    SysPostSearch( true )
  else
  if NeedToPost and
     (( not IsPosting ) or
     ( IsRetaining and ( not IsPostRetaining ))) then
  begin
    Inc( FIsPostingLevel );
    if IsRetaining then
      Inc( FIsPostRetainingLevel );
    try
      if State in [dssEdit, dssInsert] then
        ProcessLinkEvent( setFieldsUpdateData, 0 );
      try
        DisableControls;
        if State in [dssEdit, dssInsert] then
          SysMasterDataUpdate( nil );
        if (( CancelUnmodified ) or
            ( IB_Connection.ConnectionStatus in [ csDisconnectPending,
                                                  csDropPending ] )) and
           ( not IsRetaining ) and
           ( not Modified ) and
           ( State in [dssEdit, dssInsert] ) then
          SysCancel
        else
        begin
          SysBeforePost;
          if ( State in [dssEdit, dssInsert] ) and CheckRequired then
            CheckRequiredFields;
          if not IsRetaining then
            ProcessLinkEvent( setCheckBrowseMode, 0 );
          FPostedState := State;
          FPostToServerSucceeded := false;
          try
            SysExecPost( CancelUnmodified and ( not IsRetaining ));
            FPostToServerSucceeded := true;
          finally
            if FPostToServerSucceeded then
            begin
              try
                if not IsRetaining then
                  SysUpdateState;
                SysUpdateKeyLinksData;
              finally
                FHasPostRetained := IsRetaining;
                if IsRetaining then
                  Fields.SetRowState( rsModified );
                if Fields.PSQLDA.SQLn > Fields.PSQLDA.SQLd then
                  CalculateFields;
                if not FCachedUpdates then
                  case FPostedState of
                    dssInsert: Include( FDatasetFlags, dsfInsertWasPosted );
                    dssEdit:   Include( FDatasetFlags, dsfEditWasPosted   );
                    dssDelete: Include( FDatasetFlags, dsfDeleteWasPosted );
                  end;
                SysAfterPost;
              end;
            end;
          end;
        end;
      finally
        EnableControls;
      end;
    finally
      Dec( FIsPostingLevel );
      if IsRetaining then
        Dec( FIsPostRetainingLevel );
    end;
  end;
end;

function TIB_Dataset.GetIsPosting: boolean;
begin
  Result := FIsPostingLevel > 0;
end;

function TIB_Dataset.GetIsPostRetaining: boolean;
begin
  Result := FIsPostRetainingLevel > 0;
end;

function TIB_Dataset.GetCalculatingFields: boolean;
begin
  Result := inherited GetCalculatingFields;
  if not Result then
    Result := BufferFields.FCalculatingFields;
end;

procedure TIB_Dataset.SysExecPost( CancelUnmodified: boolean );
begin
  case State of
    dssEdit: CheckOperation( SysPostEditedRow, FOnPostError );
    dssInsert: if HasPostRetained then
        CheckOperation( SysPostEditedRow, FOnPostError )
      else
        CheckOperation( SysPostInsertedRow, FOnPostError );
    dssDelete: if DoConfirmDelete then
      begin
      // This should clean out cached inserts for the child datasets as well!!!
        MasterToChildAction( mcaCancelInsert );
        CheckOperation( SysPostDeletedRow, FOnDeleteError );
      end
      else
        SysUtils.Abort;
  end;
end;

procedure TIB_Dataset.SysUpdateState;
begin
  if Active then
    SetState( dssBrowse )
  else
  if Prepared then
  begin
    if FWasSearching and
       ( FRefiningIncSearch = 0 ) and
       ( not FUnpreparing ) and
       ( not Refreshing ) and
       ( not (csDestroying in ComponentState)) and
       ( not IB_Transaction.ClosePending ) and
       ( ClosePending or
       ( IsCancelling and ( CancelledState = dssInsert ))) and
       CanSearch then
      Search
    else
      SetState( dssPrepared );
  end
  else
    SetState( dssInactive );
  SysClearLock;
end;

procedure TIB_Dataset.SysCancel;
begin
  if State = dssSearch then
    SysCancelSearch
  else
  if NeedToPost and ( not IsCancelling ) then
  begin
    FIsCancelling := true;
    try
      SysBeforeCancel;
      FCancelledState := State;
      case State of
        dssEdit: SysCancelEditedRow;
        dssInsert:
        begin
          MasterToChildAction( mcaCancelInsert );
          if FHasPostRetained then
            SysDeleteCursorRow
          else
            SysCancelInsertedRow;
        end;
        dssDelete: SysCancelDeletedRow;
      end;
      try
        SysUpdateState;
      finally
        FHasPostRetained := false;
        FIsCancelling := false;
        SysAfterCancel;
      end;
    finally
      FIsCancelling := false;
    end;
  end;
end;

procedure TIB_Dataset.SysClearLock;
begin
  if IsRowLocked and Assigned( IB_transaction ) then
    with IB_Transaction do
      if ( PostPendingCount = 0 ) and
         ( TransactionState = tsInactive ) then
      begin
        try
          LosePoint;
        except
          SavePoint;
        end;
      end;
end;

procedure TIB_Dataset.SysFirst;
begin
  SysBeforeScroll;
  if Active then
    SysClose;
  SysOpen;
  if CursorBof and ( not CursorEof ) then
    SysFetchNext;
  SysAfterScroll;
end;

procedure TIB_Dataset.SysLast;
begin
  SysBeforeScroll;
  SysOpen;
  SysFetchAll( 0 );
  SysAfterScroll;
end;

function TIB_Dataset.SysMoveBy( JumpRecs: longint ): longint;
begin
  SysBeforeScroll;
  Result := SysCursorMoveByRow( JumpRecs );
  if Result > 0 then
    SysAfterScroll;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SysUpdateKeyData;
begin
// Abstract.
end;

procedure TIB_Dataset.SysPostEditedRow;
begin
  Fields.PostBlobBuffers;
  try
    SysUpdateKeyData( false );
    SysEditCursorRow;
  except
    Fields.AfterPostBuffers( false );
    raise;
  end;
  Fields.AfterPostBuffers( true );
  Fields.RefreshBuffers( false, false, true );
//Fields.SysAfterModify( nil );
end;

procedure TIB_Dataset.SysPostInsertedRow;
begin
  Fields.PostBlobBuffers;
  try
    SysUpdateKeyData( false );
    SysInsertCursorRow;
  except
    Fields.AfterPostBuffers( false );
    raise;
  end;
  Fields.AfterPostBuffers( true );
  if Unidirectional then
    Fields.ClearBuffers( rsNone )
  else
  begin
    Fields.RefreshBuffers( false, false, true );
//    Fields.SysAfterModify( nil );
  end;
end;

procedure TIB_Dataset.SysPostDeletedRow;
begin
  Fields.CancelBuffers( true );
  SysDeleteCursorRow;
  if Unidirectional then
    Fields.ClearBuffers( rsNone );
end;

procedure TIB_Dataset.SysCancelEditedRow;
begin
  Fields.CancelBuffers( false );
end;

procedure TIB_Dataset.SysCancelInsertedRow;
begin
  Fields.CancelBuffers( true );
  if Unidirectional then
    Fields.ClearBuffers( rsNone );
end;

procedure TIB_Dataset.SysCancelDeletedRow;
begin
  Fields.CancelBuffers( false );
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.BeginCallbackFetching;
var
  CurTicks: DWORD;
begin
  try
    CurTicks := GetTickCount;
    if FCallbackFetchingLevel = 0 then
      FCallbackInitTick := CurTicks;
    if not Fetching then
    begin
      FFetching := true;
      FFetchingAborted := false;
      FCallbackRefreshTick := CurTicks;
      try
        if ( CallbackInc >= 0 ) and ( CallbackFreezeLevel = 0 ) then
          SysProcessCallback( csInit );
      finally
        BeginBusy(( CallbackInc >= 0 ) and ( CallbackFreezeLevel = 0 ));
      end;
    end;
  finally
    Inc( FCallbackFetchingLevel );
  end;
end;

procedure TIB_Dataset.EndCallbackFetching;
begin
  Dec( FCallbackFetchingLevel );
  if Fetching and ( FCallbackFetchingLevel = 0 ) then
  begin
    FFetching := false;
    try
      EndBusy;
    finally
      try
        if ( CallbackInc >= 0 ) and ( CallbackFreezeLevel = 0 ) then
          SysProcessCallback( csFinal );
      finally
        if FetchingAborted then
        begin
          StateChanged;
          FFetchingAborted := false;
        end;
      end;
    end;
  end;
end;

function TIB_Dataset.SysCursorMoveByRow( JumpRecs: longint ): longint;
var
  ii: integer;
  curScanning: integer;
  curCursorGen: dword;
  curRowNum: longint;
  FAGen: integer;
begin
  Result := 0;
  if Unidirectional and ( JumpRecs < 0 ) then
    raise EIB_StatementError.CreateWithSender( Self, E_CURSOR_UNI )
  else
  if JumpRecs = 0 then
    CursorFields.SysAfterModify( nil )
  else
  if ( FActive or SysOpen ) then
  begin
    if ( JumpRecs > 0 ) and ( not CursorEof ) then
    begin
      FAGen := FetchingAbortedGen; 
      try
        if FScanningLevel = 0 then
          FMaxScanLevel := 0;
        Inc( FScanningLevel );
        Inc( FMaxScanLevel );
        curScanning := FScanningLevel;
        curCursorGen := FCursorGen;
        if JumpRecs = 1 then
        begin
          ii := 0;
          curRowNum := CursorRowNum;
          SysFetchNext;
          Inc( ii, CursorRowNum - curRowNum );
          if ( FAGen <> FetchingAbortedGen ) or
             ( curCursorGen <> FCursorGen ) then
            Result := 0
          else
            Result := ii;
        end
        else
        begin
          try
            BeginCallbackFetching;
            try
              DisableControls;
              ii := 0;
              repeat
                curRowNum := CursorRowNum;
                SysFetchNext;
                Inc( ii, CursorRowNum - curRowNum );
                Result := ii;
                if ( FAGen <> FetchingAbortedGen ) or
                   ( curCursorGen <> FCursorGen ) then
                begin
                  Result := 0;
                  Break;
                end;
                if curScanning <> FMaxScanLevel then
                  Inc( curScanning );
              until CursorEof or ( ii >= JumpRecs );
            finally
              EnableControls;
            end;
          finally
            EndCallbackFetching;
          end;
        end;
      finally
        Dec( FScanningLevel );
      end;
    end;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SysBeforeOpen;
begin
  if not Refreshing then
    DoBeforeOpen;
  if NeedToPost then
    SysPost( false, false );
end;

procedure TIB_Dataset.SysAfterOpen;
begin
  if not Refreshing then
    DoAfterOpen;
end;

procedure TIB_Dataset.SysBeforeClose;
begin
  if not Refreshing then
    DoBeforeClose;
end;

procedure TIB_Dataset.SysAfterClose;
begin
  if not Refreshing then
    DoAfterClose;
end;

procedure TIB_Dataset.SysBeforeEdit;
begin
  DoBeforeEdit;
end;

procedure TIB_Dataset.SysAfterEdit;
begin
  DoAfterEdit;
end;

procedure TIB_Dataset.SysBeforeInsert;
begin
  DoBeforeInsert;
end;

procedure TIB_Dataset.SysAfterInsert;
begin
  DoAfterInsert;
end;

procedure TIB_Dataset.SysBeforeDelete;
begin
  DoBeforeDelete;
  if State = dssEdit then
    SysCancel;
end;

procedure TIB_Dataset.SysAfterDelete;
begin
  DoAfterDelete;
end;

procedure TIB_Dataset.SysBeforePost;
begin
  DoBeforePost;
end;

procedure TIB_Dataset.SysAfterPost;
begin
  DoAfterPost;
end;

procedure TIB_Dataset.SysBeforeCancel;
begin
  DoBeforeCancel;
end;

procedure TIB_Dataset.SysAfterCancel;
begin
  DoAfterCancel;
end;

{------------------------------------------------------------------------------}

function TIB_Dataset.GetCanScroll: boolean;
begin
  Result := Refreshing or SysGetCanScroll;
  if Result and Assigned( FOnGetCanScroll ) then
    OnGetCanScroll( Self, Result );
end;

function TIB_Dataset.SysGetCanScroll: boolean;
begin
  Result := ( BufferActive or ( State = dssSearch )) and
            (( KeySource = nil ) or
             ( FKeyChildUpdatingLevel > 0 ) or
             ( KeySource.State = dssSearch ) or
               KeySource.CanModify );
end;

procedure TIB_Dataset.SysBeforeScroll;
begin
  if Prepared then
  begin
    DoBeforeScroll;
    CheckCanScroll;
    if Active then
      CheckBrowseMode
    else
    if NeedToPost then
      SysPost( true, false );
    if ( not Refreshing ) and
       ( not ( csDestroying in ComponentState )) and
       FIB_KeyDataLink.Prepared then
      if ( KeyChildUpdatingLevel = 0 ) then
        try
          BeginKeyDataFreeze;
          if not FIB_KeyDataLink.Modify then
            SysUtils.Abort;
        finally
          EndKeyDataFreeze;
        end;
  end;
end;

procedure TIB_Dataset.SysAfterScroll;
begin
  if ( not Unidirectional ) and ( ControlsDisabledLevel > 0 ) then
    FWasScrolled := true
  else
  if ( not Refreshing ) or Unidirectional then
  begin
    if FIsRowLocked then
    begin
      FIsRowLocked := false;
      Dec( IB_Transaction.FPessimisticLockCount );
      IB_Transaction.CheckOAT;
    end;
    if Assigned( KeySource ) then
    begin
      if ( MasterDataChangeLevel = 0 ) and
         ( not OpenPending ) then
        SysUpdateKeyLinksData;
    end;
    if ( Fields.RowState <> FLastRowState ) and ( not Fetching ) then
    begin
      SysStateChanged;
      FLastRowState := Fields.RowState;
      if Unidirectional then
        DataChange;
    end;
    DoAfterScroll;
  end;
end;

procedure TIB_Dataset.SysUpdateKeyLinksData;
var
  ii, jj: integer;
  tmpCol: TIB_Column;
  keyCol: TIB_Column;
  tmpCanModifyKeyLinkField: boolean;
  wasExcp: boolean;
begin
  if ( not ( csDestroying in ComponentState )) and
     ( not ( csDesigning in ComponentState )) and
     Prepared and
     Assigned( KeyDataset ) and
     KeyDataset.Prepared and
     ( not Refreshing ) then
  begin
    if KeyDataset.State = dssSearch then
      KeyDataset.InvalidateSQL
    else
    begin
      if KeyChildUpdatingLevel = 0 then
      begin
        wasExcp := false;
        BeginKeyDataFreeze;
        KeyDataset.Fields.BeginUpdate;
        try
          try
            CheckKeyLinksMaps;
            CheckMasterLinksMaps;
            for ii := 0 to KeyLinks.Count - 1 do
            begin
              tmpCanModifyKeyLinkField := true;
              tmpCol := TIB_Column( KeyLinks.Objects[ ii ] );
              if tmpCol <> pointer(-1) then
              begin
                for jj := 0 to MasterLinks.Count - 1 do
                begin
                  if tmpCol = MasterLinks.Objects[jj] then
                  begin
                    tmpCanModifyKeyLinkField := false;
                    Break;
                  end;
                end;
                if tmpCanModifyKeyLinkField then
                begin
                  keyCol := TIB_Column( FKeyLinksFieldsMap[ ii ] );
                  if keyCol <> pointer(-1) then
                    keyCol.Assign( tmpCol );
                end;
              end;
            end;
            if KeyDataset.NeedToPost then
            begin
              for ii := 0 to KeyDescLinks.Count - 1 do
              begin

                tmpCol := TIB_Column( KeyDescLinks.Objects[ ii ] );
                if tmpCol <> pointer(-1) then
                begin

                  keyCol := TIB_Column( FKeyDescLinksFieldsMap[ ii ] );
                  if keyCol <> pointer(-1) then
                  begin
                    keyCol.Assign( tmpCol );
                  end;
                end;
              end;
            end;
          except
            wasExcp := true;
            raise;
          end;
        finally
          KeyDataset.Fields.EndUpdate( true );
          EndKeyDataFreeze;
          if wasExcp then
            SysKeyDataChange( nil );
        end;
      end;
    end;
  end;
end;

{------------------------------------------------------------------------------}

procedure LaunchDSEvent( const DS: TIB_Dataset;
                         const EV: TIB_DatasetEvent );
begin
  if Assigned( EV ) and ( not ( csLoading in DS.ComponentState )) and
                        ( not ( csDestroying in DS.ComponentState )) then
    EV( DS );
end;
procedure TIB_Dataset.DoBeforeEdit;
begin LaunchDSEvent( Self, BeforeEdit ); end;
procedure TIB_Dataset.DoAfterEdit;
begin LaunchDSEvent( Self, AfterEdit ); end;
procedure TIB_Dataset.DoBeforeSearch;
begin LaunchDSEvent( Self, BeforeSearch ); end;
procedure TIB_Dataset.DoAfterSearch;
begin LaunchDSEvent( Self, AfterSearch ); end;
procedure TIB_Dataset.DoBeforeInsert;
begin LaunchDSEvent( Self, BeforeInsert ); end;
procedure TIB_Dataset.DoAfterInsert;
begin LaunchDSEvent( Self, AfterInsert ); end;
procedure TIB_Dataset.DoAfterDelete;
begin LaunchDSEvent( Self, AfterDelete ); end;
procedure TIB_Dataset.DoBeforeDelete;
begin LaunchDSEvent( Self, BeforeDelete ); end;
procedure TIB_Dataset.DoBeforePost;
begin LaunchDSEvent( Self, BeforePost ); end;
procedure TIB_Dataset.DoAfterPost;
begin LaunchDSEvent( Self, AfterPost ); end;
procedure TIB_Dataset.DoBeforeCancel;
begin LaunchDSEvent( Self, BeforeCancel ); end;
procedure TIB_Dataset.DoAfterCancel;
begin LaunchDSEvent( Self, AfterCancel ); end;
procedure TIB_Dataset.DoAfterScroll;
begin LaunchDSEvent( Self, AfterScroll ); end;
procedure TIB_Dataset.DoBeforeScroll;
begin LaunchDSEvent( Self, BeforeScroll ); end;

{------------------------------------------------------------------------------}
{  Buffering Methods                                                           }
{------------------------------------------------------------------------------}
{  At this level the buffering is made to appear as a simple cursor.           }
{  Descendant classes will override this behavior and add in more              }
{  functionality and/or replace this functionality.                            }
{------------------------------------------------------------------------------}

function TIB_Dataset.GetBufferRowNum: longint;
begin
  if BufferFields.RowState <> rsNone then
    Result := 1
  else
    Result := 0;
end;

procedure TIB_Dataset.SetBufferRowNum( AValue: longint );
begin
// Abstract action at this level.
end;

function TIB_Dataset.GetBufferBof: boolean;
begin
  Result := CursorBof;
end;

function TIB_Dataset.GetBufferEof: boolean;
begin
  Result := CursorEof or
            (( not Active ) and (( BufferRowNum > BufferRowCount ) or
                                 ( BufferRowCount = 0 )));
end;

function TIB_Dataset.GetBufferFields: TIB_Row;
begin
  Result := CursorFields;
end;

function TIB_Dataset.GetBufferRowCount: longint;
begin
  if BufferFields.RowState <> rsNone then Result := 1 else
                                          Result := 0;
end;

function TIB_Dataset.GetBufferHasBof: boolean;
begin
  Result := true;
end;

function TIB_Dataset.GetBufferHasEof: boolean;
begin
  Result := CursorEof or FWasSingleton;
end;

procedure TIB_Dataset.InvalidateRows;
begin
  if not Refreshing then
  begin
    if not NeedToPost then
      SysMoveBy( 0 );
    ProcessLinkEvent( setInvalidateRows, InvalidNodePos );
  end;
end;

procedure TIB_Dataset.InvalidateRowNum( ARowNum: longint );
begin
  if RowNum = ARowNum then
    if not NeedToPost then
      SysMoveBy( 0 );
  ProcessLinkEvent( setInvalidateRows, ARowNum );
end;

function TIB_Dataset.InvalidateBookmark( const ABookmark: string ): boolean;
begin
// Abstract at this level.
  raise EIB_DatasetError.CreateWithSender( Self, E_Not_implemented );
end;

function TIB_Dataset.ValidateRows( Start, Finish: longint ): boolean;
begin
// Abstract at this level.
  raise EIB_DatasetError.CreateWithSender( Self, E_Not_implemented );
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.SetDefaultValues( AValue: TIB_StringList );
begin
  FDefaultValues.Assign( AValue );
end;

function TIB_Dataset.DoConfirmDelete: boolean;
begin
  Result := true;
  if Assigned( FOnConfirmDelete ) then
    FOnConfirmDelete( Self, Result )
  else
  if ConfirmDeletePrompt.Count > 0 then
    Result := MessageDlg( ConfirmDeletePrompt.Text,
                          mtWarning,
                          mbOkCancel,
                          0 ) = idOk; //mrOk;
end;

procedure TIB_Dataset.DisableControls;
begin
  Inc( FControlsDisabledLevel );
end;

procedure TIB_Dataset.EnableControls;
begin
  Dec( FControlsDisabledLevel );
  if FControlsDisabledLevel = 0 then
  begin
    if FWasStateChange then
    begin
      SysStateChanged;
      ProcessLinkEvent( setFieldsDataChange, 0 );
    end; // Don't make an else here.
    if FWasDataChange then
      ProcessLinkEvent( setFieldsDataChange, 0 );
    if FWasScrolled then
    begin
      FWasScrolled := false;
      SysAfterScroll;
    end;
  end;
end;

procedure TIB_Dataset.SysStateChanged;
begin
  if not ( csDestroying in ComponentState ) then
  begin
    if ( not Unidirectional ) and ( ControlsDisabledLevel > 0 ) then
      FWasStateChange := true
    else
    begin
      FWasStateChange := false;
      if State = dssPrepared then
        FLastRowState := rsNone;
      if ( State <> dssPrepared ) or ( not FInsertPending ) then
      begin
        DoLinkStateChanged;
        if FRefreshDML then
        begin
          Refresh;
          FRefreshDML := False;
        end;
      end;
    end;
  end;
end;

// This function is used to try and prevent master-detail cascades
// from resetting the search criteria during the PostSearch process...
function IsSearchNotPosting( DSet: TIB_Dataset ): boolean;
begin
  Result := false;
  if DSet.State = dssSearch then
    if not DSet.IsPosting then
      if assigned( DSet.MasterDataset ) and DSet.MasterSearch then
        Result := IsSearchNotPosting( DSet.MasterDataset )
      else
        Result := true;
end;

procedure TIB_Dataset.ProcessLinkEvent( AEvent: TIB_StatementEventType;
                                        Info: integer );
begin
  if AEvent = setFieldsDataChange then
  begin
    if Assigned( KeyDataset ) and ( KeyDataset.State = dssSearch ) then
    begin
      if IsSearchNotPosting( KeyDataset ) then
        IB_KeyDataLink.SetSearchFromKeyData;
    end;
    if not ( csDestroying in ComponentState ) then
    begin
      if ( not Unidirectional ) and ( ControlsDisabledLevel > 0 ) then
        FWasDataChange := true
      else
      begin
        FWasDataChange := false;
        inherited ProcessLinkEvent( AEvent, Info );
      end;
    end;
  end
  else
    inherited ProcessLinkEvent( AEvent, Info );
end;

{------------------------------------------------------------------------------}

function TIB_Dataset.GetCanDoSearchedSQL: boolean;
var
  ii: integer;
  tmpRelN,
  tmpRelA,
  tmpSKR: string;
begin
  Result := false;
  if ( SysKeyRelation <> '' ) and
     ( KeyFields.ColumnCount > 0 ) then
  begin
    Result := true;
    tmpRelN := KeyFields[ 0 ].RelName;
    tmpRelA := KeyFields[ 0 ].RelAliasName;
    for ii := 1 to KeyFields.ColumnCount - 1 do
    begin
      if ( AnsiCompareText( tmpRelN, KeyFields[ii].RelName      ) <> 0 ) and
         ( AnsiCompareText( tmpRelA, KeyFields[ii].RelAliasName ) <> 0 ) then
      begin
        Result := false;
        Break;
      end;
      if ( tmpRelN = '' ) then
      begin
        Result := false;
        Break;
      end;
    end;
    if Result then
    begin
      tmpSKR := SysKeyRelation;
      if Pos( '"', tmpSKR  ) = 0 then tmpSKR  := '"' + tmpSKR  + '"';
      if Pos( '"', tmpRelN ) = 0 then tmpRelN := '"' + tmpRelN + '"';
      if Pos( '"', tmpRelA ) = 0 then tmpRelA := '"' + tmpRelA + '"';
      Result := ( AnsiCompareText( tmpRelN, tmpSKR ) = 0 ) or
                ( AnsiCompareText( tmpRelA, tmpSKR ) = 0 );
    end;
  end;
end;

function TIB_Dataset.GetSearchedDeletes: boolean;
begin
  Result := FUpdateSQL.SearchedDeletes;
end;

procedure TIB_Dataset.SetSearchedDeletes( AValue: boolean );
begin
  if SearchedDeletes <> AValue then
  begin
    FUpdateSQL.SearchedDeletes := AValue;
    SysStateChanged;
  end;
end;

function TIB_Dataset.GetSearchedEdits: boolean;
begin
  Result := FUpdateSQL.SearchedEdits;
end;

procedure TIB_Dataset.SetSearchedEdits( AValue: boolean );
begin
  if SearchedEdits <> AValue then
  begin
    FUpdateSQL.SearchedEdits := AValue;
    SysStateChanged;
  end;
end;

function TIB_Dataset.GetPreparedEdits: boolean;
begin
  Result := FUpdateSQL.PreparedEdits;
end;

procedure TIB_Dataset.SetPreparedEdits( AValue: boolean );
begin
  if PreparedEdits <> AValue then
  begin
    FUpdateSQL.PreparedEdits := AValue;
    SysStateChanged;
  end;
end;

function TIB_Dataset.GetPreparedInserts: boolean;
begin
  Result := FUpdateSQL.PreparedInserts;
end;

procedure TIB_Dataset.SetPreparedInserts( AValue: boolean );
begin
  if PreparedInserts <> AValue then
  begin
    FUpdateSQL.PreparedInserts := AValue;
    SysStateChanged;
  end;
end;

function TIB_Dataset.GetKeyFields: TIB_Row;
begin
  if FCursorKeyFieldCount > 0 then
    Result := CursorKeyFields
  else
    Result := CursorFields;
end;

function TIB_Dataset.KeyFieldByName( const AFieldName: string ): TIB_Column;
begin
  Result := KeyFields.ByName( AFieldName );
end;

function TIB_Dataset.GetUpdatesPending: boolean;
begin
  Result := false;
end;

function TIB_Dataset.GetUpdateStatus: TIB_UpdateStatus;
begin
  Result := ustUnmodified;
end;

{------------------------------------------------------------------------------}

procedure TIB_Dataset.APIFirst;
begin
  with IB_Session, FCursorFields do
  begin
    SysPrepare;
    API_CloseCursor;
    API_Execute;
    API_OpenCursor( FCursorName );
    FActive := true;
    FCursorRowNum := 0;
    CursorFields.CleanBuffers( true );
    FCursorEof := false;
    APINext;
  end;
end;

procedure TIB_Dataset.APINext;
var
  SaveCW: word;
begin
  with IB_Session, CursorFields do
  begin
    if ( MaxRows > 0 ) and ( FCursorRowNum = MaxRows ) then
      errcode := 100
    else
    begin
      if ( BlobCount + ArrayCount > 0 ) then
        ClearBlobNodes( true );
      asm fstcw [SaveCW] end;
      errcode := isc_dsql_fetch( @status,
                                 PstHandle,
                                 SQLDialect,
                                 PSQLDA );
      asm fldcw [SaveCW] end;
    end;
    Inc( FCursorRowNum );
    if errcode = 0 then
      RefreshBuffers( true, true, true )
    else
    if errcode = 100 then
    begin
      SetRowState( rsNone );
      FCursorEof := true;
      API_CloseCursor;
    end
    else
      try
        HandleException( Self );
      finally
        API_CloseCursor;
      end;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Cursor.Next;
begin
  if FActive or SysOpen then
  begin
    if CursorEof then
      raise EIB_DatasetError.CreateWithSender( Self, E_END_OF_Dataset )
    else
    begin
      BeginBusy( false );
      try
        SysBeforeScroll;
        SysFetchNext;
        SysAfterScroll;
      finally
        EndBusy;
      end;
    end;
  end;
end;

procedure TIB_Cursor.APIFirst;
begin
  inherited APIFirst;
end;

procedure TIB_Cursor.APINext;
begin
  inherited APINext;
end;

// IBA_BindingCursor.INT

procedure TIB_BindingCursor.FreeServerResources;
begin
  Close;
  inherited FreeServerResources;
  FHavePrepare := false;
end;

procedure TIB_BindingCursor.DoCalculateField( ARow: TIB_Row;
                                              AField: TIB_Column );
begin
  FBDataset.DoCalculateField( ARow, AField );
end;

function TIB_BindingCursor.GetStatementType: TIB_StatementType;
begin
  if HavePrepare then
    Result := inherited GetStatementType
  else
    Result := stSelect;
end;

procedure TIB_BindingCursor.SysPreparedChanged;
begin
  FProblemInBindingCursor := false;
  inherited SysPreparedChanged;
end;

function TIB_BindingCursor.SysAllocate: boolean;
begin
  if NeedPrepare then
    Result := inherited SysAllocate
  else
  begin
    Result := true;
    PstHandle^ := FakePointer;
  end;
end;

function TIB_BindingCursor.API_Prepare(     Text: PChar;
                                        var InVar,
                                            OutVar: smallint ): integer;
begin
  if NeedPrepare then
  begin
    Result := inherited API_Prepare( Text, InVar, OutVar );
    FHavePrepare := Result = 0;
  end
  else
  begin
    FHavePrepare := false;
    if PstHandle^ <> FakePointer then
    begin
      SysDeallocate( true );
      SysAllocate;
    end;
    OutVar := BDataset.CursorFields.PSQLDA.SQLd;
    InVar := FSysParamNames.Count;
    Result := 0;
  end;
end;

procedure TIB_BindingCursor.SysDescribeVARList( ARow: TIB_Row );
var
  ii, jj: integer;
  tmpCol: TIB_Column;
begin
  if NeedPrepare then
    inherited SysDescribeVARList( ARow )
  else
  begin
    with ARow do
    begin
      case RowType of
        rtParam:
        begin
          jj := 0;
          if BDataset.FSysParamNames.Count > 0 then
          begin
            while ARow.FPSQLDA.SQLn - jj > BDataset.FKeyVARMap.Count do
            begin
              tmpCol := BDataset.FindParam( BDataset.FSysParamNames[jj] );
              if Assigned( tmpCol ) then
              begin
                FPSQLDA.sqlvar[jj] := tmpCol.PSQLVAR^;
                FPSQLDA.sqlvar[jj].SQLInd := nil;
                FPSQLDA.sqlvar[jj].SQLData := nil;
                Inc( jj );
              end
              else
                Break;
            end;
          end;
          if BDataset.FKeyVARMap.Count > 0 then
          begin
            for ii := 0 to ARow.FPSQLDA.SQLn - 1 - jj do
            begin
              FPSQLDA.sqlvar[ii+jj] :=
                TIB_Column( BDataset.FKeyVARMap[ii] ).PSQLVAR^;
              FPSQLDA.sqlvar[ii+jj].SQLInd := nil;
              FPSQLDA.sqlvar[ii+jj].SQLData := nil;
            end;
          end;
          NameParams;
          UpdateBufferPointers;
        end;
        rtField:
        begin
          for ii := 0 to ARow.FPSQLDA.SQLn - 1 do
          begin
            FPSQLDA.sqlvar[ii] := BDataset.CursorFields[ii].PSQLVAR^;
            FPSQLDA.sqlvar[ii].SQLInd := nil;
            FPSQLDA.sqlvar[ii].SQLData := nil;
          end;
          UpdateBufferPointers;
        end;
      end;
    end;
  end;
end;

procedure TIB_BindingCursor.GetPrepare;
var
  SaveCW: word;
  MonitorText: string;
begin
  FNeedPrepare := true;
  with IB_Session do
  begin
    if PstHandle^ = FakePointer then
      PstHandle^ := nil; 
    SysAllocate;
    asm fstcw [SaveCW] end;
    errcode := isc_dsql_prepare( @status,
                                 PtrHandle,
                                 PstHandle,
                                 null_terminated,
                                 PChar( FRefinedSQL ),
                                 IB_Connection.SQLDialect,
                                 CursorFields.PSQLDA );
    asm fldcw [SaveCW] end;
    //ADDED// CW 2000-06-06
    if ( errcode = 0 ) and ClientMonitorHooksIn then
    begin
      MonitorText :=
          '//>>> STATEMENT PREPARED <<<//'#13#10 +
          'TIB_BindingCursor.GetPrepare()'#13#10 +
          Self.ClassName + ': "';
      if Assigned( Self.Owner ) and ( Self.Owner.Name <> '' ) then
        MonitorText := MonitorText + Self.Owner.Name + '.';
      MonitorText := MonitorText + Self.Name +
          '" stHandle=' + IntToStr(Integer(PstHandle^)) + ' #BC';
      if Assigned( BDataset ) then
      begin
        MonitorText := MonitorText +
            #13#10#13#10'TIB_BindingCursor.BDataset ='#13#10'  ' +
                BDataset.ClassName + ': "';
        if Assigned( BDataset.Owner ) and ( BDataset.Owner.Name <> '' ) then
          MonitorText := MonitorText + BDataset.Owner.Name + '.';
        MonitorText := MonitorText + BDataset.Name + '"';
      end;
      OutputToMonitor( MonitorText );
    end;
    //ENDADDED//
    if errcode = 0 then
    begin
      asm fstcw [SaveCW] end;
      errcode := isc_dsql_describe_bind( @status,
                                         PstHandle,
                                         IB_Connection.SQLDialect,
                                         Params.PSQLDA );
      asm fldcw [SaveCW] end;
    end;
    if errcode <> 0 then
    begin
      FHavePrepare := false;
      HandleException( Self );
    end;
  end;
  Params.NameParams;
  Params.UpdateBufferPointers;
  FHavePrepare := true;
end;

procedure TIB_BindingCursor.GetParamsData;
var
  ii, jj: integer;
  tmpInt: integer;
  tmpCol: TIB_Column;
begin
  jj := -1;
  tmpInt := 0;
  FBindBufferOffset := 0;
  if HavePrepare then
  begin
    for ii := 0 to Params.ColumnCount - 1 do
    begin
      if ( FBindBufferOffset = 0 ) and
         ( Params[ ii ].FieldName = IBO_BINDLINK + '0' ) then
      begin
        FBindBufferOffset := tmpInt;
        jj := 0;
      end
      else
      if BDataset.Params.GetByName( Params[ii].FullFieldName,
                                    tmpCol ) and
                                ( Params[ ii ].DataSize = tmpCol.DataSize ) or
         BDataset.Params.GetByName( Params[ii].FullSQLName,
                                    tmpCol ) and
                                ( Params[ ii ].DataSize = tmpCol.DataSize ) then
      begin
        Params[ ii ].ColData := tmpCol.ColData;
        Inc( tmpInt, tmpCol.DataSize + SizeOf( smallint ));
        jj := -1;
      end;
      if ( jj >= 0 ) and
         ( jj < BDataset.KeyFields.ColumnCount ) then
      begin
        Params[ ii ].FRelAliasName := BDataset.KeyFields[ jj ].RelAliasName;
        with BDataset.KeyFields[ jj ].PSQLVAR^ do
        begin
          Params[ ii ].PSQLVAR.OwnName := OwnName;
          Params[ ii ].PSQLVAR.RelName := RelName;
          Params[ ii ].PSQLVAR.SQLName := SQLName;
          Params[ ii ].PSQLVAR.OwnName_length := OwnName_length;
          Params[ ii ].PSQLVAR.RelName_length := RelName_length;
          Params[ ii ].PSQLVAR.SQLName_length := SQLName_length;
        end;
        Inc( jj );
      end;
    end;
  end;
end;

function TIB_BindingCursor.QuickFetch( Node: PIB_Node;
                                       NeedCursor,
                                       KeepCursor: boolean ): boolean;
var
  tmpPtr: pointer;
  tmpLen: longint;
  FromServer: boolean;
begin
  FromServer := false;
  Result := false;
  if ( Node = nil ) or ( rfBof in Node.RowFlags ) or
                       ( rfEof in Node.RowFlags ) then
  begin
    if NeedCursor then
      raise EIB_DatasetError.CreateWithSender( Self, E_GET_CURSOR_ERROR );
    if Prepared then
    begin
    // For some reason this wipes out an insert when the BufferRowNum gets
    // set to the BofRowNum.  The buffers clear the values inserted.
    // Test by going into Contact sample app, putting an insert at the first
    // row and then putting in a couple updates to columns and then in a
    // regular column press the up arrow and this will be triggered.
    // Although, I did a fix in the IB_Grid unit to avoid this.  See comment
    // in the Scroll() method to reintroduce the problem line of code.
//      if @CursorFields.FRowNode = Node then
      begin
        CursorFields.ClearBuffers( rsNone );
        CursorFields.FRowNode := nil;
      end;
    end;
    Result := true;
    Exit;
  end
  else
  begin
    if NeedCursor and
       ( BDataset.FetchWholeRows or ( BDataset.KeySQLSelect = '' )) and
       not BDataset.KeyLinksExist then
    begin
      if KeepCursor then
        raise EIB_DatasetError.CreateWithSender( Self, E_GET_CURSOR_ERROR );
      NeedCursor := false;
    end;
    if not NeedCursor and
       ( Node.RecordLen > 0 ) and
       ( Node.OldRecLen >= 0 ) then
    begin
      CursorFields.FRowNode := Node;
      Result := true;
    end
    else
    if Assigned( Params.RowBuffer ) then
    begin
      with IB_Session do
      begin
        KillCursor;
        CheckTransaction( true );
        if not HavePrepare then
        begin
          GetPrepare;
          GetParamsData;
        end;
        if Assigned( Node.OldKeyData ) then
          Move( Node.OldKeyData^,
                pointer(longint(Params.RowBuffer) + FBindBufferOffset)^,
                BDataset.NodeList.KeyDataLength )
        else
          Move( Node.KeyData^,
                pointer(longint(Params.RowBuffer) + FBindBufferOffset)^,
                BDataset.NodeList.KeyDataLength );
        if KeepCursor or BUG_EXEC2 then
        begin
          errcode := isc_dsql_row_fetch( @status,
                                         PtrHandle,
                                         PstHandle,
                                         IB_Connection.SQLDialect,
                                         Params.PSQLDA );
          if errcode <> 0 then
            HandleException( Self );
          CheckCursorName;
          errcode := isc_dsql_set_row_cursor_name( @status,
                                                   PstHandle,
                                                   PChar( CursorName ),
                                                   0 );
        end
        else
          errcode := isc_dsql_execute2( @status,
                                        PtrHandle,
                                        PstHandle,
                                        IB_Connection.SQLDialect,
                                        Params.PSQLDA,
                                        CursorFields.PSQLDA );
        if ( errcode <> 0 ) and ( errcode <> 100 ) then
          HandleException( Self )
        else
        begin
          FCursorIsOpen := true;
          Inc( IB_transaction.FOpenCursors );
        end;
        FActive := true;
        try
          if KeepCursor or BUG_EXEC2 then
          begin
            errcode := isc_dsql_fetch( @status,
                                       PstHandle,
                                       IB_Connection.SQLDialect,
                                       CursorFields.PSQLDA );
            if ( errcode = 0 ) and ( not KeepCursor ) then
            begin
              errcode := isc_dsql_fetch( @status,
                                         PstHandle,
                                         IB_Connection.SQLDialect,
                                         CursorFields.PSQLDA );
              if errcode = 100 then
                errcode := 0
              else
              begin
                KillCursor;
                raise EIB_DatasetError.CreateWithSender( Self, 
                  E_Multiple_Rows_In_Singleton +
                  #13#10#13#10 +
                  'Check KeyLinks and JoinLinks properties' +
                  #13#10#13#10 +
                  ServerSQL );
              end;
            end;
          end;
          Result := errcode = 0;
          if Result then
          begin
            CursorFields.CleanBuffers( true );
            CursorFields.FRowNode := Node;
            FromServer := true;
          end
          else
          if errcode = 100 then
          begin
            errcode := 0;
            CursorFields.FRowNode := nil;
          end
          else
          begin
            KillCursor;
            HandleException( Self );
          end;
        finally
          if ( not NeedCursor ) or
             ( not KeepCursor ) or
             ( not Assigned( CursorFields.FRowNode )) then
            KillCursor;
        end;
      end;
    end
    else
    if not FProblemInBindingCursor then
    begin
      FProblemInBindingCursor := true;
      raise EIB_DatasetError.CreateWithSender( Self,
                                               'Problem in BindingCursor: ' +
                                               'Check KeyLinks property' );
    end;
  end;
  if Result and Assigned( CursorFields.FRowNode ) then
  begin
    if FromServer then
    begin
      CursorFields.CalculateFields;
      PutRecord( Node, CursorFields );
      if Assigned( Node.OldRecData ) then
      begin
        if ( Node.OldRecLen < 0 ) then
        begin
          tmpPtr := Node.OldRecData;
          tmpLen := Node.OldRecLen;
          Node.OldRecData := Node.RecordData;
          Node.OldRecLen := Node.RecordLen;
          Node.RecordData := tmpPtr;
          Node.RecordLen := Abs( tmpLen );
        end;
        CursorFields.LoadFromNode( true, false );
      end
      else
        CursorFields.RefreshBuffers( true, false, true );
    end
    else
      CursorFields.LoadFromNode( true, false );
  end
  else
  begin
    CursorFields.ClearBuffers( rsNone );
    CursorFields.FRowNode := nil;
  end;
end;

procedure TIB_BindingCursor.KillCursor;
begin
  if FCursorIsOpen then
    with IB_Session do
    begin
      errcode := isc_dsql_free_row_statement( @status,
                                              PstHandle,
                                              DSQL_CLOSE );
      FActive := false;
      FCursorIsOpen := false;
      Dec( IB_transaction.FOpenCursors );
    end;
end;

// IBA_LocateCursor.INT

constructor TIB_LocateCursor.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  FLocateLinks := TIB_StringList.Create;
  FReadOnly := true;
end;

destructor TIB_LocateCursor.Destroy;
begin
  Destroying;
  FLocateLinks.Free;
  FLocateLinks := nil;
  inherited Destroy;
end;

procedure TIB_LocateCursor.FreeServerResources;
begin
  Close;
  inherited FreeServerResources;
end;

procedure TIB_LocateCursor.SetLocateLinks( AValue: TIB_StringList );
begin
  FLocateLinks.Assign( AValue );
end;

procedure TIB_LocateCursor.SetLocateKeyFields( const AValue: string );
begin
  if LocateKeyFields <> AValue then
  begin
    FLocateKeyFields := AValue;
    InvalidateSQL;
  end;
end;

procedure TIB_LocateCursor.SetLocateKeyValues( AValue: Variant );
begin
//!! Need to make sure that NULL values are considered.
//!! If a value is changing from NULL to NOT NULL then InvalidateSQL.
  FLocateKeyValues := AValue;
end;

procedure TIB_LocateCursor.SetLocateNearest( AValue: boolean );
begin
  if LocateNearest <> AValue then
  begin
    FLocateNearest := AValue;
    InvalidateSQL;
  end;
end;

procedure TIB_LocateCursor.SetLocateOptions( AValue: TIB_LocateOptions );
begin
  if LocateOptions <> AValue then
  begin
    FLocateOptions := AValue;
    InvalidateSQL;
  end;
end;

function TIB_LocateCursor.GetParamName( ALink: string ): string;
var
  tmpCol: TIB_Column;
begin
  tmpCol := BDataset.FieldByName( ALink );
  Result := '';
  if SQLDialect < 3 then
  begin
    if tmpCol.Relname <> '' then
      Result := '_' + stLitCriteria( tmpCol.RelName ) +
                '_' + stLitCriteria( tmpCol.FieldName )
    else
      Result := '_' + stLitCriteria( tmpCol.FieldName );
  end;
  Result := IBO_LOCATE_PARAMETER + IntToStr( tmpCol.FieldNo ) + Result;
  if Length( Result ) > 32 then
    SetLength( Result, 32 );
  Result := IB_Connection.mkVarIdent( Trim( Result ));
end;

procedure TIB_LocateCursor.SysPrepareSQL;
var
  ii: integer;
  tmpItem: string;
  tmpCol: TIB_Column;
  tmpStr: string;
  tmpOper: string;
  tmpParam: string;
  tmpKey: variant;
  tmpOrdNo: integer;
  tmpOrdStr: string;
  newOrdSQL: string;
begin
  inherited SysPrepareSQL;
  ExtractFieldsIntoList( LocateKeyFields, LocateLinks );
  FLocateMustBeOnServer := false;
  if LocateNearest then
    newOrdSQL := 'ORDER BY '
  else
    newOrdSQL := '';
  SQLWhereLow.Add( '(' );
  for ii := 0 to LocateLinks.Count - 1 do
  begin
    tmpCol := BDataset.FieldByName( LocateLinks[ ii ] );
    tmpParam := '?' + ParamName[ tmpCol.FullFieldName ];
    if ( BDataset.SysFieldNames.Count = BDataset.FieldCount ) and
       ( BDataset.Fields.RelationCount < 2 ) then
      tmpItem := Trim( GetCharValues( BDataset.SysFieldNames[tmpCol.FieldNo] ))
    else
    begin
      tmpItem := tmpCol.RelAliasName;
      if tmpItem = '' then
        tmpItem := tmpCol.RelName;
      if tmpItem <> '' then
        tmpItem := tmpItem + '.';
      if tmpCol.SQLName <> '' then
        tmpItem := tmpItem + tmpCol.SQLName
      else
        tmpItem := tmpItem + tmpCol.FieldName;
    end;
    if ( LocateLinks.Count > 1 ) or VarIsArray( LocateKeyValues ) then
      tmpKey := LocateKeyValues[ ii ]
    else
      tmpKey := LocateKeyValues;

    if VarIsNull( tmpKey ) or VarIsEmpty( tmpKey ) then
    begin
      tmpParam := '';
      tmpOper := ' IS NULL ';
      if LocateNearest then
        raise EIB_DatasetError.CreateWithSender( BDataset,
                                         E_Cannot_use_LocateNearest_with_NULL );
    end
    else
    if LocateNearest then
    begin
      tmpOrdNo := BDataset.SQLOrderLinks.LinkIndex[ tmpCol.FullFieldName ];
      if ( tmpOrdNo < 0 ) and
         ( tmpCol.FullFieldName <> tmpCol.FullSQLName ) then
        tmpOrdNo := BDataset.SQLOrderLinks.LinkIndex[ tmpCol.FullSQLName ];

      tmpOrdStr := BDataset.SQLOrderLinks.LinkValues[ tmpCol.FullFieldName ];
      if ( tmpOrdStr = '' ) and
         ( tmpCol.FullFieldName <> tmpCol.FullSQLName ) then
        tmpOrdStr := BDataset.SQLOrderLinks.LinkValues[ tmpCol.FullSQLName ];
      if ii > 0 then
        newOrdSQL := newOrdSQL + #13#10'       , ';
      if ( not FLocateMustBeOnServer ) and
         ( tmpOrdNo = ii ) and ( tmpOrdStr = 'DESC' ) then
      begin
        newOrdSQL := newOrdSQL + tmpItem + ' DESC';
        tmpOper := ' <= ';
      end
      else
      if ( not FLocateMustBeOnServer ) and
         ( tmpOrdNo = ii ) and ( tmpOrdStr = 'ASC' ) then
      begin
        newOrdSQL := newOrdSQL + tmpItem + ' ASC';
        tmpOper := ' >= ';
      end
      else
      begin
        newOrdSQL := newOrdSQL + tmpItem + ' ASC';
        tmpOper := ' >= ';
        FLocateMustBeOnServer := true;
      end;
    end
    else
      tmpOper := ' = ';
    if tmpCol.IsText and ( tmpParam <> '' ) then
    begin
      if ( lopPartialKey in LocateOptions ) and ( not LocateNearest ) then
      begin
        if tmpKey = '' then
          tmpOper := ' >= '
        else
          tmpOper := ' STARTING WITH ';
      end;
      if lopCaseInsensitive in LocateOptions then
      begin
        tmpStr := tmpCol.NoCaseFieldName;
        if tmpStr = '' then
          if tmpCol.CharCase = ccUpper then
            tmpStr := tmpCol.FullFieldName;
        if tmpStr = '' then
        begin
          tmpItem := 'UPPER( ' + tmpItem + ' )';
          tmpParam := 'UPPER( ' + tmpParam + ' )';
        end
        else
        begin
          tmpItem := tmpStr;
          tmpParam := 'UPPER( ' + tmpParam + ' )';
        end;
      end;
    end;
    SQLWhereLow.Add( '(' );
    if ( ii = LocateLinks.Count - 1 ) or ( not LocateNearest ) then
    begin
      SQLWhereLow.Add( '(' );
      SQLWhereLow.Add( tmpItem + tmpOper + tmpParam );
      SQLWhereLow.Add( ')' );
    end
    else
    begin
      if Pos( '<', tmpOper ) > 0 then
        tmpOper := ' < '
      else
        tmpOper := ' > ';
      SQLWhereLow.Add( '(' );
      SQLWhereLow.Add( tmpItem + tmpOper + tmpParam );
      SQLWhereLow.Add( ')' );
      SQLWhereLow.Add( 'OR' );
      SQLWhereLow.Add( '(' );
      SQLWhereLow.Add( '(' );
      tmpOper := ' = ';
      SQLWhereLow.Add( tmpItem + tmpOper + tmpParam );
      SQLWhereLow.Add( ')' );
      SQLWhereLow.Add( 'AND' );
    end;
  end;
  for ii := 0 to LocateLinks.Count - 1 do
  begin
    SQLWhereLow.Add( ')' );
    if ( ii <> LocateLinks.Count - 1 ) and LocateNearest then
      SQLWhereLow.Add( ')' );
  end;
  SQLWhereLow.Add( ')' );
  if LocateNearest then
  // Shouldn't take out inherant ORDER BY unless necessary.
  // Otherwise, the Locate may not end up on the first occurance of the
  // desired record in the dataset.
    SQLOrder.Text := newOrdSQL;
  OrderingItemNo := 0;
end;

procedure TIB_LocateCursor.SysBeforeOpen;
var
  ii: integer;
  tmpParam: TIB_Column;
  tmpOfs: integer;
begin
  inherited SysBeforeOpen;
  for ii := 0 to ParamCount - 1 do
  begin
    tmpParam := BDataset.FindParam( Params[ ii ].FullFieldName );
    if not Assigned( tmpParam ) then
      tmpParam := BDataset.FindParam( Params[ ii ].FullSQLName );
    if Assigned( tmpParam ) then
      Params[ ii ].Assign( tmpParam );
  end;
  if ( LocateLinks.Count > 1 ) or VarIsArray( LocateKeyValues ) then
  begin
    tmpOfs := VarArrayLowBound( LocateKeyValues, 1 ); 
    for ii := 0 to LocateLinks.Count - 1 do
    begin
      tmpParam := FindParam( ParamName[LocateLinks[ii]] );
      if Assigned( tmpParam ) then
        tmpParam.AsVariant := LocateKeyValues[ii+tmpOfs];
    end;
  end
  else
  if ( LocateLinks.Count = 0 ) then
    raise EIB_DatasetError.CreateWithSender( Self, E_No_LocateLinks_entries )
  else
  begin
    tmpParam := FindParam( ParamName[LocateLinks[0]] );
    if Assigned( tmpParam ) then
      tmpParam.AsVariant := LocateKeyValues;
  end;
end;

// IBA_BDataset.INT

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Jason Wharton <jwharton@jwharton.com>                                       }
{  13 May 2002                                                                 }
{     I fixed a problem when callig InvalidateRowNum and the record had been   }
{     deleted from the server. In some cases two records ended up being        }
{     removed from the buffer instead of just one. It had to do with calling   }
{     ValidateRows() inside of the implementation which also will remove a     }
{     row from the buffer if it comes across an invalid record that no longer  }
{     belongs in the buffer.                                                   }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  09-Jan-2002                                                                 }
{     Altered E_UnableToSearch into two separate messages and expanded the     }
{     explanation to make the problems more obvious.                           }
{                                                                              }
{  Jason Wharton <jwharton@jwharton.com>                                       }
{  11/24/2001                                                                  }
{     I fixed a problem with the Locate() method where it was getting fouled   }
{     due to trailing spaces in the key fields. If a search was done on a key  }
{     field and the values in the database have trailing spaces and the value  }
{     being searched on didn't have trailing spaces, yet it partially matched  }
{     a value in the database that did have trailing spaces, minus the spaces, }
{     then it was getting confused and doing a refresh trying to get it to     }
{     appear. Only, it never would because its binary value wasn't the same.   }
{     I added a check to ensure they were the same and if not I returned false.}
{                                                                              }
{******************************************************************************}

constructor TIB_BDataset.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  FKeyVARMap := TList.Create;
  FBufferCursor := TIB_BindingCursor.CreateWithBinding( Self, Self );
  FCurrentCursor := TIB_BindingCursor.CreateWithBinding( Self, Self );
  FNodeList := TIB_NodeList.Create;
  FNodeList.OnApplyUpdate := NodeApplyUpdate;
  FNodeList.OnCancelUpdate := NodeCancelUpdate;
  FNodeList.OnGetRestoreInsertedRecord := GetRestoreInsertedRecord;
  FNodeList.OnGetRecordIsFiltered := GetRecordIsFiltered;
  FNodeList.OnBeginBusy := NodeListBeginBusy;
  FNodeList.OnEndBusy := NodeListEndBusy;
  FNodeList.OnNeedRecord := NeedRecords;
  FBufferCursor.Fields.IsBufferFields := true;
  FBufferCursor.OnError := Self.DoHandleError;
  FBufferCursor.FBDataset := Self;
  FBufferCursor.ReadOnly := true;
  FBufferCursor.CursorFields.OnRowStateChanged := nil;
  FBufferCursor.CursorFields.BeforeModify := nil;
  FBufferCursor.CursorFields.AfterModify := nil;
  FCurrentCursor.OnError := Self.DoHandleError;
  FCurrentCursor.FBDataset := Self;
  CheckCursorFields;
  inherited RefreshAction := raKeepRowNum;
  inherited RefreshOnParamChange := true;
  inherited KeyLinksAutoDefine := true;
  inherited SearchedEdits := true;
  inherited SearchedDeletes := true;
  inherited PreparedEdits := true;
  FSeekCursor := nil;
  FSeekCursorPrepared := false;
  FIncSearchSeekInt := 180000;
  FIncSearchKeyInt := 3500;
  FIncSearchKeyString := '';
  FBufferSynchroFlags := [];
  FFetchWholeRows := true;
  FSuppressDupRecs := false;
  FTreeTrash := -1;
  FTreeExpandLevel := -1;
end;

destructor TIB_BDataset.Destroy;
begin
  try
    SysClose;
  except
  end;
  if UpdatesPending then
    CancelUpdates;
  try
    SysUnprepare;
  except
  end;
  try
    SysDeallocate( true );
  except
  end;
  with FCurrentCursor.Fields do
  begin
    OnRowStateChanged := nil;
    BeforeModify := nil;
    AfterModify := nil;
  end;
  CheckTransactionFlagForCachedUpdates;
  inherited Destroy;
  FNodeList.Free;
  FNodeList := nil;
  FKeyVARMap.Free;
  FKeyVARMap := nil;
  if Assigned( PSeekDA ) then
  begin
    FreeMem( PSeekDA );
    PSeekDA := nil;
  end;
end;

procedure TIB_BDataset.FreeServerResources;
begin
  FBufferCursor.FreeServerResources;
  FCurrentCursor.FreeServerResources;
  if Assigned( FLocateCursor ) then
    FLocateCursor.FreeServerResources;
  if Assigned( FFilterCursor ) then
    FFilterCursor.FreeServerResources;
  inherited FreeServerResources;
end;

{------------------------------------------------------------------------------}

procedure TIB_BDataset.SetDatabaseName( const AValue: string );
begin
  inherited SetDatabaseName( AValue );
  FCurrentCursor.DatabaseName := AValue;
  FBufferCursor.DatabaseName := AValue;
  if Assigned( FLocateCursor ) then
    FLocateCursor.DatabaseName := AValue;
  if Assigned( FFilterCursor ) then
    FFilterCursor.DatabaseName := AValue;
end;

procedure TIB_BDataset.SetConnection( AValue: TIB_Connection );
begin
  if IB_Connection <> AValue then
  begin
    inherited SetConnection( AValue );
    FCurrentCursor.IB_Connection := IB_Connection;
    FBufferCursor.IB_Connection := IB_Connection;
    if Assigned( FLocateCursor ) then
      FLocateCursor.IB_Connection := IB_Connection;
    if Assigned( FFilterCursor ) then
      FFilterCursor.IB_Connection := IB_Connection;
  end;
end;

procedure TIB_BDataset.SetTransaction( AValue: TIB_Transaction );
begin
  if IB_Transaction <> AValue then
  begin
    if Assigned( IB_Transaction ) and
       ( UpdatesPending or ( CachedUpdates and NeedToPost )) then
      IB_Transaction.SysAdjustCachedUpdatePendingCount( -1 );
    inherited SetTransaction( AValue );
    FCurrentCursor.IB_Transaction := AValue;
    FBufferCursor.IB_Transaction := AValue;
    if Assigned( FLocateCursor ) then
      FLocateCursor.IB_Transaction := AValue;
    if Assigned( FFilterCursor ) then
      FFilterCursor.IB_Transaction := AValue;
    if Assigned( IB_Transaction ) and
       ( UpdatesPending or ( CachedUpdates and NeedToPost )) then
      IB_Transaction.SysAdjustCachedUpdatePendingCount( 1 );
  end;
end;

{------------------------------------------------------------------------------}

function TIB_BDataset.GetBufferFields: TIB_Row;
begin
  if FBufferCursor.Prepared then
    Result := FBufferCursor.CursorFields
  else
    Result := inherited GetBufferFields;
end;

function TIB_BDataset.GetBufferRowNum: longint;
begin
  Result := NodeList.BufRowNum;
end;
function TIB_BDataset.GetBufferBof: boolean;
begin
  Result := BufferHasBof and NodeList.BufBof;
end;
function TIB_BDataset.GetBufferEof: boolean;
begin
  Result := BufferHasEof and NodeList.BufEof;
end;
function TIB_BDataset.GetBufferRowCount: longint;
begin
  if Assigned( NodeList ) then
    Result := NodeList.RowCount
  else
    Result := 0;
end;
function TIB_BDataset.GetBufferHasBof: boolean;
begin
  Result := NodeList.BufferHasBof;
end;
function TIB_BDataset.GetBufferHasEof: boolean;
begin
  Result := NodeList.BufferHasEof;
end;
function TIB_BDataset.GetBufferRowFlags: TIB_RowFlagSet;
begin
  if Assigned( NodeList ) and Assigned( NodeList.BufRef.Node ) then
    Result := NodeList.BufRef.Node.RowFlags
  else
    Result := [rfEof,rfBof];
end;
function TIB_BDataset.GetRowFlags: TIB_RowFlagSet;
begin
  if Assigned( NodeList ) and Assigned( NodeList.CurRef.Node ) then
    Result := NodeList.CurRef.Node.RowFlags
  else
    Result := [rfEof,rfBof];
end;
function TIB_BDataset.GetSysFieldNames: TIB_StringList;
begin
  Result := FBufferCursor.SysFieldNames;
end;
{------------------------------------------------------------------------------}
function TIB_BDataset.GetBofRowNum: longint;
begin
  Result := NodeList.BofRowNum;
end;
function TIB_BDataset.GetEofRowNum: longint;
begin
  Result := NodeList.EofRowNum;
end;
function TIB_BDataset.GetRowNum: longint;
begin
  if ProcessingUpdates then
    Result := NodeList.CachedUpdateRef.Pos
  else
    Result := NodeList.CurRowNum;
end;
function TIB_BDataset.GetBof: boolean;
begin
  Result := BufferHasBof and NodeList.CurBof;
end;
function TIB_BDataset.GetEof: boolean;
begin
  Result := BufferHasEof and NodeList.CurEof;
end;
{------------------------------------------------------------------------------}
function TIB_BDataset.GetCanEdit: boolean;
begin
  Result := Assigned( Fields.RowNode ) and
            ( not ( rfDeleted in Fields.RowNode.RowFlags ));
  if Result then
    Result := inherited GetCanEdit;
end;
function TIB_BDataset.GetCanInsert: boolean;
begin
  Result := FCanInsert and inherited GetCanInsert;
end;
function TIB_BDataset.GetUnidirectional: boolean;
begin
  Result := NodeList.Unidirectional;
end;
procedure TIB_BDataset.SetUnidirectional( AValue: boolean );
begin
  if Unidirectional <> AValue then
  begin
    Unprepare;
    NodeList.Unidirectional := AValue;
  end;
end;
function TIB_BDataset.GetFields: TIB_Row;
begin
  if FCurrentCursor.Prepared then
    Result := FCurrentCursor.CursorFields
  else
    Result := inherited GetFields;
end;
function TIB_BDataset.GetKeyFields: TIB_Row;
begin
  if CursorIsKeyFields then
    Result := CursorFields
  else
    Result := CursorKeyFields;
end;
{------------------------------------------------------------------------------}

function TIB_BDataset.GotoBOF: boolean;
var
  curCursorGen: dword;
begin
  Result := true;
  if Assigned( OrderingParam ) then
  begin
    Inc( FRefiningIncSearch );
    try
      SysClose;
      FRefineZone := rzTop;
      curCursorGen := FCursorGen;
      if SysOpen and ( curCursorGen + 1 = FCursorGen ) then
        RowNum := BofRowNum
      else
        Result := false;
    finally
      Dec( FRefiningIncSearch );
    end;
  end
  else
    RowNum := BofRowNum;
end;

function TIB_BDataset.GotoEOF: boolean;
var
  curCursorGen: dword;
begin
  Result := true;
  if Assigned( OrderingParam ) then
  begin
    Inc( FRefiningIncSearch );
    try
      SysClose;
      FRefineZone := rzBot;
      curCursorGen := FCursorGen;
      if SysOpen and ( curCursorGen + 1 = FCursorGen ) then
        RowNum := EofRowNum
      else
        Result := false;
    finally
      Dec( FRefiningIncSearch );
    end;
  end
  else
  begin
    if ( not BufferHasEof ) then SysFetchAll( 0 );
    if BufferHasEof then RowNum := EofRowNum
                    else RowNum := EofRowNum - 1;
    Result := BufferHasEof and ( RowNum = EofRowNum );
  end;
end;

function TIB_BDataset.GotoNULL: boolean;
begin
  Result := false;
  Inc( FKeyChildUpdatingLevel );
  try
    case FRefineZone of
      rzTop: Result := GotoBof;
      rzMid: Result := GotoBof;
      rzBot: Result := GotoEof;
    end;
  finally
    Dec( FKeyChildUpdatingLevel );
  end;
  if Assigned( KeySource ) then
    SysUpdateKeyLinksData;
end;

procedure TIB_BDataset.BufferGotoNULL;
begin
  if BufferHasBof then
    BufferRowNum := BofRowNum
  else
  if BufferHasEof then
    BufferRowNum := EofRowNum
  else
  begin
    BufferRowNum := BofRowNum + 1;
{!! Need to make this move in a way that won't pull in a record.}
    BufferRowNum := BofRowNum;
  end;
end;

function TIB_BDataset.GetBookmark: string;
begin
  Result := '';
  if Prepared then
    if NodeList.ItemCount > 0 then
    begin
      if ( NodeList.CurRef.Node.Prev <> nil ) and
         ( NodeList.CurRef.Node.Next <> nil ) then
      begin
        if NeedToPost then
          SysUpdateKeyData( false );
        Result := BinaryToHexText( NodeList.CurRef.Node.KeyData,
                                   NodeList.KeyDataLength );
      end;
    end;
end;

procedure TIB_BDataset.SetBookmark( const ABookMark: string );
var
  OldFetchingAborted: boolean;
begin
  CheckBrowseMode;
  if ( Bookmark <> ABookmark ) and
     ( ComponentState * [csDestroying] = [] ) then
  begin
    if Length( ABookmark ) = 0 then
      GotoNULL
    else
    begin
      OldFetchingAborted := FFetchingAborted;
      try
        FFetchingAborted := false;
        KeyFields.RowData := ABookmark;
        if not LookupKeyForFields then
          GotoNULL;
      finally
        if OldFetchingAborted then FFetchingAborted := true;
      end;
    end;
  end;
end;

function TIB_BDataset.GetBufferBookmark: string;
begin
  Result := '';
  if Prepared and Assigned( NodeList ) then
    with NodeList do
    begin
      if ( BufRef.Node <> nil ) then
      begin
        if BufRef.Node = CurRef.Node then
        begin
          if NeedToPost then
            SysUpdateKeyData( false );
          Result := BinaryToHexText( CurRef.Node.KeyData, KeyDataLength );
        end
        else
          Result := BinaryToHexText( BufRef.Node.KeyData, KeyDataLength );
      end;
    end;
end;

procedure TIB_BDataset.SetBufferBookmark( const ABookmark: string );
begin
  if Length( ABookmark ) = 0 then
    BufferGotoNULL
  else
  begin
    CheckPrepared;
    KeyFields.RowData := ABookmark;
    if not LookupKeyForBufferFields then
      BufferGotoNULL; 
  end;
end;

procedure TIB_BDataset.SetFetchWholeRows( AValue: boolean );
begin
  if FetchWholeRows <> AValue then
  begin
    Prepared := false;
    FFetchWholeRows := AValue;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_BDataset.CancelQuery;
begin
  inherited CancelQuery;
  FCurrentCursor.CancelQuery;
  FBufferCursor.CancelQuery;
  if Assigned( FLocateCursor ) then
    FLocateCursor.CancelQuery;
  if Assigned( FFilterCursor ) then
    FFilterCursor.CancelQuery;
end;

procedure TIB_BDataset.CheckCursorFields;
begin
  if ( SQL.Count > 0 ) and IsSelectSQL then
  begin
    with CursorFields do
    begin
      IsKeyFields := true;
      IsKeyFieldsWhole := not CursorIsKeyFields;
      OnRowStateChanged := nil;
      BeforeModify := nil;
      AfterModify := nil;
    end;
    with FCurrentCursor.CursorFields do
    begin
      OnRowStateChanged := Self.SysFieldsStateChanged;
      BeforeModify := Self.SysBeforeFieldDataChange;
      AfterModify := Self.SysAfterFieldDataChange;
    end;
  end
  else
  begin
    with CursorFields do
    begin
      IsKeyFields := false;
      IsKeyFieldsWhole := false;
      OnRowStateChanged := SysCursorFieldsStateChanged;
      BeforeModify := SysBeforeCursorFieldDataChange;
      AfterModify := SysAfterCursorFieldDataChange;
    end;
    with FCurrentCursor.CursorFields do
    begin
      OnRowStateChanged := nil;
      BeforeModify := nil;
      AfterModify := nil;
    end;
  end;
end;

function TIB_BDataset.SyncBufferFields: boolean;
begin
  if ( BufferRowNum = RowNum ) or
     (( BufferRowCount = 0 ) and
      ( BufferRowNum <= BofRowNum + 1 ) and
      ( RowNum <= BofRowNum + 1 )) then
  begin
    Result := true;
    BufferFields.RowData := Fields.RowData;
    BufferFields.OldRowData := Fields.OldRowData;
    BufferFields.SetRowState( Fields.RowState );
  end
  else
  begin
    Result := false;
  end;
end;

procedure TIB_BDataset.SysAfterFieldDataChange( Sender: TIB_Row;
                                                AField: TIB_Column);
begin
  SyncBufferFields;
  inherited SysAfterFieldDataChange( Sender, AField );
end;

procedure TIB_BDataset.SysPost( CancelUnmodified, IsRetaining: boolean );
begin
  if CachedUpdates and IsRetaining then
    EIB_DatasetError.CreateWithSender( Self, E_NO_POSTRTN_CACHED_UPDT )
  else
    inherited SysPost( CancelUnmodified, IsRetaining );
end;

procedure TIB_BDataset.SysExecPost( CancelUnmodified: boolean );
begin
{$IFDEF DEBUG}
  if not Assigned( Fields.RowNode ) then
    raise EIB_DatasetError.CreateWithSender( Self,
                                             E_SysExecPost_Unassigned_RowNode );
{$ENDIF}
  if Assigned( OrderingField ) and
     OrderingField.IsModified then
    Fields.RowNode.RowFlags := Fields.RowNode.RowFlags + [ rfUnsorted ];
  if CachedUpdates then
  begin
    case State of
      dssEdit: NodeList.PostUpdate( Fields.RowNode, rfEdited );
      dssInsert: NodeList.PostUpdate( Fields.RowNode, rfInserted );
      dssDelete:
        if ( rfInserted in Fields.RowNode.RowFlags ) then
        begin
          SysAdjustCurrentRow( true, true );
          Exit;
        end
        else
        begin
          NodeList.PostUpdate( Fields.RowNode, rfDeleted );
          if NodeList.FilterDel then
          begin
            SysAdjustCurrentRow( false, true );
            Exit;
          end;
        end;
    end;
    SysUpdateKeyData( true );
    Fields.StoreBlobsToNode;
    CheckTransactionFlagForCachedUpdates;
  end
  else
    inherited SysExecPost( CancelUnmodified );
end;

function TIB_BDataset.GetCachedEditCount: longint;
begin Result := NodeList.CachedEditCount end;
function TIB_BDataset.GetCachedInsertCount: longint;
begin Result := NodeList.CachedInsertCount end;
function TIB_BDataset.GetCachedDeleteCount: longint;
begin Result := NodeList.CachedDeleteCount end;

procedure TIB_BDataset.ApplyUpdates;
begin
  if CachedUpdates then
  begin
    if NeedToPost then
      Post;
    if UpdatesPending and ( not FProcessingUpdates ) then
      try
        FProcessingUpdates := true;
        SysProcessUpdates( cupApply );
        SysScrollCurrent( false, false, false );
        SysStateChanged;
      finally
        FProcessingUpdates := false;
      end;
  end;
end;

procedure TIB_BDataset.CancelUpdates;
begin
  if CachedUpdates then
  begin
    if NeedToPost then
      Cancel;
    if UpdatesPending then
    begin
      SysProcessUpdates( cupCancel );
      SysScrollCurrent( false, false, false );
      SysStateChanged;
    end;
  end;
end;

procedure TIB_BDataset.CommitUpdates;
begin
  SysProcessUpdates( cupCommit );
end;

procedure TIB_BDataset.GetRestoreInsertedRecord(     Sender: TObject;
                                                     ANode: PIB_Node;
                                                 var ABoolean: boolean );
var
  ii: integer;
  tmpParam: TIB_Column;
  tmpCol: TIB_Column;
begin
  ABoolean := true;
  if Prepared and
     Assigned( MasterDataset ) and MasterDataset.Prepared then
  begin
    FBufferCursor.QuickFetch( ANode, false, false );
    for ii := 0 to MasterLinks.Count - 1 do
    begin
      tmpParam := BufferFieldByName( MasterLinks.IndexNames[ii] );
      tmpCol := MasterDataset.FieldByName( MasterLinks.IndexValues[ii] );
      if tmpParam.AsString <> tmpCol.AsString then
      begin
        ABoolean := false;
        Break;
      end;
    end;
  end;
  if ABoolean then
    ABoolean := CheckRecordFilter( BufferFields );
end;

procedure TIB_BDataSet.DoUpdateRecord(     UpdateKind: TIB_UpdateKind;
                                       var UpdateAction: TIB_UpdateAction );
begin
  if Assigned( OnUpdateRecord ) then
    OnUpdateRecord( Self, UpdateKind, UpdateAction );
end;

// Make an OnApplyUpdate event here.

procedure TIB_BDataset.NodeCancelUpdate( Sender: TObject; ANode: PIB_Node );
begin
  Fields.FRowNode := ANode;
  Fields.LoadFromNode( false, true );
  Fields.CancelBlobBuffers;
  SysUpdateKeyData( false );
end;

procedure TIB_BDataset.NodeApplyUpdate(     Sender: TObject;
                                            ANode: PIB_Node;
                                            UpdateKind: TIB_UpdateKind;
                                        var UpdateAction: TIB_UpdateAction );
begin
  Fields.FRowNode := ANode;
  Fields.LoadFromNode( false, true );
  Fields.PostBlobBuffers;
  try
    case UpdateKind of
    ukiModify: FState := dssEdit;
    ukiInsert: FState := dssInsert;
    ukiDelete: FState := dssDelete;
    end;
    case FState of
    dssEdit: UpdateOperation( SysPostEditedRow, ukiModify, UpdateAction );
    dssInsert: UpdateOperation( SysPostInsertedRow, ukiInsert, UpdateAction );
    dssDelete: UpdateOperation( SysPostDeletedRow, ukiDelete, UpdateAction );
    end;
    if UpdateAction = uacApplied then
    begin
      Fields.AfterPostBuffers( true );
      SysUpdateKeyData( false );
    end;
  finally
    Fields.StoreBlobsToNode;
  end;
end;

procedure TIB_BDataSet.UpdateOperation(     Operation: TIB_DataOperation;
                                            UpdateKind: TIB_UpdateKind;
                                        var UpdateAction: TIB_UpdateAction );
begin
  repeat
    UpdateAction := uacFail;
    try
      if Assigned( FOnUpdateRecord ) then
        DoUpdateRecord( UpdateKind, UpdateAction )
      else
      begin
        SysUpdateOperation( Operation, UpdateKind, UpdateAction );
        UpdateAction := uacApplied;
      end;
      if FCursorRecordCountValid and ( UpdateAction = uacApplied ) then
        case UpdateKind of
          ukiInsert: Inc( FCursorRecordCount );
          ukiDelete: Dec( FCursorRecordCount );
        end
    except
      on E: Exception do
      begin
        UpdateError( E, UpdateKind, UpdateAction );
        case UpdateAction of
        uacAbort: SysUtils.Abort;
        uacFail: raise;
        end;
      end;
    end;
  until UpdateAction <> uacRetry;
end;

procedure TIB_BDataSet.UpdateError(     E: Exception;
                                        UpdateKind: TIB_UpdateKind;
                                    var UpdateAction: TIB_UpdateAction );
begin
  if Assigned( FOnUpdateError ) then
  begin
    if Assigned( Self.FIBODataset ) then
      FOnUpdateError( Self.FIBODataset, E, UpdateKind, UpdateAction )
    else
      FOnUpdateError( Self, E, UpdateKind, UpdateAction );
  end
  else
  begin
    Application.HandleException( Self );
    UpdateAction := uacAbort;
  end;
end;

procedure TIB_BDataSet.SysUpdateOperation(     Operation: TIB_DataOperation;
                                               UpdateKind: TIB_UpdateKind;
                                           var UpdateAction: TIB_UpdateAction );
begin
  Operation;
end;

procedure TIB_BDataset.SysProcessUpdates( AProcess: TIB_CachedUpdatesProcess );
var
  oldState: TIB_DatasetState;
begin
  oldState := State;
  DisableControls;
  try
    try
      try
        NodeList.ProcessUpdates( AProcess );
      finally
        FState := oldState;
        if Active then
        begin
          SysScrollCurrent( false, false, false );
          StateChanged;
          DataChange;
        end;
      end;
    finally
      CheckTransactionFlagForCachedUpdates;
    end;
  finally
    EnableControls;
  end;
end;

procedure TIB_BDataset.RevertRecord;
begin
  if State = dssInsert then
    Cancel
  else
  begin
    if NeedToPost then
      Cancel;
    if not FRevertingRecord then
    begin
      FRevertingRecord := true;
      try
        if ( Fields.RowState <> rsNone ) then
        begin
          if rfInserted in RowFlags then
          begin
            ProcessLinkEvent( setCheckBrowseMode, 0 );
            SysAdjustCurrentRow( true, true );
            CheckTransactionFlagForCachedUpdates;
            SysStateChanged;
          end
          else
          begin
            ProcessLinkEvent( setCheckBrowseMode, 0 );
            NodeList.RevertNode( NodeList.CurRef.Node, true );
            GetRecord( NodeList.CurRef.Node, Fields );
            Fields.CancelBlobBuffers;
            CheckTransactionFlagForCachedUpdates;
            DataChange;
          end;
        end;
      finally
        FRevertingRecord := false;
      end;
    end;
  end;
end;

procedure TIB_BDataset.CheckTransactionFlagForCachedUpdates;
var
  tmpB: boolean;
begin
  tmpB := UpdatesPending or ( CachedUpdates and NeedToPost );
  if FTransactionCachedUpdatesFlag <> tmpB then
  begin
    FTransactionCachedUpdatesFlag := tmpB;
    if Assigned( IB_Transaction ) then
    begin
      if tmpB then
        IB_Transaction.SysAdjustCachedUpdatePendingCount( 1 )
      else
        IB_Transaction.SysAdjustCachedUpdatePendingCount( -1 );
    end;
  end;
end;

function TIB_BDataset.GetUpdatesPending: boolean;
begin
  Result := ( CachedEditCount   > 0 ) or
            ( CachedInsertCount > 0 ) or
            ( CachedDeleteCount > 0 );
end;

function TIB_BDataset.GetUpdateStatus: TIB_UpdateStatus;
begin
  Result := ustUnmodified;
  if Assigned( Fields.RowNode ) then
  begin
    if rfDeleted in RowFlags then
      Result := ustDeleted
    else
    if rfInserted in RowFlags then
      Result := ustInserted
    else
    if rfEdited in RowFlags then
      Result := ustModified;
  end;
end;

procedure TIB_BDataset.SysAfterCancel;
begin
  SyncBufferFields;
  CheckTransactionFlagForCachedUpdates;
  if FCursorRecordCountValid and ( CancelledState = dssInsert ) then
    Dec( FCursorRecordCount );
  inherited SysAfterCancel;
end;

procedure TIB_BDataset.SysAfterPost;
begin
  if NodeList.AdjCurrentFilFlag( not CheckRecordFilter( Fields )) then
  begin
    SysAdjustCurrentRow( false, true );
    FCursorRecordCountValid := false;
  end;
  SyncBufferFields;
  CheckTransactionFlagForCachedUpdates;
  if FCursorRecordCountValid and ( PostedState = dssInsert ) then
    Dec( FCursorRecordCount );
  inherited SysAfterPost;
end;

function TIB_BDataset.CheckRecordFilter( ARow: TIB_Row ): boolean;

{ Tree support held back
  function ParentIsExpanded(Node: PIB_Node): boolean;
    var
      P: PIB_Node;
    begin
      P := Node;
      if Assigned( P ) then
        P := P.Prev;
      while Assigned(P) and (P.ID <> Node.ParID) do
        P := P.Prev;
      Result := Assigned(P) and ((rfExpanded in P.RowFlags) or (rfBof in P.RowFlags));  //when Inserting the rfBox is in the parent node
      if Result and (P.Level > 0)then
        Result := ParentIsExpanded(P);
    end;
  Tree support held back }

begin
  Result := true;
{ Tree stuff being held back  
  if FIsTree and Assigned(ARow.RowNode) then
  begin
    if ARow.RowState <> rsNone then
      Result := (ARow.RowNode.Level = 0) or
                (rfVisible in ARow.RowNode.RowFlags) or
                ParentIsExpanded(ARow.RowNode);
    if (rfVisible in ARow.RowNode.RowFlags) then
      ARow.RowNode.RowFlags := ARow.RowNode.RowFlags - [rfVisible];
  end;
  Tree stuff being held back }  
  if Result and ( ARow.RowState <> rsNone ) and Assigned( FOnFilterRecord ) then
    OnFilterRecord( ARow, Result );
end;

function TIB_BDataset.SysAfterFetchCursorRow: boolean;
begin
  Result := inherited SysAfterFetchCursorRow;
  if Result then
    Result := AfterFetchRow( Self );
end;

procedure TIB_BDataset.DescCursorAfterFetchEof(IB_Dataset: TIB_Dataset);
begin
  NodeList.AfterFetchCursorEof( false );
// JLW - 09/09/2001
// I removed this because when doing attempt fetches it would cause the grid
// to interrupt what edits are currently being done.
// When attempt fetches are being performed the callback stuff is frozen.
  ProcessLinkEvent( setBufferStatusChanged, longint( false ));
  if CallbackFreezeLevel = 0 then
    DataChange;
end;

procedure TIB_BDataset.DescCursorAfterFetchRow(IB_Dataset: TIB_Dataset);
begin
  if inherited SysAfterFetchCursorRow then
    AfterFetchRow( IB_Dataset );
end;

procedure TIB_BDataset.DescCursorBeforeUnprepare(Sender: TIB_Statement);
begin
  FDescCursor.Fields.UpdateBufferPointers;
end;

{!!!
procedure TIB_BDataset.NullCursorAfterFetchEof(IB_Dataset: TIB_Dataset);
begin
  NodeList.AfterFetchCursorEof( false );
// JLW - 09/09/2001
// I removed this because when doing attempt fetches it would cause the grid
// to interrupt what edits are currently being done.
// When attempt fetches are being performed the callback stuff is frozen.
  ProcessLinkEvent( setBufferStatusChanged, longint( false ));
  if CallbackFreezeLevel = 0 then
    DataChange;
end;

procedure TIB_BDataset.NullCursorAfterFetchRow(IB_Dataset: TIB_Dataset);
begin
  if inherited SysAfterFetchCursorRow then
    AfterFetchRow( IB_Dataset );
end;

procedure TIB_BDataset.NullCursorBeforeUnprepare(Sender: TIB_Statement);
begin
  FNullCursor.Fields.UpdateBufferPointers;
end;
!!!}

function TIB_BDataset.AfterFetchRow( IB_Dataset: TIB_Dataset ): boolean;
var
  ii: smallint;
  tmpNode: PIB_Node;
  tmpRecordPassedFilter: boolean;
begin
  Result := true;
  if not CursorIsKeyFields then
  begin
    if FKeyVARMap.Count = 0 then
    begin
      Inc( FLastAllocatedKeyVal );
      if Assigned( KeyFields[0] ) then
        KeyFields[0].AsInteger := FLastAllocatedKeyVal;
    end
    else
      for ii := 0 to FKeyVARMap.Count - 1 do
        Move( pointer(TIB_Column( FKeyVARMap[ii] ).PSQLVAR.SQLInd)^,
              pointer(KeyFields[ii].PSQLVAR.SQLInd)^,
              KeyFields[ii].DataSize + SizeOf( smallint ));
    if IB_Dataset <> Self then
      CursorFields.CalculateFields;
  end;
  tmpRecordPassedFilter := true;
  if Assigned( OnFilterRecord ) then
    if CursorIsKeyFields then
    begin
      Inc( FBufferKeyIsValid );
      try
        BufferBookmark := IB_Dataset.CursorFields.RowData;
        BufferFields.CalculateFields;
        tmpRecordPassedFilter := CheckRecordFilter( BufferFields );
      finally
        Dec( FBufferKeyIsValid );
      end;
    end
    else
      tmpRecordPassedFilter := CheckRecordFilter( CursorFields );
  tmpNode := NodeList.AfterFetchCursorRow( KeyFields.RowBuffer,
                                           FSuppressDupRecs,
                                           ( not tmpRecordPassedFilter ),
                                           IB_Dataset = Self );
  if Assigned( tmpNode ) then
  begin
    if not CursorIsKeyFields then
    begin
      if rfEdited in tmpNode.RowFlags then
      // Ignore the record's value because we don't want to cancel cached
      // updates entered in by the user.!!
      // We also want to avoid corrupting the OLD value buffers.
      // The new values will be brought in again during the apply updates
      // if necessary.
      else
      if rfDeleted in tmpNode.RowFlags then
      begin
        PutRecord( tmpNode, CursorFields );
        Result := not NodeList.FilterDel;
      end
      else
      if rfInserted in tmpNode.RowFlags then
      begin
        PutRecord( tmpNode, CursorFields );
      end
      else
        PutRecord( tmpNode, CursorFields );
    end;
    if Result then
      Result := tmpRecordPassedFilter or ( not Filtered );
  end
  else
  if FSuppressDupRecs then
    Result := false;
end;

procedure TIB_BDataset.SysAfterFetchCursorEof;
begin
  inherited SysAfterFetchCursorEof;
  NodeList.AfterFetchCursorEof( true );
// JLW - 09/09/2001  
// I removed this because when doing attempt fetches it would cause the grid
// to interrupt what edits are currently being done.
// When attempt fetches are being performed the callback stuff is frozen.
  ProcessLinkEvent( setBufferStatusChanged, longint( true ));
  if CallbackFreezeLevel = 0 then
    DataChange;                      
end;

function TIB_BDataset.SysScrollCurrent( NeedCursor,
                                        KeepCursor,
                                        ClearBlobs: boolean ): boolean;
begin
  Fields.OnRowStateChanged := nil;
  Fields.BeforeModify := nil;
  Fields.AfterModify := nil;
  try
    Result := FCurrentCursor.QuickFetch( NodeList.CurRef.Node,
                                         NeedCursor,
                                         KeepCursor );
    if Result then
    begin
      if NodeList.BufRef.Node = NodeList.CurRef.Node then
        BufferFields.RowData := Fields.RowData;
    end;
  finally
    Fields.OnRowStateChanged := SysFieldsStateChanged;
    Fields.BeforeModify := SysBeforeFieldDataChange;
    Fields.AfterModify := SysAfterFieldDataChange;
  end;
end;

{------------------------------------------------------------------------------}

function TIB_BDataset.Lookup( const AKeyFields: string;
                              const AKeyValues: Variant;
                              const AResultFields: string ): Variant;
begin
  Result := Unassigned;
  if LocateRecord( AKeyFields, AKeyValues, [], false, false ) then
    Result := FBufferCursor.FieldValues[ AResultFields ];
end;

function TIB_BDataset.Locate( const AKeyFields: string;
                              const AKeyValues: Variant;
                                    AOptions: TIB_LocateOptions ): boolean;
begin
  Result := LocateRecord( AKeyFields,
                          AKeyValues,
                          AOptions,
                          true,
                          lopFindNearest in AOptions );
end;

function TIB_BDataset.LocateRecord( const AKeyFields: string;
                                    const AKeyValues: Variant;
                                          AOptions: TIB_LocateOptions;
                                          SyncCursor: boolean;
                                          ALocateNearest: boolean ): boolean;
var
  ii: integer;
  tmpStrings: TIB_StringList;
  tmpCol: TIB_Column;
  tmpRow: longint;
  HaveKeyValues: boolean;
  HaveFromCursor: boolean;
  FAGen: integer;

  procedure GetFinalResult( AFunct: TIB_DataFunction );
  begin
    Result := AFunct;
    if ( not Result ) and HaveFromCursor and ( FAGen = FetchingAbortedGen ) then
    begin
      repeat
        FLocateCursor.Next;
        if FAGen <> FetchingAbortedGen then Break;
        if FLocateCursor.Eof then Break;
        Self.KeyFields.RowData := FLocateCursor.Fields.RowData;
        Result := AFunct;
        if FAGen <> FetchingAbortedGen then Break; 
      until Result;
    end;
  end;

var
  KeyBased: boolean;
  ANodeRef: TIB_NodeRef;
begin
  FAGen := FetchingAbortedGen;
  Result := false;
  CheckPrepared;
  if not SQLIsValid then
  begin
    SysPrepare;
    Open;
  end;
  tmpStrings := TIB_StringList.Create;
  try
    ExtractFieldsIntoList( AKeyFields, tmpStrings );
    KeyBased := KeyFields.ColumnCount = tmpStrings.Count;
    HaveKeyValues := KeyBased;
    ii := 0;
    while KeyBased and ( ii < tmpStrings.Count ) do
    begin
      KeyBased := KeyFields.GetByName( tmpStrings[ ii ], tmpCol );
      if ( not KeyBased ) and Assigned( IB_Connection ) then
        KeyBased := KeyFields.GetByName(
          IB_Connection.mkVarIdent( tmpStrings[ ii ] ), tmpCol );
      HaveKeyValues := HaveKeyValues and KeyBased;
      if Assigned( tmpCol ) then
      begin
        if VarIsArray( AKeyValues ) then
          tmpCol.AsVariant := AKeyValues[ ii ]
        else
          tmpCol.AsVariant := AKeyValues;
        HaveKeyValues := HaveKeyValues and
                         (( not tmpCol.IsText ) or ( AOptions = [] ));
      end;
      Inc( ii );
    end;
  finally
    tmpStrings.Free;
  end;
  tmpRow := InvalidNodePos;
  HaveFromCursor := false;
  if ( not HaveKeyValues ) or ALocateNearest or UpdatesPending then
  begin
    if KeyBased then
    begin
      KeyFields.CleanBuffers( false );
      FKeyLookupRowData := KeyFields.RowData;
      KeyFields.OldRowData := FKeyLookupRowData;
      if SeekKeyForBufferFields( ANodeRef ) then
      begin
        // Check if record was deleted or filtered out.
        if (( ANodeRef.Pos = NodeList.BofRef.Pos ) and
            ( NodeList.BufRef.Pos > NodeList.BofRef.Pos )) then
        else
          tmpRow := NodeList.BufRowNum;
      end;
    end;
    if ( tmpRow > BofRowNum ) or Result then
      HaveKeyValues := true
    else
    begin    
      if ( not KeyLinksExist ) or ( KeySQLSelect = '' ) then
      begin
      // It is mandatory that we scan the buffers since an SQL statement to
      // obtain the result cannot be automatically determined.
        tmpRow := SysLocateRecord( AKeyFields,
                                   AKeyValues,
                                   FAGen,
                                   AOptions,
                                   SyncCursor,
                                   BofRowNum + 1,
                                   MaxInt );
        HaveKeyValues := tmpRow > BofRowNum;
      end
      else
      begin
      // Try looking through the memory buffers for the result first.
(*
        if ( BufferRowCount < 20000 {Carlos bug needs this raised.} ) or
           ( IB_Connection.Protocol <> cpLocal ) or
           ( CachedEditCount > 0 ) or
           ( CachedInsertCount > 0 ) then
*)
        begin
          tmpRow := SysLocateRecord( AKeyFields,
                                     AKeyValues,
                                     FAGen,
                                     AOptions,
                                     SyncCursor,
                                     BofRowNum + 1,
                                     EofRowNum - 1 );
          HaveKeyValues := tmpRow > BofRowNum;
          // In case (of what???)
          if ( Assigned( OrderingParam )) and
             ( lopPartialKey in AOptions ) and
             ( tmpRow = BofRowNum + 1 ) then
          begin
            HaveKeyValues := false;
            tmpRow := InvalidNodePos;
          end;
        end;
        if not HaveKeyValues then
        try
        // Attempt to put together an SQL statement that will fetch the key
        // value for us. Then the record will be located with that key.
          if not Assigned( FLocateCursor ) then
          begin
            FLocateCursor := TIB_LocateCursor.CreateWithBinding( Self, Self );
            FLocateCursor.FBDataset := Self;
            FLocateCursor.OnError := Self.DoHandleError;
          end;
          FLocateCursor.IB_Connection := Self.IB_Connection;
          FLocateCursor.IB_Transaction := Self.IB_Transaction;
          FLocateCursor.CallbackInc := Self.CallbackInc;
          with FLocateCursor do
          begin
            LocateKeyFields := AKeyFields;
            LocateKeyValues := AKeyValues;
            LocateOptions := AOptions;
            LocateNearest := ALocateNearest;
            if ( not Prepared ) or ( not SQLIsValid ) then
            begin
              AssignSQLWithSearch( Self );
              // This will cause client code to get executed and cause problems.
              CalculatedFields.Clear;
              OnCalculateField := nil;
              // JLW - 04/13/2002
              SQLSelect.Text := Self.KeySQLSelect;
              OrderingItemNo := Self.OrderingItemNo;
              if OrderingItemNo = 0 then
                SQLOrder.Text := Self.SQLOrder.Text;
              Prepare;
            end;
            First;
            if not Eof then
            begin
              HaveKeyValues := true;
              HaveFromCursor := true;
              Self.KeyFields.RowData := Fields.RowData;
            end;
          // Don't close here.
          end;
        except
          if Assigned( FLocateCursor ) then
            FLocateCursor.Unprepare;
          raise;
        end;
      end;
    end;
  end;
  try
    Inc( FLocateLevel );
    if HaveKeyValues then
    begin
      if tmpRow > BofRowNum then
      begin
        if SyncCursor then
        begin
          RowNum := tmpRow;
          Result := RowNum = tmpRow;
        end
        else
        begin
          BufferRowNum := tmpRow;
          Result := BufferRowNum = tmpRow;
        end;
      end
      else
      if SyncCursor then
        GetFinalResult( LookupKeyForFields )
      else
        GetFinalResult( LookupKeyForBufferFields );
    end;
  finally
    Dec( FLocateLevel );
    if Assigned( FLocateCursor ) then
      FLocateCursor.Close;
  end;
end;

function TIB_BDataset.DoCompareText(       IB_Field: TIB_Column;
                                     const S1, S2: string ): integer;
begin
  if Assigned( IB_Connection.OnCustomCompareText ) then
    Result := IB_Connection.OnCustomCompareText( IB_Field, S1, S2 )
  else
    Result := AnsiCompareText(S1,S2);
end;

function TIB_BDataset.DoCompareStr(       IB_Field: TIB_Column;
                                    const S1, S2: string ): integer;
begin
  if Assigned( IB_Connection.OnCustomCompareStr ) then
    Result := IB_Connection.OnCustomCompareStr( IB_Field, S1, S2 )
  else
  begin
    if S1 = #0 then
      Result := 1
    else
      Result := AnsiCompareStr( S1, S2 );
  end;
end;


function TIB_BDataset.SysLocateRecord( const AKeyFields: string;
                                       const AKeyValues: Variant;
                                       const FAGen: integer;
                                             AOptions: TIB_LocateOptions;
                                             SyncCursor: boolean;
                                             StartPos,
                                             EndPos: longint ): longint;
var
  ii: integer;
  tmpCol: TIB_Column;
  AnsiStrCmp: function (       IB_Field: TIB_Column;
                         const S1, S2: string ): integer of object;
  KeyRowData: string;
  FLst: TList;
  FCnt: integer;
  CCnt: integer;
  VIsAr: boolean;
  VLDim: integer;
  FVal: variant;
  KVal: variant;
  tmpResult: boolean;
  SwapPos: longint;
begin
// Implement the lopFindNearest option...!
// For now it just returns false and allows the server to do the search.
// But, if there are no KeyLinks this just may fail.
  if StartPos > EndPos then
  begin
    SwapPos := StartPos;
    StartPos := EndPos;
    EndPos := SwapPos;
  end;
  tmpResult := false;
  if lopCaseInsensitive in AOptions then AnsiStrCmp := DoCompareText
                                    else AnsiStrCmp := DoCompareStr;
  KeyRowData := KeyFields.RowData;
  FLst := TList.Create;
  try
    GetBufferFieldList( FLst, AKeyFields );
    FCnt := FLst.Count;
    VIsAr := VarIsArray( AKeyValues );
    VLDim := 0;
    if VIsAr then
    begin
      if VarArrayDimCount( AKeyValues ) > 1 then
        raise EIB_DatasetError.CreateWithSender( Self,
                                                 E_Unable_To_Locate_Record );
      VLDim := VarArrayLowBound( AKeyValues, 1 );
    end;
    try
      BeginCallbackFetching;
      try
        ii := StartPos;
        repeat
          if ii <= BofRowNum then
            ii := BofRowNum;
          BufferRowNum := ii; 
          if FAGen <> FetchingAbortedGen then
            Break;
          if BufferRowNum <> ii then
          begin
            BufferRowNum := ii;
            if BufferRowNum <> ii then
            begin
              tmpResult := false;
              Break;
            end;
          end;
          if ( BufferRowNum = ii ) and ( not BufferEof ) then
          begin
            CCnt := 1;
            tmpResult := true;
            while tmpResult and ( CCnt <= FCnt ) do
            begin
              tmpCol := TIB_Column( Flst[ CCnt - 1 ] );
              FVal := tmpCol.Value;
              if VIsAr then
                KVal := AKeyValues[ VLDim + CCnt - 1 ]
              else
                KVal := AKeyValues;
              tmpResult := not (( VarIsEmpty( KVal ) or VarIsNull( KVal )) xor
                                ( VarIsEmpty( FVal ) or VarIsNull( FVal )));
              if tmpResult and
                 ( not ( VarIsEmpty( KVal ) and VarIsEmpty( FVal ))) then
              begin
                try
                  FVal := VarAsType( FVal, VarType( KVal ));
                except
                  on E: EVariantError do
                    tmpResult := false;
                end;
                if tmpCol.IsText then
                begin
                  if lopPartialKey in AOptions then
                  begin
                    if lopCaseInsensitive in AOptions then
                      tmpResult := tmpResult and ( AnsiPos( AnsiUpperCase( KVal ),
                                                      AnsiUpperCase( FVal )) = 1 )
                    else
                      tmpResult := tmpResult and ( AnsiPos( KVal, FVal ) = 1 )
                  end
                  else
                    tmpResult := tmpResult and
                                         ( AnsiStrCmp( tmpCol, KVal, FVal ) = 0 );
                end
                else
                  tmpResult := tmpResult and ( KVal = FVal );
              end;
              Inc( CCnt );
            end;
            if tmpResult then
              KeyRowData := BufferBookmark
            else
              Inc( ii );
          end;
        until tmpResult or
              BufferEof or
              ( FAGen <> FetchingAbortedGen ) or
              ( ii - 1 >= EndPos );
      except
        tmpResult := false;
        if ( EndPos = MaxInt ) then
          raise;
      end;
    finally
      EndCallbackFetching;
    end;
    KeyFields.RowData := KeyRowData;
  finally
    FLst.Free;
  end;
  if tmpResult and ( FAGen = FetchingAbortedGen ) then
    Result := BufferRowNum
  else
    Result := InvalidNodePos;
end;

{------------------------------------------------------------------------------}

procedure TIB_BDataset.CalculateFields;
begin
  if Prepared and IsSelectSQL then
  begin
    inherited CalculateFields;
    KeyToChildAction( kcaUpdateKeyDescCalcFields, longint(Fields) );
    if not NeedToPost then
      PutRecord( NodeList.CurRef.Node, Fields );
    if BufferFields.RowNode = Fields.RowNode then
    begin
      BufferFields.RowData := Fields.RowData;
      BufferFields.OldRowData := Fields.OldRowData;
    end;
  end;
end;

procedure TIB_BDataset.DefaultDMLCacheReceivedItem(
  const ADMLCacheItem: TIB_DMLCacheItem);
var
  ii: integer;
  tmpStr: string;
begin
  tmpStr := '';
  for ii := 0 to KeyFields.ColumnCount - 1 do
  begin
    if ii > 0 then
      tmpStr := tmpStr + ';';
    tmpStr := tmpStr + KeyFields[ii].FieldName;
  end;
  if AnsiCompareText( ADMLCacheItem.KeyFieldNames, tmpStr ) = 0 then
  begin
    KeyFields.Values[ ADMLCacheItem.KeyFieldNames ] :=
      ADMLCacheItem.KeyFieldValues;
    if NeedToPost and ( Bookmark = KeyFields.RowData ) then
      FRefreshDML := true
    else
      case ADMLCacheItem.DMLCacheItemType of
        ditEdit: InvalidateBookmark( KeyFields.RowData );
        ditDelete: DeleteBufferBookmark( KeyFields.RowData );
        ditInsert: InsertBufferBookmark( KeyFields.RowData );
      end;
  end;
end;

procedure TIB_BDataset.SysBeforeScroll;
begin
  inherited SysBeforeScroll;
end;

procedure TIB_BDataset.SysAfterScroll;
begin
  FCurrentCursor.KillCursor;
  inherited SysAfterScroll;
end;

procedure TIB_BDataset.SysUpdateKeyData( PreserveOldKeyData: boolean );
var
  ii: integer;
  Src, Dst: TIB_Column;
  DstNode: PIB_Node;
begin
// This method serves two purposes.
// First, it maintains the proper values in
// the nodelist's CurRef.Node so that the Bookmark will accurately reflect
// the row's value in "real-time".
// Second, it prepares the KeyLinks buffers so that the necessary information
// will be there for getting the Old key values when doing a DML via
// TIB_UpdateSQL.
  if Assigned( NodeList ) then
  begin
    if NodeList.ProcessingUpdates then
      DstNode := NodeList.CachedUpdateRef.Node
    else
      DstNode := NodeList.CurRef.Node;
    if not Assigned( DstNode ) then Exit;

    if ( PreserveOldKeyData ) and
       ( not Assigned( DstNode.OldKeyData )) then
    begin
      GetMem( DstNode.OldKeyData, NodeList.KeyDataLength );
      Move( DstNode.KeyData^, DstNode.OldKeyData^, NodeList.KeyDataLength );
    end;

// I make this move because it is possible for there to be columns that are
// skipped in the field-links below.
// By copying the data the wrong way first the items that are skipped will
// simply stay the same instead of an un-initialized value.
    Move( DstNode.KeyData^, KeyFields.RowBuffer^, NodeList.KeyDataLength );

    for ii := 0 to FCurrentCursor.MasterLinks.Count - 1 do
    begin
      FCurrentCursor.Fields.GetByName(
        FCurrentCursor.MasterLinks.IndexValues[ ii ], Src );
      KeyFields.GetByName(
        FCurrentCursor.MasterLinks.IndexNames[ ii ], Dst );
      if Assigned( Src ) and Assigned( Dst ) then
        Dst.Assign( Src );
    end;
    Move( KeyFields.RowBuffer^, DstNode.KeyData^, NodeList.KeyDataLength );
    if Assigned( DstNode.OldKeyData ) and
       BuffersEqual( DstNode.KeyData,
                     DstNode.OldKeyData,
                     NodeList.KeyDataLength ) then
    begin
      FreeMem( DstNode.OldKeyData );
      DstNode.OldKeyData := nil;
    end;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_BDataset.SysBeforeSearch;
begin
  if Assigned( NodeList ) then
  begin
    FNodeList.RemoveRecords( false, FetchWholeRows, KeyLinksExist );
    inherited SysBeforeSearch;
    Fields.ClearBuffers( rsNone );
    BufferFields.ClearBuffers( rsNone );
    Fields.FRowNode := nil;
    BufferFields.FRowNode := nil;
  end;
end;

procedure TIB_BDataset.SysAfterExecuteForOutput;
begin
  FCurrentCursor.GetParamsData;
  FBufferCursor.GetParamsData;
  if not ( Refreshing or OrderingItemNoChanging ) then
    NodeList.RemoveRecords( CursorIsKeyFields,
                            FetchWholeRows,
                            KeyLinksExist );
  NodeList.AfterExecute( RefineZone );
  inherited SysAfterExecuteForOutput;
end;

procedure TIB_BDataset.SysJustBeforeOpen;
begin
  FRefineZone := rzTop;
  inherited SysJustBeforeOpen;
end;

procedure TIB_BDataset.SysBeforeOpen;
begin
  if ( RefineZone <> rzTop ) and ( not SQLIsRefined ) then
    FRefineZone := rzTop;
  inherited SysBeforeOpen;
end;

procedure TIB_BDataset.SysAfterOpen;
begin
  Fields.FRowNode := nil;
  BufferFields.FRowNode := nil;
  FLastAllocatedKeyVal := 0;
  inherited SysAfterOpen;
{ Tree stuff held back
  if IsTree then
    RebuildNodeList;
  Tree stuff held back }
end;

procedure TIB_BDataset.SysAfterClose;
begin
  FCurrentCursor.SysClose;
  FBufferCursor.SysClose;
  if Assigned( FDescCursor ) then
    FDescCursor.SysClose;
{
  if Assigned( FNullCursor ) then
    FNullCursor.SysClose;
}
  if Assigned( NodeList ) then
    NodeList.RemoveRecords((( CursorIsKeyFields ) or
                            ( Fields.ArrayCount > 0 ) or
                            ( Fields.BlobCount > 0 )) and
                           (( Refreshing ) or
                            ( OrderingItemNoChanging )),
                            FetchWholeRows,
                            KeyLinksExist );

  Fields.ClearBuffers( rsNone );
  BufferFields.ClearBuffers( rsNone );
  Fields.FRowNode := nil;
  BufferFields.FRowNode := nil;
  if (( not Refreshing ) and
      ( FRefiningIncSearch = 0 )) or OrderingItemNoChanging then
  begin
    if Assigned( FLocateCursor ) then
      FLocateCursor.Close;
    if Assigned( FFilterCursor ) then
      FFilterCursor.Close;
  end;
  CheckTransactionFlagForCachedUpdates;
  inherited SysAfterClose;
end;

procedure TIB_BDataset.SysBeforeUnprepare;
begin
  inherited SysBeforeUnprepare;
  if Assigned( FDescCursor ) then
    FDescCursor.SysDeallocate( true );
{!!!
  if Assigned( FNullCursor ) then
    FNullCursor.SysDeallocate( true );
!!!}
end;

procedure TIB_BDataset.SysAfterUnprepare;
begin
  FKeyVARMap.Clear;
  inherited SysAfterUnprepare;
  FCurrentCursor.SysDeallocate( true );
  FBufferCursor.SysDeallocate( true );
  SysDeallocateSeekCursors;
end;

procedure TIB_BDataset.SysDeallocateSeekCursors;
begin
  if Assigned( FSeekCursor ) then
  begin
    IB_Connection.DeallocateStmtHandle( @FSeekCursor );
    FSeekCursorPrepared := false;
  end;
end;

function TIB_BDataset.CanUseDualCursors: boolean;
begin
  Result := ( OrderingItemNo <> 0 ) and
            ( OrderingLinks.LinkParamIsSet[ OrderingLink, 'POS' ] );
  NodeList.KeepBofRowNumZero := false;
{$IFNDEF IBO_40_OR_GREATER}
  if Result and ( not Assigned( FIBODataset )) then
  begin
    Result := false;
    NodeList.KeepBofRowNumZero := true;
  end;
{$ENDIF}
end;

function TIB_BDataset.SysNeedToRefineSQL: boolean;
begin
  Result := inherited SysNeedToRefineSQL;
  if not Result then
    Result := Filtered and ( FilterPlan <> '' );
  if not Result then
    Result := CanUseDualCursors;
end;

function TIB_BDataset.GetRefineSQLWhere( Ascending,
                                         CheckNull: boolean ): string;
var
  tmpPos: integer;
  prmStr: string;
begin
// Need to make sure DB Engine can optimize this before I allow it.
  checknull := false;
  Result := OrderingLink;
  if IsColAttributeSet[ OrderingLink, IBO_NOCASE ] then
  begin
    Result := Trim( ColAttributeParams[ Result, IBO_NOCASE ] );
    if Result = '' then
      Result := 'UPPER( ' + OrderingLink + ' )';
  end;
  prmStr := OrderingLink;
  if prmStr <> '' then
  begin
    tmpPos := getLitSafePos( '.', prmStr, 1 );
    if tmpPos > 0 then
      System.Delete( prmStr, 1, tmpPos );
    prmStr := stLitCriteria( prmStr );
    prmStr := IB_Connection.mkVarIdent( Copy( IBO_ORDLINK + prmStr,1,31 ));
    if Ascending then
    begin
      if OrderingItemNo > 0 then
      begin
        if CheckNull then  
          Result := '(( ' + Result + ' >= ?' + prmStr + ' ) OR ( ' +
                            Result + ' IS NULL ))'
        else
          Result := '( ' + Result + ' >= ?' + prmStr + ' )';
      end
      else
      begin
        if CheckNull then  
          Result := '(( ' + Result + ' <= ?' + prmStr + ' ) OR ( ' +
                            Result + ' IS NULL ))'
        else
          Result := '( ' + Result + ' <= ?' + prmStr + ' )';

      end;
    end
    else
    begin
      if OrderingItemNo > 0 then
        Result := '( ' + Result + ' < ?' + prmStr + ' )'
      else
        Result := '( ' + Result + ' > ?' + prmStr + ' )';
    end;
  end;
end;

procedure TIB_BDataset.SysRefineSQL;
begin
  inherited SysRefineSQL;
  if not SQLWhereChanged then
    if CanUseDualCursors then
      SQLWhereLow.Add( GetRefineSQLWhere( true, true ));
  if Filtered and ( FilterPlan <> '' ) then
    SQLPlan.Text := 'PLAN ' + FilterPlan;
end;

procedure TIB_BDataset.FreeExtraCursors;
begin
  if Assigned( FLocateCursor ) then
  begin
    FLocateCursor.Free;
    FLocateCursor := nil;
  end;
  if Assigned( FFilterCursor ) then
  begin
    FFilterCursor.Free;
    FFilterCursor := nil;
  end;
end;

procedure TIB_BDataset.SysPrepareSQL;
var
  ii: integer;
  BindLinks: string;
  tmpStr: string;
  tmpInt: integer;
  tmpLen: longint;
  SQLSelectTxt: string;
  SQLFirstSkip: string;
begin
  if not RepreparingSQL then
    FRefineZone := rzTop;
  if Assigned( PSeekDA ) then
  begin
    FreeMem( PSeekDA );
    PSeekDA := nil;
  end;
  SysDeallocateSeekCursors; // Parameters can change when re-preparing.
  FreeExtraCursors;
  inherited SysPrepareSQL;
  if SQL.Count = 0 then
    raise EIB_StatementError.CreateWithSender( Self, E_BLANK_STATEMENT )
  else
  if IsSelectSQL then
  begin
    if Filtered and ( Filter <> '' ) then
    begin
      if Prepared then
        ExtractFilterClause( Self, SQLWhereItems, ParamValueLinks )
      else
        InvalidateSQL;
    end;
    if ( not RepreparingSQL ) or ( not FCurrentCursor.SQLIsValid ) then
    begin
      FKeySQLSelect := '';
      SQLFirstSkip := '';
      BindLinks := '';
      if ( KeyLinks.Count > 0 ) or // GetKeyLinks auto defines here...
         ( FetchWholeRows ) or
         ( FKeySQLSelect = '' ) then
      begin
        for ii := 0 to KeyLinks.Count - 1 do
        begin
          if ii = 0 then
          begin
            SQLFirstSkip := GetSQLFirstAndSkip( SQLSelect.Text );
            FKeySQLSelect := Trim( 'SELECT ' + SQLFirstSkip ) + ' ';
          end
          else
          begin
            FKeySQLSelect := FKeySQLSelect + ', ';
            BindLinks := BindLinks + #13#10;
          end;
          tmpStr := KeyLinks.IndexNames[ii];
          BindLinks := BindLinks + tmpStr + '=' + tmpStr;
          FKeySQLSelect := FKeySQLSelect + tmpStr;
        end;
        with FCurrentCursor do
        begin
          Inc( FIgnoreLayoutChange );
          try
            SQL.Assign( Self.SQL );
            MasterLinks.Text := Trim( BindLinks );
            KeyLinks.Clear;
          finally
            Dec( FIgnoreLayoutChange );
          end;
          OnBlobCallback := Self.OnBlobCallback;
        end;

        with FBufferCursor do
        begin
          Inc( FIgnoreLayoutChange );
          try
            SQL.Assign( FCurrentCursor.SQL );
          finally
            Dec( FIgnoreLayoutChange );
          end;
          OnBlobCallback := Self.OnBlobCallback;
        end;
      end;
    end;
    FCursorIsKeyFields := false;
    if not FetchWholeRows then
    begin
      if ( not ( FKeySQLSelect = '' )) and
         ( not ( SQLUnion.Count > 0 )) and
         ( not ( SQLIsAggregate )) and
         ( not ( SQLIsExecuteBlock )) then
      begin
        SQLSelect.Text := FKeySQLSelect;
        FCursorIsKeyFields := true;
      end;
    end
    else
    if KeyLinksAutoDefined and KeyLinksAreDBKEY then
    begin
      SQLSelectTxt := Trim( SQLSelect.Text );
      MakeServerSQL( SQLSelectTxt,
                     nil,
                     nil,
                     nil,
                     tmpStr,
                     ParamChar,
                     false,
                     tmpLen,
                     false );
      swap_chars( tmpStr, #13#10, ' ' );
      tmpInt := getLitSafeStrPos( '  ', tmpStr, 1 );
      while tmpInt <> 0 do
      begin
        System.Delete( tmpStr, tmpInt, 1 );
        tmpInt := getLitSafeStrPos( '  ', tmpStr, 1 );
      end;
      tmpStr := Trim( tmpStr );

      if AnsiUpperCase( tmpStr ) =
         AnsiUpperCase( Trim( 'SELECT ' + SQLFirstSkip ) + ' *' ) then

        SQLSelectTxt := Trim( 'SELECT ' + SQLFirstSkip ) + ' ' +
                        SysKeyRelation + '.*';
      tmpStr := Trim( SQLSelectTxt ) +
                #13#10'     , ' + SysKeyRelation +
                '.' + IBO_RDB + IBO_DB_KEY;
      SQLSelect.Text := tmpStr;
      if not RepreparingSQL then
      begin
        FCurrentCursor.SQLSelect.Text := tmpStr;
        FBufferCursor.SQLSelect.Text := tmpStr;
      end;
    end;
  end
  else
  begin
    FCurrentCursor.Unprepare;
    FBufferCursor.Unprepare;
  end;
end;

procedure TIB_BDataset.SysLayoutChange( Sender: TObject );
begin
  if Sender = FieldsAlignment then
    FBufferCursor.FieldsAlignment.Assign(FieldsAlignment)
  else
  if Sender = FieldsCharCase then
    FBufferCursor.FieldsCharCase.Assign(FieldsCharCase)
  else
  if Sender = FieldsDisplayFormat then
    FBufferCursor.FieldsDisplayFormat.Assign(FieldsDisplayFormat)
  else
  if Sender = FieldsDisplayLabel then
    FBufferCursor.FieldsDisplayLabel.Assign(FieldsDisplayLabel)
  else
  if Sender = FieldsGridLabel then
    FBufferCursor.FieldsGridLabel.Assign(FieldsGridLabel)
  else
  if Sender = FieldsGridTitleHint then
    FBufferCursor.FieldsGridTitleHint.Assign(FieldsGridTitleHint)
  else
  if Sender = FieldsDisplayWidth then
    FBufferCursor.FieldsDisplayWidth.Assign(FieldsDisplayWidth)
  else
  if Sender = FieldsEditMask then
    FBufferCursor.FieldsEditMask.Assign(FieldsEditMask)
  else
  if Sender = FieldsIndex then
    FBufferCursor.FieldsIndex.Assign(FieldsIndex)
  else
  if Sender = FieldsReadOnly then
    FBufferCursor.FieldsReadOnly.Assign(FieldsReadOnly)
  else
  if Sender = FieldsTrimming then
    FBufferCursor.FieldsTrimming.Assign(FieldsTrimming)
  else
  if Sender = FieldsVisible then
    FBufferCursor.FieldsVisible.Assign(FieldsVisible);
  if FIgnoreLayoutChange = 0 then
    inherited SysLayoutChange( Sender );
end;

procedure TIB_BDataset.BeginLayout;
begin
  inherited BeginLayout;
  FBufferCursor.BeginLayout;
end;

procedure TIB_BDataset.EndLayout;
begin
  inherited EndLayout;
  FBufferCursor.EndLayout;
end;

procedure TIB_BDataset.SysFirst;
var
  OldRowNum: longint;
begin
  if not Fetching then
  begin
    DisableControls;
    try
      if BufferActive then CheckBrowseMode;
      if ( not Active ) and ( BufferRowCount = 0 ) then
        Open;
      OldRowNum := RowNum;
      if ( Assigned( OrderingParam )) then
        GotoBof;
      if ( Assigned( NodeList )) and
         ( State in [ dssPrepared, dssBrowse ] ) and
         (( RowNum = OldRowNum ) or ( Assigned( OrderingParam ))) then
        RowNum := BofRowNum + 1;
    finally
      EnableControls;
    end;
  end;
end;

function TIB_BDataset.SysMoveBy( IncRows: longint ): longint;
var
  OldRowNum: longint;
begin
  OldRowNum := RowNum;
  SetRowNum( OldRowNum + IncRows );
  if IncRows <> 1 then
    Result := RowNum - OldRowNum
  else
  if Eof then
    Result := 0
  else
    Result := 1;
end;

procedure TIB_BDataset.SysLast;
var
  OldRowNum: longint;
begin
  if not Fetching then
  begin
    DisableControls;
    try
      if BufferActive then CheckBrowseMode;
      if ( not Active ) and ( BufferRowCount = 0 ) then
        Open;
      OldRowNum := RowNum;
      if ( Assigned( OrderingParam )) then
        GotoEof
      else
        SysFetchAll( 0 );
      if ( Assigned( NodeList )) and
         ( State in [ dssPrepared, dssBrowse ] ) and
         (( RowNum = OldRowNum ) or ( Assigned( OrderingParam ))) then
        RowNum := EofRowNum - 1;
    finally
      EnableControls;
    end;
  end;
end;

procedure TIB_BDataset.SysUpdateDescriptors;
  procedure CleanupInternalCursors;
  begin
    FKeySQLSelect := '';
    FCurrentCursor.SysDeallocate( true );
    FBufferCursor.SysDeallocate( true );
  end;
var
  ii: integer;
  tmpCol: TIB_Column;
  tmpStr: string;
  tmpWhere: string;
  tmpOrder: string;
  Bg, En, Ul: integer;
  prmStr: string;
begin
  CheckCursorFields;
  inherited SysUpdateDescriptors;
  if ( not RepreparingSQL ) and IsSelectSQL then
  begin
    if not CursorIsKeyFields then
    begin
      FKeyVARMap.Clear;
      for ii := 0 to KeyFields.ColumnCount - 1 do
      begin
        if CursorFields.GetByName( KeyFields.Columns[ii].FullFieldName,
                                   tmpCol ) or
           CursorFields.GetByName( KeyFields.Columns[ii].FullSQLName,
                                   tmpCol ) then
          FKeyVARMap.Add( tmpCol )
        else
          Break;
      end;
    end;
  end;
  if not RepreparingSQL then
    if Assigned( NodeList ) then
      NodeList.UpdateDescriptors( KeyFields.BufferLength );
  if Prepared and IsSelectSQL then
  begin
    try
      FCurrentCursor.FNeedPrepare := CursorIsKeyFields;
      FBufferCursor.FNeedPrepare := CursorIsKeyFields;
      FCurrentCursor.Prepare;
      FBufferCursor.Prepare;
      if SQLIsRefined then
      begin
        tmpStr := RefinedSQL;
        Ul := -1;
        GetSQLWhere( tmpStr, tmpWhere, Bg, En, Ul );
        prmStr := GetRefineSQLWhere( true, true );
        MakeServerSQL( prmStr, nil, nil, nil,
                       prmStr, ParamChar, false, ii, false );
        ii := Pos( prmStr, tmpWhere );
        if ii > 0 then
          tmpWhere := Copy( tmpWhere, 1, ii - 1 ) +
                      GetRefineSQLWhere( false, true ) +
                      Copy( tmpWhere, ii + Length( prmStr ), MaxInt );
        if tmpWhere <> '' then
        begin
          tmpOrder := 'ORDER BY ';
          with SQLOrderLinks do
          begin
            for ii := 0 to Count - 1 do
            begin
              if ii > 0 then
                tmpOrder := tmpOrder + #13#10'       , ';
              if IndexValues[ii] = 'DESC' then
                tmpOrder := tmpOrder + IndexNames[ii] + ' ASC'
              else
                tmpOrder := tmpOrder + IndexNames[ii] + ' DESC';
            end;
          end;
          if not Assigned( FDescCursor ) then
          begin
            FDescCursor := TIB_Dataset.Create( Self );
            FDescCursor.AfterFetchRow := DescCursorAfterFetchRow;
            FDescCursor.AfterFetchEof := DescCursorAfterFetchEof;
            FDescCursor.BeforeUnprepare := DescCursorBeforeUnprepare;
            FDescCursor.OnError := DoHandleError;
            FDescCursor.AutoFetchFirst := false;
          end;
          FDescCursor.IB_Connection := IB_Connection;
          FDescCursor.IB_Transaction := IB_Transaction;
          FDescCursor.CallbackInc := CallbackInc;
          FDescCursor.OnCallback := OnCallback;
          SetSQLWhere( tmpStr, tmpWhere, -1{!} );
          SetSQLOrder( tmpStr, tmpOrder );
          if Trim( FDescCursor.SQL.Text ) <> Trim( tmpStr ) then
            FDescCursor.SQL.Text := tmpStr;

{!!!
          if not Assigned( FNullCursor ) then
          begin
            FNullCursor := TIB_Dataset.Create( Self );
            FNullCursor.AfterFetchRow := NullCursorAfterFetchRow;
            FNullCursor.AfterFetchEof := NullCursorAfterFetchEof;
            FNullCursor.BeforeUnprepare := NullCursorBeforeUnprepare;
            FNullCursor.OnError := DoHandleError;
            FNullCursor.AutoFetchFirst := false;
          end;
          FNullCursor.IB_Connection := IB_Connection;
          FNullCursor.IB_Transaction := IB_Transaction;
          FNullCursor.CallbackInc := CallbackInc;
          FNullCursor.OnCallback := OnCallback;
          SetSQLWhere( tmpStr, tmpWhere );
          SetSQLOrder( tmpStr, tmpOrder );
          if Trim( FNullCursor.SQL.Text ) <> Trim( tmpStr ) then
            FNullCursor.SQL.Text := tmpStr;
!!!}

        end;
      end
      else
      begin
        if Assigned( FDescCursor ) then
        begin
          FDescCursor.Free;
          FDescCursor := nil;
        end;
{!!!
        if Assigned( FNullCursor ) then
        begin
          FNullCursor.Free;
          FNullCursor := nil;
        end;
!!!}
      end;
    except
      CleanupInternalCursors;
      raise;
    end;
  end
  else
    CleanupInternalCursors;
end;

procedure TIB_BDataset.SysPreparedChanged;
var
  ii: integer;
begin
  FCanInsert := true;
  if ( not Assigned( FBindingCursor )) and
     IsSelectSQL and
     ( InsertSQL.Count = 0 ) and
     ( not Assigned( FOnUpdateRecord )) then
    for ii := 0 to KeyFields.ColumnCount - 1 do
      if ( FindField( KeyFields.Columns[ii].FullFieldName ) = nil ) and
         ( FindField( KeyFields.Columns[ii].FullSQLName   ) = nil ) then
      begin
        FCanInsert := false;
        Break;
      end;
  inherited SysPreparedChanged;
end;

{------------------------------------------------------------------------------}

procedure TIB_BDataset.SysInsertRow;
//var
//  i, Level: integer;
begin
{ Tree stuff held back
  if FIsTree and not FNewAsChild then
  begin
    if FieldByName(TreeParID).IsNotNull then    //new record has the same parent as the current record - we create new sibling node
      FNewParID := FieldByName(TreeParID).AsInteger
    else
      FNewParID := -1; //-1 means "NULL" in the DoAfterInsert - new record is another root node
    FNewLevel := NodeList.CurRef.Node.Level;
  end;
  Tree stuff held back }

  if SysPrepare and Assigned( NodeList ) then
  begin
    NodeList.CurrentInsert;
    Fields.FRowNode := NodeList.CurRef.Node;
    if not CursorIsKeyFields then
    begin
      if FKeyVARMap.Count = 0 then
      begin
        Inc( FLastAllocatedKeyVal );
        KeyFields[0].AsInteger := FLastAllocatedKeyVal;
        Move( KeyFields[0].PSQLVAR.SQLInd^,
              NodeList.CurRef.Node.KeyData^,
              NodeList.KeyDataLength );
      end;
    end;
  end;
end;

procedure TIB_BDataset.SysAfterEdit;
begin
  CheckTransactionFlagForCachedUpdates;
  inherited SysAfterEdit;
end;

procedure TIB_BDataset.SysAfterInsert;
begin
  PutRecord( NodeList.CurRef.Node, Fields );
  if FCursorRecordCountValid then
    Inc( FCursorRecordCount );

{ Tree stuff held back
  if FIsTree then
  begin
    if FNewParID > -1 then
      FieldByName(TreeParID).AsInteger := FNewParID   //assign parent ID
    else
      FieldByName(TreeParID).Clear;                   //root node
    NodeList.CurRef.Node.Level := FNewLevel;          //new level - storing this make things easier when drawing the Tree
    NodeList.CurRef.Node.RowFlags := NodeList.CurRef.Node.RowFlags + [rfVisible];
  end;
  Tree stuff held back }

  CheckTransactionFlagForCachedUpdates;
  inherited SysAfterInsert;
  DataChange;
end;

procedure TIB_BDataset.SysAfterDelete;
begin
  CheckTransactionFlagForCachedUpdates;
  inherited SysAfterDelete;
end;

{------------------------------------------------------------------------------}

procedure TIB_BDataset.SysPostEditedRow;
begin
  inherited SysPostEditedRow;
  if not CachedUpdates then
    PutRecord( NodeList.CurRef.Node, Fields );
end;

procedure TIB_BDataset.SysPostInsertedRow;
begin
  inherited SysPostInsertedRow;
  if not CachedUpdates then
    PutRecord( NodeList.CurRef.Node, Fields );
end;

procedure TIB_BDataset.SysLockRow;
begin
  if not CachedUpdates then
  begin
    if ( bsBeforeEdit in BufferSynchroFlags ) or ( not SearchedEdits ) then
      if SysScrollCurrent( true, not SearchedEdits, false ) then
        DataChange
      else
        raise EIB_DatasetError.CreateWithSender( Self, E_UnableToEditRow );
  end;
  if PessimisticLocking then
    inherited SysLockRow;
end;

procedure TIB_BDataset.SysEditCursorRow;
begin
  inherited SysEditCursorRow;
  if not CachedUpdates then
  begin
    if ( bsAfterEdit in BufferSynchroFlags ) or
       (( Fields.FTempBlobIDCount > 0 ) and ( KeyLinksExist )) then
      if not SysScrollCurrent( true, false, false ) then
        SysAdjustCurrentRow( true, true );
//!        Cancel;
//!        raise EIB_DatasetError.CreateWithSender( Self,
//!                                                 'Edited record was lost' );
  end;
end;

procedure TIB_BDataset.SysInsertCursorRow;
begin
  inherited SysInsertCursorRow;
  if not CachedUpdates then
  begin
    if ( bsAfterInsert in BufferSynchroFlags ) or
       (( Fields.FTempBlobIDCount > 0 ) and ( KeyLinksExist )) or
       ( (not PreparedInserts) and ( InsertSQL.Count = 0 ) and
         FUpdateSQL.NeedRecordResync ) then
      if not SysScrollCurrent( true, false, false ) then
        SysCancelInsertedRow;
//!       Cancel;
//!       raise EIB_DatasetError.CreateWithSender( Self,
//!                                                'Inserted record was lost' );
  end;
end;

procedure TIB_BDataset.SysDeleteCursorRow;
begin
  inherited SysDeleteCursorRow;
  if not CachedUpdates then
    SysAdjustCurrentRow( true, true );
end;

{------------------------------------------------------------------------------}

function TIB_BDataset.IsUsingManualDML( UpdateKind: TIB_UpdateKind ): boolean;
begin
  if Assigned( OnUpdateRecord ) then
    Result := true
  else
    Result := inherited IsUsingManualDML( UpdateKind );
end;

procedure TIB_BDataset.SysBeforeSearchedDML;
var
  ii: integer;
  Src,
  Dst: TIB_Column;
begin
  with FCurrentCursor do
    for ii := 0 to MasterLinks.Count - 1 do
    begin
      Fields.GetByName( MasterLinks.IndexValues[ ii ], Src );
      Self.KeyFields.GetByName( MasterLinks.IndexNames[ ii ], Dst );
      if Assigned( Src ) and Assigned( Dst ) then
      begin
        if Src.OldIsNull then
          Dst.Clear
        else
        begin
          Dst.FSetText := Src.FOnSetText;
          Dst.FSetTextCol := Src;
          try
            Dst.AsVariant := Src.OldAsVariant;
          finally
            Dst.FSetText := nil;
            Dst.FSetTextCol := nil;
          end;
        end;
      end;
    end;
end;

procedure TIB_BDataset.SQL_DeleteRow;
begin
  if SearchedDeletes then
  begin
    SysBeforeSearchedDML;
    inherited SQL_DeleteRow;
  end
  else
  if SysGetCurrentCursor( false ) then
  begin
    inherited SQL_DeleteRow;
    FCurrentCursor.KillCursor;
  end;
end;

procedure TIB_BDataset.SQL_EditRow;
begin
  if SearchedEdits then
  begin
    SysBeforeSearchedDML;
    inherited SQL_EditRow;
  end
  else
  if SysGetCurrentCursor( true ) then
    inherited SQL_EditRow;
end;

function TIB_BDataset.SQL_LockRow: boolean;
begin
  Result := false;
  if SearchedEdits then
  begin
    SysBeforeSearchedDML;
    Result := inherited SQL_LockRow;
  end
  else
  if SysGetCurrentCursor( false ) then
    Result := inherited SQL_LockRow;
end;

function TIB_BDataset.SysGetCurrentCursor( CheckSynchro: boolean ): boolean;
{
var
  tmpRowData: string;
  tmpOldData: string;
}
begin
  if not FCurrentCursor.FCursorIsOpen then
  begin
{
    if CheckSynchro then begin
      tmpRowData := Fields.RowData;
      tmpOldData := Fields.OldRowData;
    end;
}
    if not SysScrollCurrent( true, true, false ) then
      raise EIB_DatasetError.CreateWithSender( Self, E_SCROLL_ERROR );
{
    if CheckSynchro then begin
      if tmpOldData <> Fields.OldRowData then begin
//      raise EIB_DatasetError.CreateWithSender( Self, E_Record_OutOfSynch );
      end;
      Fields.RowData := tmpRowData;
    end;
}
  end;
  Result := FCurrentCursor.FCursorIsOpen;
end;

{------------------------------------------------------------------------------}

procedure TIB_BDataset.SysCancelInsertedRow;
begin
  Fields.CancelBuffers( true );
  BufferFields.CancelBuffers( true );
  SysAdjustCurrentRow( true, true );
end;

procedure TIB_BDataset.SysAdjustCurrentRow( ADeleteNode,
                                            AScrollEvents: boolean );
var
  OldCurNode: PIB_Node;
  OldBufNode: PIB_Node;
begin
  try
    OldCurNode := NodeList.CurRef.Node;
    OldBufNode := NodeList.BufRef.Node;
    if ADeleteNode then
    begin
      if not BufferHasBof then ValidateRows( RowNum - 1, RowNum - 1 );
      if not BufferHasEof then ValidateRows( RowNum + 1, RowNum + 1 );
      // It's possible the node could be deleted in the above code.
      // This prevents more than one record being removed from the buffer.
      if NodeList.CurRef.Node = OldCurNode then
        NodeList.CurrentDelete;
    end;
    NodeList.BufferMoveBy( 0 );
    NodeList.CurrentMoveBy( 0 );
    if OldBufNode <> NodeList.BufRef.Node then
      FBufferCursor.QuickFetch( NodeList.BufRef.Node, false, false );
    if AScrollEvents or ( OldCurNode <> NodeList.CurRef.Node ) then
    begin
      SysScrollCurrent( false, false, false );
      DataChange;
      SysAfterScroll;
    end;
  except
    on E: Exception do
      Application.HandleException( E );
  end;
end;

procedure TIB_BDataset.DeleteBufferBookmark( ABookmark: string );
var
  ARowNum: longint;
begin
  if ABookmark <> '' then
  begin
    BufferBookmark := ABookmark;
    ARowNum := BufferRowNum;
    if ARowNum > BofRowNum then 
      DeleteBufferRowNum( ARowNum );
  end;
end;

procedure TIB_BDataset.DeleteBufferRowNum( ARowNum: longint );
var
  tmpBool: boolean;
begin
  BufferRowNum := ARowNum;
  if ( BufferRowNum > BofRowNum ) and ( BufferRowNum = ARowNum ) then
  begin
    tmpBool := RowNum = BufferRowNum;
    if tmpBool then
      SysBeforeScroll;
    if not BufferHasBof then
      ValidateRows( BufferRowNum - 1, BufferRowNum - 1 );
    if not BufferHasEof then
      ValidateRows( BufferRowNum + 1, BufferRowNum + 1 );
    NodeList.BufferDelete;
    try
      FBufferCursor.QuickFetch( NodeList.BufRef.Node, false, false );
      if tmpBool then
      begin
        SysScrollCurrent( false, false, false );
        DataChange;
        SysAfterScroll;
      end;
    finally
      SysStateChanged;
    end;
    CheckTransactionFlagForCachedUpdates;
  end;
end;

procedure TIB_BDataset.InsertBufferBookmark( ABookmark: string );
begin
{ TODO : This needs some more work!!! }
  if Fetching then
    AbortFetching;
  RefreshKeys;
end;

{------------------------------------------------------------------------------}

function TIB_BDataset.GetStatementType: TIB_StatementType;
begin
  Result := inherited GetStatementType;
  if ( Result in [ stSelect, stSelectForUpdate ] ) and
     FCurrentCursor.Prepared then
    Result := FCurrentCursor.StatementType;
end;

function TIB_BDataset.GetIsEmpty: boolean;
begin
  if ( not Prepared ) or ( not IsSelectSQL ) then
    Result := true
  else
  begin
    if ( BufferRowCount = 0 ) and ( not BufferHasEof ) then
      ValidateRows( EofRowNum, EofRowNum );
    if ( BufferRowCount = 0 ) and ( not BufferHasBof ) then
      ValidateRows( BofRowNum, BofRowNum );
    Result := BufferRowCount = 0;
  end;
end;

procedure TIB_BDataset.SetCachedUpdates( AValue: boolean );
begin
  if CachedUpdates <> AValue then
  begin
    if CachedUpdates then
      CancelUpdates;
    FCachedUpdates := AValue;
    CheckTransactionFlagForCachedUpdates;
  end;
end;

procedure TIB_BDataset.SetOnFilterRecord( AValue: TIB_FilterRecordEvent );
begin
  FOnFilterRecord := AValue;
  if Active and
     ( not Assigned( FIBODataset )) and
     ( not ( csDesigning in ComponentState )) then
    RefreshFilteredRows;
end;

function TIB_BDataset.CheckRowNum( var ARowNum: longint ): boolean;
var
  OldFetchingAborted: boolean;
begin
  Result := true;
  OldFetchingAborted := FFetchingAborted;
  try
    FFetchingAborted := false;
    if SQLIsRefined and Assigned( FDescCursor ) and
       Active and ( not BufferHasBof ) and ( ARowNum <= BofRowNum ) then
    begin
      BeginBusy( false );
      try
        while ( not FDescCursor.FetchingAborted ) and
              ( not BufferHasBof ) and
              ( ARowNum <= BofRowNum ) do
          NeedRecords( Self, false, BofRowNum - ARowNum + 1 );
        Result := not FDescCursor.FetchingAborted;
      finally
        EndBusy;
      end;
    end;
    if Result then
    begin
      if ARowNum < BofRowNum then
        ARowNum := BofRowNum;
      if Active and ( not BufferHasEof ) and ( ARowNum >= EofRowNum ) then
      begin
        BeginBusy( false );
        try
          while ( not FetchingAborted ) and
                ( not BufferHasEof ) and
                ( ARowNum >= EofRowNum ) do
            NeedRecords( Self, true, ARowNum - EofRowNum + 1 );
          Result := not FetchingAborted;
        finally
          EndBusy;
        end;
      end;
    end;

{!!!}
{
    if Result and SQLIsRefined and Assigned( FNullCursor ) and
       Active and not BufferHasEof and ( ARowNum <= BofRowNum ) then
    begin
      BeginBusy( false );
      try
        while ( not FNullCursor.FetchingAborted ) and
              ( not BufferHasBof ) and
              ( ARowNum <= BofRowNum ) do
          NeedRecords( Self, false, BofRowNum - ARowNum + 1 );
        Result := not FNullCursor.FetchingAborted;
      finally
        EndBusy;
      end;
    end;
}
{!!!}

    if Result then
    begin
      if ARowNum > EofRowNum then
      begin
        ARowNum := EofRowNum;
      end;
    end;
  finally
    if OldFetchingAborted then
    begin
      FFetchingAborted := true;
      Inc( FFetchingAbortedGen );
    end;
  end;
end;

procedure TIB_BDataset.SetRowNum( AValue: longint );
var
  WasMoved: boolean;
  curNode: PIB_Node;
begin
  if CheckRowNum( AValue ) then
  begin
    WasMoved := false;
    if RowNum <> AValue then
    begin
      if State = dssSearch then
        Open
      else
      begin
        SysBeforeScroll;
        WasMoved := true;
      end;
    end;
    // Even if scrolling 0 it is possible to come up with a new record
    // if a filter criteria was no longer satisfied, or something else.
    curNode := NodeList.CurRef.Node;
    NodeList.CurrentMoveBy( AValue - RowNum );
    if SysScrollCurrent( false, false, false ) then
    begin
      if WasMoved then
        DataChange
      else
      if curNode <> NodeList.CurRef.Node then
      begin
        SysBeforeScroll;
        DataChange;
        SysAfterScroll;
      end;
    end
    else
    if KeyLinksExist then
    begin
      SysAdjustCurrentRow( true, true ); // Record was deleted by another user.
      WasMoved := false; // Already announced the AfterScroll event here.
      SysStateChanged;
    end;
    if WasMoved then
      SysAfterScroll;
  end;
end;

procedure TIB_BDataset.SetBufferRowNum( AValue: longint );
var
  DoStateChg: boolean;
begin
  if CheckRowNum( AValue ) then
  begin
    if ( AValue = RowNum ) and NeedToPost then
    begin
      NodeList.BufferPos := NodeList.CurRef.Pos;
      SysUpdateKeyData( false );
      BufferFields.RowData := Fields.RowData;
      BufferFields.OldRowData := Fields.OldRowData;
      BufferFields.SetRowState( Fields.RowState );
    end
    else
    if ( AValue <> BufferRowNum ) or
       ( AValue <= BofRowNum ) or
       ( AValue >= EofRowNum ) or
       ( BufferFields.RowNode <> NodeList.BufRef.Node ) or
       ( Assigned( BufferFields.RowNode ) and
         ( not Assigned( BufferFields.RowNode.RecordData ))) then
    begin
      DoStateChg := false;
      if NodeList.BufRef.Pos < NodeList.BofRef.Pos then
        NodeList.BufferPos := NodeList.BofRef.Pos;
      NodeList.BufferMoveBy( AValue - BufferRowNum );
      while not FBufferCursor.QuickFetch( NodeList.BufRef.Node,
                                          false,
                                          false ) do
      begin
        if KeyLinksExist then
        begin
          NodeList.BufferDelete;
          DoStateChg := true;
        end
        else
          Break;
      end;
      if DoStateChg then
        SysStateChanged;
    end;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_BDataset.SetFilterText( const AValue: string );
var
  OldFilterText: string;
begin
  if Filter <> AValue then
  begin
    OldFilterText := Filter;
    FFilterText := AValue;
    if Prepared then
    begin
      if Filtered then
        try
          InvalidateSQL;
          if Active then
            Refresh
          else
            Prepare;
        except
          FFilterText := OldFilterText;
          raise;
        end
      else
      if Assigned( FFilterCursor ) then
        FFilterCursor.InvalidateSQL;
    end;
  end;
end;

function TIB_BDataset.GetFiltered: boolean;
begin
  Result := NodeList.Filtered;
end;

procedure TIB_BDataset.SetFiltered( AValue: boolean );
var
  tmpFiltered: boolean;
  tmpRowNum: longint;
begin
  if Filtered <> AValue then
  begin
    if BufferActive then CheckBrowseMode;
    tmpFiltered := Filtered;
    try
      if Trim( Filter ) = '' then
      begin
        if Active and Assigned( OnFilterRecord ) then
        begin
          DisableControls;
          try
            NodeList.Filtered := AValue;
            tmpRowNum := RowNum;
            CheckRowNum( tmpRowNum );
            if SysScrollCurrent( false, false, false ) then
            begin
              DataChange;
              SysAfterScroll;
            end
            else
            if KeyLinksExist then
            begin
              SysAdjustCurrentRow( true, true ); // Rec deleted by an user.
              SysStateChanged;
            end;
          finally
            EnableControls;
            ProcessLinkEvent( setInvalidateRows, InvalidNodePos );
            FCursorRecordCountValid := false;
          end;
        end
        else
          NodeList.Filtered := AValue;
      end
      else
      if Prepared then
      begin
        InvalidateSQL;
        NodeList.Filtered := AValue;
        if Active then
          Refresh
        else
          Prepare;
      end
      else
        NodeList.Filtered := AValue;
    except
      NodeList.Filtered := tmpFiltered;
      raise;
    end;
  end;
end;

function TIB_BDataset.GetFilterDeletedRecords: boolean;
begin
  Result := NodeList.FilterDel;
end;

procedure TIB_BDataset.SetFilterDeletedRecords( AValue: boolean );
var
  tmpFilterDeletedRecords: boolean;
  tmpRowNum: longint;
begin
  if FilterDeletedRecords <> AValue then
  begin
    if BufferActive then CheckBrowseMode;
    tmpFilterDeletedRecords := FilterDeletedRecords;
    try
      if Active then
      begin
        DisableControls;
        try
          NodeList.FilterDel := AValue;
          tmpRowNum := RowNum;
          CheckRowNum( tmpRowNum );
          if SysScrollCurrent( false, false, false ) then
          begin
            DataChange;
            SysAfterScroll;
          end
          else
          if KeyLinksExist then
          begin
            SysAdjustCurrentRow( true, true ); // Rec deleted by an user.
            SysStateChanged;
          end;
        finally
          EnableControls;
          ProcessLinkEvent( setInvalidateRows, InvalidNodePos );
          FCursorRecordCountValid := false;
        end;
      end
      else
        NodeList.FilterDel := AValue;
    except
      NodeList.FilterDel := tmpFilterDeletedRecords;
      raise;
    end;
  end;
end;

procedure TIB_BDataset.RefreshFilteredRows;
begin
  NodeList.RefreshFilteredRows;
  SysAdjustCurrentRow( false, true );
  ProcessLinkEvent( setInvalidateRows, InvalidNodePos );
end;

procedure TIB_BDataset.GetRecordIsFiltered(     Sender: TObject;
                                                ANode: PIB_Node;
                                            var ABoolean: boolean );
begin
  if ANode = Fields.RowNode then
    ABoolean := not CheckRecordFilter( Fields )
  else
  if ANode = BufferFields.RowNode then
    ABoolean := not CheckRecordFilter( BufferFields )
  else
  begin
    FBufferCursor.QuickFetch( ANode, false, false );
    ABoolean := not CheckRecordFilter( BufferFields );
  end;
end;

function TIB_BDataset.GetCalculatingFields: boolean;
begin
  Result := inherited GetCalculatingFields or
            CursorFields.FCalculatingFields or
            BufferFields.FCalculatingFields;
end;

procedure TIB_BDataset.NodeListBeginBusy( Sender: TObject );
begin
  BeginBusy( CallbackInc >= 0 );
end;

procedure TIB_BDataset.NodeListEndBusy( Sender: TObject );
begin
  EndBusy;
end;

function TIB_BDataset.NeedRecords( Sender: TObject;
                                   AAscending: boolean;
                                   ANumOfRecs: longint ): longint;
var
  ii: integer;
  tmpCol: TIB_Column;
begin
  if AAscending then
    Result := SysCursorMoveByRow( ANumOfRecs )
  else
  if not Assigned( FDescCursor ) then
    Result := SysCursorMoveByRow( ANumOfRecs )
  else
  begin
    if not FDescCursor.Prepared then
    begin
      FDescCursor.Prepare;
      with FDescCursor.Fields do
        for ii := 0 to PSQLDA.SQLd - 1 do
          PSQLDA.SQLVAR[ii] := Self.CursorFields.PSQLDA.SQLVAR[ii];
    end;
    if not FDescCursor.Active then
    begin
// I have discovered that it helps a whole lot to get the full and exact
// value of one of the records in order to process this query. As a result,
// it is much better to try and find the value from the buffer if it is there.
// It has to do with the descending order impacting the fetch first speed.
// This is also the beginnings of a truncated dataset for unidirectional.
      if ( RefineZone = rzMid ) and ( CursorRowNum < 1 ) and
         ( not CursorEof ) then
        SysFetchNext;
      if ( RefineZone = rzMid ) and
         (( CursorRowNum > 1 ) or
         (( CursorRowNum = 1 ) and ( not CursorEof ))) then
      begin
        GetRecord( NodeList.BofRef.Node.Next, CursorFields );
        if Assigned( OrderingParam ) then
        begin
          if CursorFields.GetByName( OrderingLink, tmpCol ) then
            FDescCursor.Params[ OrderingParam.FieldNo ].AsString :=
              mkOrderingRefineStr( tmpCol.AsString, RefineZone = rzBot );
        end;
      end
      else
      begin
        FDescCursor.Params.RowData := Params.RowData;
        if Assigned( OrderingParam ) then
          FDescCursor.Params[ OrderingParam.FieldNo ].AsString :=
            mkOrderingRefineStr( OrderingRefineStr, RefineZone = rzBot );
      end;
      FDescCursor.Open;
    end;
    Result := FDescCursor.SysCursorMoveByRow( ANumOfRecs );
  end;
end;

function TIB_BDataset.GetFilterOptions: TIB_FilterOptions;
begin
  Result := NodeList.FilterOptions;
end;

procedure TIB_BDataset.SetFilterOptions( AValue: TIB_FilterOptions );
var
  OldFilterOptions: TIB_FilterOptions;
begin
  if FilterOptions <> AValue then
  begin
    OldFilterOptions := FilterOptions;
    NodeList.FilterOptions := AValue;
    if Prepared and Filtered then
    try
      InvalidateSQL;
      if Active then
        Refresh
      else
        Prepare;
    except
      NodeList.FilterOptions := OldFilterOptions;
      raise;
    end;
  end;
end;

procedure StripEndChars( var AString: string; const AChar: char );
begin
  while ( Length( AString ) > 0 ) and
        ( AString[ Length( AString ) ] = AChar ) do
    System.Delete( AString, Length( AString ), 1 );
end;

function TIB_BDataset.GetOrderingRefineStr: string;
begin
  if Assigned( OrderingParam ) then
  begin
    Result := OrderingParam.AsString;
    if OrderingParam.IsText then
    else
    case OrderingParam.SQLType of
      SQL_SHORT,
      SQL_SHORT_:
        if ( Result = IntToStr(High(smallint))) or
           ( Result = IntToStr(Low(smallint))) then
          Result := '';
      SQL_LONG,
      SQL_LONG_:
        if ( Result = IntToStr(High(integer))) or
           ( Result = IntToStr(Low(integer))) then
          Result := '';
      SQL_INT64,
      SQL_INT64_:
{$IFDEF IBO_VCL40_OR_GREATER}
        if ( Result = IntToStr(High(int64))) or
           ( Result = IntToStr(Low(int64))) then
          Result := '';
{$ELSE}
        raise EIB_DatasetError.CreateWithSender( Self,
                                                 Format( E_UNSUPPORTED_COLUMN,
                                                   [ OrderingParam.SQLType ] ));
{$ENDIF}
      SQL_TYPE_DATE,
      SQL_TYPE_DATE_:
        if ( Result = DateToStr(maxdate)) or
           ( Result = DateToStr(mindate)) then
          Result := '';
      SQL_DOUBLE,
      SQL_DOUBLE_:
        if ( Result = FloatToStr(maxdouble)) or
           ( Result = FloatToStr(mindouble)) then
          Result := '';
      SQL_TIMESTAMP,
      SQL_TIMESTAMP_:
        if ( Result = DateToStr(maxdate)) or
           ( Result = DateToStr(mindate)) then
          Result := '';
      else
{ TODO : Do the other column types here.}
        raise EIB_DatasetError.CreateWithSender( Self,
                                                 Format( E_UNSUPPORTED_COLUMN,
                                                 [ OrderingParam.SQLType ] ));
    end;
    if OrderingParam.IsText then
    begin
      StripEndChars( Result, #255 );
      StripEndChars( Result, #0 );
    end;
  end
  else
    Result := '';
end;

procedure TIB_BDataset.SysBeforeExecuteForOutput;
begin
  inherited SysBeforeExecuteForOutput;
// This is to make sure that when changing from ascending to descending
// the OrderingParam value has the right format to insure that the correct
// records will be searched for.
  if RefineZone = rzMid then
    SetOrderingRefineStr( OrderingRefineStr )
  else
    SetOrderingRefineStr( '' );
end;

procedure TIB_BDataset.SetOrderingRefineStr( AValue: string );
var
  tmpBuffBk: string;
  WasActive: boolean;
begin
  if Assigned( OrderingParam ) then
  begin
    tmpBuffBk := KeyFields.RowData;
    if AValue <> '' then
      FRefineZone := rzMid;
    AValue := mkOrderingRefineStr( AValue, false );
    Inc( FPreventKeySeeking );
    Inc( FRefiningIncSearch );
    try
      if OrderingParam.IsNull or ( OrderingParam.AsString <> AValue ) then
      begin
        WasActive := Active;
        SysClose;
        OrderingParam.FBlankIsNull := false;
        OrderingParam.AsString := AValue;
        OrderingParam.IsNull := false;
        if WasActive then
          SysOpen;
        FIncSearchNearestRowNum := InvalidNodePos; // May need out of a level.
        FIncSearchCurRow := InvalidNodePos; // Might need out of this level.!!!
      end;
    finally
      Dec( FRefiningIncSearch );
      Dec( FPreventKeySeeking );
      KeyFields.RowData := tmpBuffBk;
    end;
  end;
end;

function TIB_BDataset.mkOrderingRefineStr( const AValue: string;
                                                 Desc: boolean ): string;
var
  tmpChar: char;
//  tmpPos: integer;
begin
  Result := '';
  if Assigned( OrderingParam ) then
  begin
    Result := AValue;
//    tmpPos := OrderingRefinePos;
    if OrderingParam.IsText then
    begin
//      if tmpPos > 0 then
//        Result := Copy( Result, 1, tmpPos );
    end
    else
    case OrderingParam.SQLType of
      SQL_SHORT,
      SQL_SHORT_:
        if ( AValue = IntToStr( High(smallint) )) or
           ( AValue = IntToStr( Low(smallint) )) then
          Result := '';
      SQL_LONG,
      SQL_LONG_:
        if ( AValue = IntToStr( High(integer) )) or
           ( AValue = IntToStr( Low(integer) )) then
          Result := '';
      SQL_INT64,
      SQL_INT64_:
{$IFDEF IBO_VCL40_OR_GREATER}
        if ( Result = IntToStr(High(int64))) or
           ( Result = IntToStr(Low(int64))) then
          Result := '';
{$ELSE}
        raise EIB_DatasetError.CreateWithSender( Self,
                                                 Format( E_UNSUPPORTED_COLUMN,
                                                   [ OrderingParam.SQLType ] ));
{$ENDIF}
      SQL_TYPE_DATE,
      SQL_TYPE_DATE_:
        if ( Result = DateToStr(maxdate)) or
           ( Result = DateToStr(mindate)) then
          Result := '';
      SQL_DOUBLE,
      SQL_DOUBLE_:
        if ( AValue = FloatToStr( maxdouble )) or
           ( AValue = FloatToStr( mindouble )) then
          Result := '';
      SQL_TIMESTAMP,
      SQL_TIMESTAMP_:
        if ( AValue = DateToStr( maxdate )) or
           ( AValue = DateToStr( mindate )) then
          Result := '';
      else
      { TODO : Do the other column types here.}
        raise EIB_DatasetError.CreateWithSender( Self,
                                                 Format( E_UNSUPPORTED_COLUMN,
                                                 [ OrderingParam.SQLType ] ));
    end;
    if Assigned( OrderingField ) then
      if ( OrderingField.NoCaseFieldName <> '' ) then
        Result := AnsiUpperCase( Result );
  // end;  NEED to still be in check for Assigned(OrderingParam)
  if (( OrderingItemNo < 0 ) and ( not Desc )) or
     (( OrderingItemNo > 0 ) and Desc ) then
  begin
    if OrderingParam.IsText then
    begin
//      tmpChar := #255;
      tmpChar := 'z';
      if Assigned( IB_Connection ) and
         Assigned( IB_Connection.OnGetHighCollateChar ) then
        IB_Connection.OnGetHighCollateChar( OrderingParam, tmpChar );
      Result := PadRight( AValue, tmpChar, OrderingParam.SQLLen, true )
    end
    else
    if AValue = '' then
      case OrderingParam.SQLType of
        SQL_SHORT,
        SQL_SHORT_: Result := IntToStr( High( SmallInt ));
        SQL_LONG,
        SQL_LONG_: Result := IntToStr( High( Integer ));
        SQL_INT64,
        SQL_INT64_:
{$IFDEF IBO_VCL40_OR_GREATER}
          Result := IntToStr( High( int64));
{$ELSE}
          raise EIB_DatasetError.CreateWithSender( Self,
                                                   Format( E_UNSUPPORTED_COLUMN,
                                                   [ OrderingParam.SQLType ] ));
{$ENDIF}
        SQL_TYPE_DATE,
        SQL_TYPE_DATE_: Result := DateToStr(maxdate);
        SQL_DOUBLE,
        SQL_DOUBLE_: Result := FloatToStr( maxdouble );
        SQL_TIMESTAMP,
        SQL_TIMESTAMP_: Result := DateToStr( maxdate );
        else
        {TODO : Do the other column types here.}
      end;
  end
  else
  begin
    if ( OrderingParam.SQLType = SQL_TEXT ) or
       ( OrderingParam.SQLType = SQL_TEXT_ ) then
      Result := PadRight( AValue, #0, OrderingParam.SQLLen, true )
    else
    if AValue = '' then
      case OrderingParam.SQLType of
        SQL_SHORT,
        SQL_SHORT_: Result := IntToStr( Low( SmallInt ));
        SQL_LONG,
        SQL_LONG_: Result := IntToStr( Low( Integer ));
        SQL_INT64,
        SQL_INT64_:
{$IFDEF IBO_VCL40_OR_GREATER}
          Result := IntToStr( Low( int64));
{$ELSE}
          raise EIB_DatasetError.CreateWithSender( Self,
                                                   Format( E_UNSUPPORTED_COLUMN,
                                                   [ OrderingParam.SQLType ] ));
{$ENDIF}
        SQL_TYPE_DATE,
        SQL_TYPE_DATE_: Result := DateToStr(mindate);
        SQL_DOUBLE,
        SQL_DOUBLE_: Result := FloatToStr( mindouble );
        SQL_TIMESTAMP,
        SQL_TIMESTAMP_: Result := DateToStr( mindate );
        else
        {TODO : Do the other column types here.}
      end;
  end;
  end; // now we can end check for Assigned(OrderingParam)
end;

function TIB_BDataset.GetOrderingRefinePos: smallint;
var
  tmpStr: string;
begin
  Result := 0;
  if Assigned( OrderingParam ) then
  begin
    tmpStr := Trim( OrderingLinks.LinkParamValue[ OrderingLink, 'POS' ] );
    if tmpStr <> '' then
      Result := StrToInt( tmpStr );
  end;
end;

procedure TIB_BDataset.SetOrderingRefinePos( AValue: smallint );
begin
  if Assigned( OrderingParam ) then
  begin
    if OrderingRefinePos <> AValue then
    begin
      with OrderingLinks do
        try
          OnChange := nil;
          LinkParamValue[ OrderingLink, 'POS' ] := IntToStr(AValue);
        finally
          OnChange := OnSQLChange;
        end;
    end;
  end;
end;

procedure TIB_BDataset.SetSelected( ARowNum: longint; AValue: boolean );
var
  ANode: PIB_Node;
  ABookmark: string;
begin
  if Assigned( NodeList ) then
    with NodeList do
    begin
      if ARowNum = RowNum then
        ANode := Fields.RowNode
      else
      begin
        if BufferRowNum <> ARowNum then
          BufferRowNum := ARowNum;
        ANode := BufferFields.RowNode;
      end;
      if Assigned( ANode ) then
      begin
        if NodeList.SetNodeSelected( ANode, AValue ) then
        begin
          ProcessLinkEvent( setSelectedChanged, ARowNum );
          ABookmark := BinaryToHexText( ANode.KeyData, KeyDataLength );
          if Assigned( FOnRowSelectedChanged ) then
            FOnRowSelectedChanged( Self, ARowNum, ABookmark, AValue );
        end;
      end;
    end;
end;

function TIB_BDataset.GetSelected( ARowNum: longint ): boolean;
var
  ANode: PIB_Node;
begin
  Result := false;
  if Assigned( NodeList ) and ( ARowNum > BofRowNum ) then
    with NodeList do
    begin
      if ARowNum = RowNum then
        ANode := Fields.RowNode
      else
      begin
        if BufferRowNum <> ARowNum then
          BufferRowNum := ARowNum;
        ANode := BufferFields.RowNode;
      end;
      Result := Assigned( ANode ) and ( rfSelected in ANode.RowFlags );
    end;
end;

procedure TIB_BDataset.SetBookmarkSelected( const ABookmark: string;
                                                  AValue: boolean );
var
  ANodeRef: TIB_NodeRef;
  ARowNum: longint;
begin
  if Assigned( NodeList ) and ( ABookmark <> '' ) then
    with NodeList do
    begin
      try
        HexTextToBinary( ABookmark, BofRef.Node.KeyData, KeyDataLength );
      except
        raise EIB_DatasetError.CreateWithSender(
          Self, E_Invalid_Bookmark + ' in SetBookmarkSelected()' );
      end;
      if LookupNode( BofRef.Node.KeyData, true, ANodeRef ) then
        with ANodeRef.Node^ do
          if ( rfSelected in RowFlags ) <> AValue then
          begin
            if AValue then
              RowFlags := RowFlags + [ rfSelected ]
            else
              RowFlags := RowFlags - [ rfSelected ];
            ARowNum := NodeList.GetRowNum( ANodeRef );
            if ANodeRef.Pos > NodeList.BofRef.Pos then 
              ProcessLinkEvent( setSelectedChanged, ARowNum );
            if Assigned( FOnRowSelectedChanged ) then
              FOnRowSelectedChanged( Self, ARowNum, ABookmark, AValue );
          end;
    end;
end;

function TIB_BDataset.GetBookmarkSelected( const ABookmark: string ): boolean;
var
  ANodeRef: TIB_NodeRef;
begin
  Result := false;
  if Assigned( NodeList ) then with NodeList do
  begin
    try
      HexTextToBinary( ABookmark, BofRef.Node.KeyData, KeyDataLength );
    except
      raise EIB_DatasetError.CreateWithSender(
        Self, E_Invalid_Bookmark + ' in GetBookmarkSelected()' );
    end;
    if LookupNode( BofRef.Node.KeyData, true, ANodeRef ) then
    {!!! DEL}
      Result := rfSelected in ANodeRef.Node.RowFlags;
  end;
end;

procedure TIB_BDataset.SelectedBookmarks( AStrings: TStrings );
var
  AValue: PIB_Node;
  tmpLen: integer;
begin
  AStrings.BeginUpdate;
  try
    AStrings.Clear;
    if Assigned( NodeList ) then
    begin
      if NodeList.DefaultSelectedState then
        FetchAll
      else
      begin
        tmpLen := 0;
        AValue := NodeList.TempList;
        while Assigned( AValue ) do
        begin
          if rfSelected in AValue.RowFlags then
          begin
            tmpLen := 1;
            Break;
          end;
          AValue := AValue.Next;
        end;
        if tmpLen = 1 then
          FetchAll;
      end;
      tmpLen := NodeList.KeyDataLength;
      AValue := NodeList.BofRef.Node.Next;
      while Assigned( AValue.Next ) do
      begin
        if rfSelected in AValue.RowFlags then
          AStrings.Add( BinaryToHexText( AValue.KeyData, tmpLen ));
        AValue := AValue.Next;
      end;
    end;
  finally
    AStrings.EndUpdate;
  end;
end;

procedure TIB_BDataset.ToggleSelected;
begin
  if Assigned( NodeList ) then
  begin
    NodeList.ToggleSelected;
    ProcessLinkEvent( setSelectedChanged, InvalidNodePos );
    if Assigned(FOnMultiSelect) then
      FOnMultiSelect( Self ) ;
  end;
end;

procedure TIB_BDataset.SelectAll( State: boolean );
begin
  if Assigned( NodeList ) then
  begin
    NodeList.SelectAll( State );
    ProcessLinkEvent( setSelectedChanged, InvalidNodePos );
    if Assigned(FOnMultiSelect) then
      FOnMultiSelect( Self ) ;
  end;
end;

procedure TIB_BDataset.SelectRange( StartRow, EndRow: longint;
                                    State, Exclusive: boolean );
begin
  if Assigned( NodeList ) then
  begin
    NodeList.SelectRange( StartRow, EndRow, State, Exclusive );
    ProcessLinkEvent( setSelectedChanged, InvalidNodePos );
    if Assigned(FOnMultiSelect) then
      FOnMultiSelect( Self ) ;
  end;
end;

procedure TIB_BDataset.InvalidateSQLWithCursors;
begin
  InvalidateSQL;
  FCurrentCursor.InvalidateSQL;
  FBufferCursor.InvalidateSQL;
  if Assigned( FDescCursor ) then
    FDescCursor.InvalidateSQL;
end;

procedure TIB_BDataset.InvalidateCalculatedFields;
var
  ii: integer;
  eofr: longint;
begin
  ii := BofRowNum + 1;
  eofr := EofRowNum;
  while ii < eofr do
  begin
    BufferRowNum := ii;
    if ( RowNum <> BufferRowNum ) or ( not NeedToPost ) then
    begin
      BufferFields.CalculateFields;
      PutRecord( NodeList.BufRef.Node, BufferFields );
    end;
    Inc( ii );
  end;
  ProcessLinkEvent( setInvalidateRows, InvalidNodePos );
end;

procedure TIB_BDataset.InvalidateRows;
begin
  if Assigned( NodeList ) then NodeList.InvalidateNodes;
  inherited InvalidateRows;
  Include( FDatasetFlags, dsfWasInvalidated );
end;

procedure TIB_BDataset.InvalidateRowNum( ARowNum: longint );
begin
  if Assigned( NodeList ) then
  begin
    BufferRowNum := ARowNum;
    NodeList.InvalidateNode( NodeList.BufRef.Node );
  end;
  inherited InvalidateRowNum( ARowNum );
  Include( FDatasetFlags, dsfWasInvalidated );
end;

function TIB_BDataset.InvalidateBookmark( const ABookmark: string ): boolean;
var
  ANodeRef: TIB_NodeRef;
  ARowNum: longint;
begin
  try
    Result := NodeList.InvalidateBookmark( ABookmark, ANodeRef );
    if Result then
    begin
      ARowNum := NodeList.GetRowNum( ANodeRef );
      if ( ARowNum = RowNum ) and ( not NeedToPost ) then
        SysMoveBy( 0 );
      if ARowNum = BufferRowNum then
      begin
        BufferRowNum := BofRowNum;
        BufferRowNum := ARowNum;
      end;
      ProcessLinkEvent( setInvalidateRows, ARowNum );
    end;
    Include( FDatasetFlags, dsfWasInvalidated );
  except
    raise EIB_DatasetError.CreateWithSender(
      Self, E_Invalid_Bookmark + ' in InvalidateBookmark()' );
  end;
end;

function TIB_BDataset.ValidateRows( Start, Finish: longint ): boolean;
var
  ii: integer;
  tmpInt: longint;
  tmpState: TIB_RowState;
  tmpNode: PIB_Node;
  tmpBool: boolean;
  aSt, aFn: longint;
begin
  aSt := Start;
  aFn := Finish;
  tmpBool := false;
  Result := false;
  if Unidirectional then Exit;
  if Start > Finish then
  begin
    tmpInt := Finish;
    Finish := Start;
    Start  := tmpInt;
  end;
  if Prepared then
  begin
    tmpState := BufferFields.RowState; 
    tmpInt := BufferRowNum;
    if BufferHasEof and (Finish >= EofRowNum) then Finish := EofRowNum - 1;
    BufferRowNum := Finish;
    if BufferRowNum >= EofRowNum then BufferRowNum := EofRowNum - 1;
    if BufferRowNum <= BofRowNum then Exit;
    Finish := NodeList.BufRef.Pos;
    BufferRowNum := Start;
    if BufferRowNum <= BofRowNum then BufferRowNum := BofRowNum + 1;
    if BufferRowNum >= EofRowNum then Exit;
    Start := NodeList.BufRef.Pos;
    Result := ( Start = aSt ) and ( Finish = aFn );
    try
      tmpNode := NodeList.BufRef.Node.Next;
      if Assigned( tmpNode ) then
      begin
        ii := Start + 1;
        while ii < Finish do
        begin
          if Assigned( tmpNode ) then
          begin
            if ( tmpNode.RecordLen = 0 ) and
               (( tmpNode <> Fields.RowNode ) or
                ( State <> dssInsert )) then
            begin
              if not tmpBool then
              begin
                tmpBool := true;
                IB_Session.BeginBusy( false );
              end;
              if ( ( not ( rfDeleted in tmpNode.RowFlags )) or
                   ( not NodeList.FilterDel )) and
                 ( ( not ( rfFiltered in tmpNode.RowFlags )) or
                   ( not NodeList.Filtered )) then
                FBufferCursor.QuickFetch( tmpNode, false, false );
            end;
            tmpNode := tmpNode.Next;
          end
          else
          begin
            Result := false;
            Break;
          end;
          Inc( ii );
        end;
      end;
    finally
      if tmpState <> rsNone then
        BufferRowNum := tmpInt;
      if tmpBool then
        IB_Session.EndBusy;
    end;
  end;
end;

function TIB_BDataset.SysRecordCount: longint;
var
  tmpCnt: integer;
begin
  Result := inherited SysRecordCount;
  Inc( Result, CachedInsertCount - CachedDeleteCount );
  if Filtered then
  begin
    tmpCnt := NodeList.BofRef.FilCnt +
              NodeList.EofRef.FilCnt;
    if NodeList.FilterDel then
      tmpCnt := tmpCnt - ( NodeList.BofRef.DblCnt +
                           NodeList.EofRef.DblCnt );
    if tmpCnt > 0 then
      Result := Result - Round(( Result * tmpCnt ) /
                               ( BufferRowCount + tmpCnt ));
  end;
  if Result < BufferRowCount then
    Result := BufferRowCount;
end;

function TIB_BDataset.SysGetCursorRecordCount: longint;
var
  IsAggProcExec: boolean;
begin
  if Assigned( FOnGetRecordCount ) then
  begin
    FOnGetRecordCount( Self, Result );
    Dec( Result, CachedInsertCount - CachedDeleteCount );
  end
  else
  if Active and BufferHasEof and BufferHasBof and ( not SQLIsRefined ) then
  begin
    Result := BufferRowCount;
    Dec( Result, CachedInsertCount - CachedDeleteCount );
  end
  else
  begin
    IsAggProcExec := SQLIsAggregate or SQLIsSelectProc or SQLIsExecuteBlock or
                     ( SQLUnion.Count > 0 );
    if ( Active ) and
       ( not SQLIsRefined ) and
       ( IsAggProcExec or ( not SQLIsValid )) then
    begin
      SysFetchAll( 0 );
      Result := BufferRowCount;
      Dec( Result, CachedInsertCount - CachedDeleteCount );
    end
    else
    if IsAggProcExec then
    // In Firebird 2.0 it will be possible to make use of EXECUTE BLOCK.
      raise EIB_DatasetError.CreateWithSender( Self, E_Cannot_GetRecCount )
    else
    begin
      Result := inherited SysGetCursorRecordCount;
      Dec( Result, CachedInsertCount - CachedDeleteCount );
    end;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_BDataset.DoKeyDataChange( AField: TIB_Column );
var
  ii: integer;
  tmpCol: TIB_Column;
  KeyIsNull: boolean;
begin
  if ( FPreventKeySeeking = 0 ) and Active then
  begin
    if Assigned( KeyDataset ) and KeyDataset.Prepared then
    begin
      if KeyDataset.State = dssSearch then
      // Do nothing.
      else
      if KeyDataset.Fields.RowState = rsNone then
        GotoBOF
      else
      begin
        CheckKeyLinksMaps;
        KeyIsNull := false;
        for ii := 0 to KeyFields.ColumnCount - 1 do
        begin
          tmpCol := FKeyLinksFieldsMap[ii];
          if Assigned( KeyFields[ii] ) then
          begin
            if ( tmpCol = pointer(-1) ) or tmpCol.IsNull then
            begin
              KeyIsNull := true;
              KeyFields[ii].Clear;
            end
            else
            begin
              KeyFields[ii].Assign( tmpCol );
            end;
          end;
        end;
        if KeyIsNull then
          GotoBOF
        else
        if KeySeeking then
        begin
          if not LookupKeyForFields then
            GotoBOF;
        end
        else
          LookupKeyForBufferFields;
      end;
    end
    else
    if KeySeeking then
      GotoBOF;
  end;
end;

{------------------------------------------------------------------------------}

function TIB_BDataset.SeekKeyForBufferFields(
                                           var ANodeRef: TIB_NodeRef ): boolean;
var
  tmpCursorGen: dword;
begin
  Result := NodeList.LookupBufferNode( KeyFields.OldBuffer );
  if ( not Result ) and
     ( not CursorIsKeyFields ) and
     ( not KeyLinksExist ) then
  begin
    tmpCursorGen := FCursorGen;
    BeginCallbackFetching;
    try
      while ( not Result ) and BufferActive and
            ( (not BufferHasBof) or (not BufferHasEof) ) and
            ( FCursorGen = tmpCursorGen ) do
      begin
        if ( not BufferHasBof ) then
          ValidateRows( BofRowNum - 500, BofRowNum );
        if ( not BufferHasEof ) then
          ValidateRows( EofRowNum, EofRowNum + 500 );
        Result := NodeList.LookupBufferNode( KeyFields.OldBuffer );
      end;
    finally
      EndCallbackFetching;
    end;
  end;
  if Result then
  begin
    ANodeRef := NodeList.BufRef;
    if ( ANodeRef.Pos > NodeList.BofRef.Pos ) then
    begin
      if NodeList.FilterDel then
        if rfDeleted in NodeList.BufRef.Node.RowFlags then
          ANodeRef.Pos := NodeList.BofRef.Pos;
      if NodeList.Filtered then
        if rfFiltered in NodeList.BufRef.Node.RowFlags then
          ANodeRef.Pos := NodeList.BofRef.Pos;
    end;
    if ANodeRef.Pos <> NodeList.BofRef.Pos then
    begin
      if KeyLinksExist or Assigned( NodeList.BufRef.Node.RecordData ) then
        FBufferCursor.QuickFetch( NodeList.BufRef.Node, false, false );
      Result := ANodeRef.Pos > NodeList.BofRef.Pos;
    end;
  end
  else
    ANodeRef := InvalidNodeRef;
end;

function TIB_BDataset.LookupKeyForBufferFields: boolean;
var
  ANodeRef: TIB_NodeRef;
begin
  KeyFields.CleanBuffers( false );
  FKeyLookupRowData := KeyFields.RowData;
  KeyFields.OldRowData := FKeyLookupRowData;
  if SeekKeyForBufferFields( ANodeRef ) then
    // Check if record was deleted or filtered out.
    Result := not (( ANodeRef.Pos = NodeList.BofRef.Pos ) and
                   ( NodeList.BufRef.Pos > NodeList.BofRef.Pos ))
  else
    Result := SysLookupKeyForBufferFields( ANodeRef );
end;

function TIB_BDataset.SysLookupKeyForBufferFields(
                                               ANodeRef: TIB_NodeRef ): boolean;
var
  tmpNode: PIB_Node;
  procedure DoScrollFailed;
  begin
    with NodeList, KeyFields do
    begin
      Result := false;
      with tmpNode^ do
      begin
        if RecordData <> nil then
          FreeMem( RecordData, RecordLen );
        FreeMem( KeyData, BufferLength );
      end;
      FreeMem( tmpNode, SizEof( tmpNode ));
    end;
  end;
  procedure GetSeekCursor;
  var
    ii: integer;
    LookupSQL: string;
    tmpPrms: integer;
    incAdj: integer;
    SPrmCnt: integer;
    MonitorText: string;
  begin
    tmpPrms := MakeLookupSQL( ServerSQL,
                              KeySQLSelect,
                              KeyLinks,
                              LookupSQL,
                              SPrmCnt );
    if @FSeekCursor^ = nil then
      IB_Connection.AllocateStmtHandle( @FSeekCursor );
    incAdj := 0;
    if {( not Refined ) and} Assigned( OrderingParam ) then
      incAdj := 1;

    if Assigned( PSeekDA ) then
    begin
      FreeMem( PSeekDA );
      PSeekDA := nil;
    end;
    PSeekDA := AllocMem( XSQLDA_LENGTH( KeyFields.PSQLDA.SQLn +
                                        Params.PSQLDA.SQLn - incAdj ));
    PSeekDA.version := SQLDA_VERSION1;
    PSeekDA.sqld := KeyFields.PSQLDA.SQLn + Params.PSQLDA.SQLn - incAdj - SPrmCnt;
    PSeekDA.sqln := KeyFields.PSQLDA.SQLn + Params.PSQLDA.SQLn - incAdj - SPrmCnt;
    with IB_Session do
    begin
      errcode := isc_dsql_prepare( @status,
                                   PtrHandle,
                                   @FSeekCursor,
                                   Length( LookupSQL ),
                                   pchar( LookupSQL ),
                                   IB_Connection.SQLDialect,
                                   KeyFields.PSQLDA );
      if ( errcode = 0 ) then
      begin
        FSeekCursorPrepared := true;
        if ClientMonitorHooksIn then
        begin
          MonitorText :=
              '//>>> STATEMENT PREPARED <<<//'#13#10 +
              'TIB_BDataset.SysLookupKeyForBufferFields.GetSeekCursor()'#13#10 +
              Self.ClassName + ': "';
          if Assigned( Self.Owner ) and ( Self.Owner.Name <> '' ) then
            MonitorText := MonitorText + Self.Owner.Name + '.';
          MonitorText := MonitorText + Self.Name +
              '" stHandle=' + IntToStr(Integer(@FSeekCursor^)) + ' #SC';
          OutputToMonitor( MonitorText );
        end;
      end
      else
        HandleException( Self );
    end;
    for ii := 0 to tmpPrms - 1 do
      PSeekDA.SQLVAR[ii] := Params.PSQLDA.SQLVAR[ii];
    for ii := 0 to KeyFields.PSQLDA.SQLn - 1 do
      PSeekDA.SQLVAR[ii + tmpPrms] := KeyFields.PSQLDA.SQLVAR[ii];
    for ii := 0 to Params.PSQLDA.SQLn - 1 - tmpPrms - incAdj - SPrmCnt do
      PSeekDA.SQLVAR[ii + tmpPrms + KeyFields.PSQLDA.SQLn] :=
        Params.PSQLDA.SQLVAR[ii + tmpPrms];
  end;
var
  ii: integer;  
begin
  if KeyLinksExist then
  begin
    KeyFields.OldRowData := FKeyLookupRowData;
    KeyFields.CleanBuffers( false );
    Result := true;
    for ii := 0 to KeyFields.ColumnCount - 1 do
    begin
      if KeyFields[ii].IsNull then
      begin
        Result := false;
        Exit;
      end;
    end;
  end
  else
  begin
    Result := false;
    Exit;
  end;
// SeekKeyForBufferFields should have already been called.
  CheckTransaction( false );
  if ANodeRef.Pos = InvalidNodePos then
  begin // Node was not found at all.
    tmpNode := NodeList.GetNewNode;
    Move( KeyFields.OldBuffer^,
          tmpNode.KeyData^,
          NodeList.KeyDataLength );
    try
      if FBufferCursor.QuickFetch( tmpNode, false, false ) then
      begin
        NodeList.StoreNode( tmpNode );
        NodeList.HackBufferPos( StoredNodePos, 0, 0, 0, tmpNode );
      end
      else
      begin
        DoScrollFailed;
        Exit;
      end;
    except
      DoScrollFailed;
      raise;
    end;
  end;
// Make sure that the node is part of the actual Dataset according to the SQL.
// This is because the buffer node is from the stored list.
  if Result and ( NodeList.BufRef.Pos < NodeList.BofRef.Pos ) and
                ( FBufferKeyIsValid = 0 ) then
  begin
    if ( not Assigned( FSeekCursor )) or ( not FSeekCursorPrepared ) then
      GetSeekCursor;//( ServerSQL, false, @FSeekCursor, PSeekDA );
    CheckTransaction( true );
    with IB_Session do
    begin
      errcode := isc_dsql_execute( @status,
                                   PtrHandle,
                                   @FSeekCursor,
                                   IB_Connection.SQLDialect,
                                   PSeekDA );
      if errcode <> 0 then HandleException( Self );
      errcode := isc_dsql_set_cursor_name( @status,
                                           @FSeekCursor,
                                           pchar(IntToStr(cardinal(FSeekCursor))
                                                                 + CursorName ),
                                           0 );
      if errcode <> 0 then HandleException( Self );
      errcode := isc_dsql_fetch( @status,
                                 @FSeekCursor,
                                 IB_Connection.SQLDialect,
                                 KeyFields.PSQLDA );
      Result := errcode = 0;
      if errcode = 100 then errcode := 0;
      try
        if errcode <> 0 then HandleException( Self );
      finally
        errcode := isc_dsql_free_statement( @status, @FSeekCursor, DSQL_CLOSE );
      end;
    end;
  end;
end;

function TIB_BDataset.LookupKeyForFields: boolean;
var
  tmpCurLevel: integer;
  FAGen: integer;
  function SysLookupKeyForFields: boolean;
  var
    tmpRow: longint;
    curCursorGen: dword;
    procedure SysLookupNodeList;
    begin
    // Make sure to avoid race condition if fetching was aborted.
      if ( FAGen = FetchingAbortedGen ) then
      begin
        tmpRow := CursorRowNum;
        Result := NodeList.LookupNode( KeyFields.OldBuffer,
                                       false,
                                       FKeyLookupRef );
      end;
    end;
  begin
    Result := false;
    if Assigned( NodeList ) and ( FAGen = FetchingAbortedGen ) then
      with KeyFields do
      begin
        RowData := FKeyLookupRowData;
        CleanBuffers( false );
        OldRowData := RowData;
        CheckTransaction( true );
        SysLookupNodeList;
        if ( not Assigned( FKeyLookupRef.Node )) and
           ( not Result ) and
           ( not Active ) then
        begin
        // Plug the results of the buffer lookup into the fields.
          if BufferBookmark <> FKeyLookupRowData then
            BufferBookmark := FKeyLookupRowData;
          if BufferBookmark = FKeyLookupRowData then
          begin
            CursorFields.RowData := BufferFields.RowData;
            if SysAfterFetchCursorRow then
            begin
              SysLookupNodeList;
              if Assigned( FKeyLookupRef.Node ) then
                FKeyLookupRef.Node.RowFlags := FKeyLookupRef.Node.RowFlags +
                                               [ rfUnsorted ]; 
            end;
          end;
        end;
        try
          if FScanningLevel = 0 then
            FMaxScanLevel := 0;
          Inc( FScanningLevel );
          Inc( FMaxScanLevel );
          curCursorGen := FCursorGen;
          while ( not Assigned( FKeyLookupRef.Node )) and
                ( not Result ) and
                ( not CursorEof ) and
                ( FAGen = FetchingAbortedGen ) and
                ( curCursorGen = FCursorGen ) do
          begin
            Inc( tmpRow );
            SysFetchNext;
            if tmpCurLevel = FKeyLookupMaxLevel then
            begin
              if tmpRow <> CursorRowNum then
              // Somehow (via callbacks) there were additional fetches
              // performed that did not pass through the code below to check
              // for a match. So, by checking the nodelist we see if the
              // match slipped by.
                SysLookupNodeList
              else
              if BuffersEqual( RowBuffer, OldBuffer, BufferLength ) then
              begin
                Result := true;
                FKeyLookupRef := NodeList.EofRef;
                NodeList.IterateNodeRef(FKeyLookupRef,-1,true,true,false,true);
              end
              else
              if CursorRowNum mod 5000 = 0 then
              // Fail safe incase anything weird happens and the match is
              // missed when scanning through fetched records.
                SysLookupNodeList;
            end
            else
              Result := true;
          end;
        finally
          Dec( FScanningLevel );
        end;
      end;
  end;

var
  ii: integer;
  tmpBool: boolean;
  tmpCol: TIB_Column;
  tmpCriteria: string;
begin
  FAGen := FetchingAbortedGen;
  if FKeyLookupCurLevel = 0 then
    FKeyLookupMaxLevel := 0;
  Inc( FKeyLookupMaxLevel );
  Inc( FKeyLookupCurLevel );
  tmpCurLevel := FKeyLookupCurLevel;
  FKeyLookupRef := InvalidNodeRef;
  FIncSearchRowNum := InvalidNodePos;
  CheckTransaction( false );
  try
    KeyFields.CleanBuffers( false );
    FKeyLookupRowData := KeyFields.RowData;
    KeyFields.OldRowData := FKeyLookupRowData;
    try
      DisableControls;
      if Active then
        CheckBrowseMode;
      if ( CallbackInc >= 0 ) and ( CallbackFreezeLevel = 0 ) then
        IB_Session.DoAppCallback;
      BeginCallbackFetching;
      try
        DoCallback;
        if tmpCurLevel = FKeyLookupMaxLevel then
        begin
          KeyFields.OldRowData := FKeyLookupRowData;
          if SeekKeyForBufferFields( FKeyLookupRef ) then
          begin
            if ( FKeyLookupRef.Pos = NodeList.BofRef.Pos ) and
               ( NodeList.BufRef.Pos > NodeList.BofRef.Pos ) then
              FKeyLookupRef.Pos := InvalidNodePos // Rec was deleted.
            else
            if ( FKeyLookupRef.Pos > NodeList.BofRef.Pos ) then
            // It may be a cached edit in the buffer that didn't match the
            // inmemory version of the record.
              if ( rfEdited in NodeList.BufRef.Node.RowFlags ) and
                 ( FLocateLevel > 0 ) then
                FKeyLookupRef.Pos := NodeList.BofRef.Pos; // Rec was edited.
          end
          else
          if SysLookupKeyForBufferFields( FKeyLookupRef ) then
          begin
          // If it gets here then the server tells us the record should be in
          // the buffer so assume it can and will be found.
          // There are some cases where it may actually not be findable.
          // One example is with trailing spaces on varchar columns.
            tmpCol := FindBufferField( OrderingLink );
            if Assigned( tmpCol ) then
              tmpCriteria := tmpCol.AsString
            else
              tmpCriteria := '';
            DoCallback;
            if tmpCurLevel = FKeyLookupMaxLevel then
            begin
              KeyFields.OldRowData := FKeyLookupRowData;
              if Assigned( OrderingParam ) then
                OrderingRefineStr := tmpCriteria;
              if ( not SysLookupKeyForFields ) and
                 ( not Assigned( OrderingParam )) then
              begin
              // This is used to catch the case where the server says there is
              // a matching record but in truth they don't match at the binary
              // comparison of the ColData so they should not be considered a
              // match.
                tmpBool := true;
                for ii := 0 to KeyFields.ColumnCount - 1 do
                begin
                  tmpCol := FindBufferField( KeyFields[ii].FullFieldName );
                  if not Assigned( tmpCol ) then
                    tmpCol := FindBufferField( KeyFields[ii].FullSQLName );
                  if Assigned( tmpCol ) then
                  begin
                    if KeyFields[ii].OldColData <> tmpCol.ColData then
                    begin
                      tmpBool := false;
                      Break;
                    end;
                  end;
                end;
                if tmpBool and ( tmpCurLevel = FKeyLookupMaxLevel ) then
                begin
                  BeginKeyDataFreeze;
                  try
                    if Refreshing then
                    begin
                      SysClose;
                      SysOpen;
                    end
                    else
                      SysRefresh( false, true );
                  finally
                    KeyFields.OldRowData := FKeyLookupRowData;
                    EndKeyDataFreeze;
                  end;
                  if tmpCurLevel = FKeyLookupMaxLevel then
                    if not SysLookupKeyForFields then
                      FKeyLookupRef.Pos := InvalidNodePos;
                end;
              end;
            end;
          end;
        end;
      finally
        EndCallbackFetching;
      end;
    finally
      EnableControls;
    end;
  finally
    Dec( FKeyLookupCurLevel );
  end;
  Result := ( Assigned( FKeyLookupRef.Node )) and
            ( FKeyLookupRef.Pos > StoredNodePos );
  if Result and ( FKeyLookupCurLevel = 0 ) then
  begin
    if NodeList.FilterDel and
       ( rfDeleted in FKeyLookupRef.Node.RowFlags ) then Result := false;
    if NodeList.Filtered and
       ( rfFiltered in FKeyLookupRef.Node.RowFlags ) then Result := false;
    if Result then
      RowNum := NodeList.GetRowNum( FKeyLookupRef );
  end;
end;

{------------------------------------------------------------------------------}

function TIB_BDataset.IncSearchKey( AKey: char;
                                    KeyByKey: boolean;
                                    AllowTimeout: boolean;
                                    SeekNearest: boolean ): boolean;
var
  StartRowNum: longint;
begin
  StartRowNum := BofRowNum + 1;
  Result := false;
  if AKey in [ #8, #9, #13, #27, #32..#255 ] then
  begin
    if AllowTimeout and
       ( not Fetching ) and
       ( not ( AKey in [ #8, #9, #13 ] )) and
       ( GetTickCount > IncSearchLastTick + IncSearchKeyInt ) then
    begin
      FIncSearchKeyString := '';
      FIncSearchNearestRowNum := InvalidNodePos;
    end
    else
    if ( FIncSearchKeyString = '' ) and ( AKey >= #32 ) then
    begin
      FIncSearchNearestRowNum := InvalidNodePos;
      SysBeforeScroll;
    end;
    if AKey = #8 then
    begin
      if Length( FIncSearchKeyString ) >= 1 then
        SetLength( FIncSearchKeyString, Length( FIncSearchKeyString ) - 1 );
    end
    else
    if AKey in [ #9, #13 ] then
    begin
      Result := IncSearchString( IncSearchKeyString,
                                 StartRowNum,
                                 SeekNearest );
      if ( AKey = #13 ) {and Result} and ( not AllowTimeout ) then    //#AK#
      begin
        FIncSearchKeyString := '';
        FIncSearchNearestRowNum := InvalidNodePos;
      end;
    end
    else
    if AKey = #27 then
    begin
      FIncSearchKeyString := '';
      FIncSearchNearestRowNum := InvalidNodePos;
      AbortFetching;
    end
    else
    begin
      if ( Length( FIncSearchKeyString ) > 0 ) and
         ( FIncSearchNearestRowNum > BofRowNum ) then
        StartRowNum := FIncSearchNearestRowNum;
      FIncSearchKeyString := FIncSearchKeyString + AKey;
    end;
    if AKey in [ #8, #27, #32..#255 ] then
    begin
      if Assigned( OrderingField ) and
         ( OrderingField.IsText or
           ( OrderingField.IsNumeric and
             ( OrderingRefinePos > 0 ))) and
         ( Length( FIncSearchKeyString ) >= OrderingRefinePos ) then
      begin
        if KeyByKey and ( Length( FIncSearchKeyString ) > 0 ) then
          Result := IncSearchString( FIncSearchKeyString,
                                     StartRowNum,
                                     SeekNearest )
        else
          Result := true;
      end;
    end;
    FIncSearchLastTick := GetTickCount;
  end;
end;

procedure TIB_BDataset.ClearIncSearchString;
begin
  IncSearchKey( #27, false, false, false );
  FIncSearchString := '';
end;

function TIB_BDataset.IncSearchString( AString: string;
                                       StartRowNum: longint;
                                       SeekNearest: boolean ): boolean;
var
  tmpIncSearchLevel: integer;
  ExactMatchFound: boolean;
begin
  Result := false;
  if AString = '' then
    Exit;     //#AK#
  ExactMatchFound := false;
  if not Assigned( OrderingField ) then
    raise EIB_DatasetError.CreateWithSender( Self,
                                             E_UnableToSearch_NoOrdering );
  if FIncSearchLevel = 0 then
  begin
    if Refreshing or Fetching then
      raise EIB_DatasetError.CreateWithSender( Self,
                                               E_UnableToSearch_DatasetBusy );
    FIncSearchMaxLevel := 0;
  end;
// Ignore any previously found record because new criteria has been provided.
  FIncSearchRowNum := InvalidNodePos;
// Place the CurRow pointer to the row prior to the first one to be checked.
  if StartRowNum < BofRowNum + 1 then
    StartRowNum := BofRowNum + 1;
  FIncSearchCurRow := StartRowNum - 1;
  tmpIncSearchLevel := FIncSearchLevel + 1;
// If a bookmark lookup is in process this will effectively terminate it.

  FKeyLookupRef := InvalidNodeRef;

  CheckTransaction( false );
  try
    Inc( FIncSearchLevel );
    DisableControls;
    try
      BeginCallbackFetching;
      Inc( FIncSearchMaxLevel );
      FIncSearchLastTick := GetTickCount;
      FIncSearchString := AString;
      // Need to be able to tell if a new context needs to be established.
      if OrderingRefineStr <> mkOrderingRefineStr( AString, false ) then
      begin
        OrderingRefineStr := AString;
        DoCallback;
        FIncSearchCurRow := 0;
      end;
      if tmpIncSearchLevel = FIncSearchMaxLevel then
      begin
        if AString = '' then
          FIncSearchRowNum := 1
        else
          ExactMatchFound := SysIncSearch( AString,
                                           OrderingLink,
                                           SeekNearest,
                                           true );
      end;
    finally
      EndCallbackFetching;
    end;
  finally
    try
      EnableControls;
    finally
      Dec( FIncSearchLevel );
    end;
  end;
  Result := ( FIncSearchRowNum > BofRowNum ) and ExactMatchFound;
  if FIncSearchLevel = 0 then
  begin
    if FIncSearchRowNum > BofRowNum then
    begin
      if RowNum = FIncSearchRowNum then
        ProcessLinkEvent( setShowNearest, RowNum )
      else
      begin
        RowNum := FIncSearchRowNum;
        SysStateChanged; // RowState can get mixed up a bit.
      end;
    end
    else
    if FIncSearchNearestRowNum > BofRowNum then
      ProcessLinkEvent( setShowNearest, FIncSearchNearestRowNum );
  end;
end;

{
function TIB_BDataset.SysIncSearch( AString: string;
                                    ACol: string;
                                    SeekNearest: boolean;
                                    PartialMatch: boolean ): boolean;
begin
  Result := LocateRecord( OrderingLink,
                          AString,
                          [ lopPartialKey ], true, SeekNearest );
  if Result then begin
    FIncSearchRowNum := RowNum;
  end;
end;
}

function TIB_BDataset.SysIncSearch( AString: string;
                                    ACol: string;
                                    SeekNearest: boolean;
                                    PartialMatch: boolean ): boolean;
var
  tmpCol: TIB_Column;
  tmpStr: string;
  prtStr: string;
  tmpDt1: TDateTime;
  tmpDt2: TDateTime;
  tmpEx1: extended;
  tmpEx2: extended;
  tmpCmp: smallint;
  prtCmp: smallint;
  tmpOrdered: boolean;
  SkipFactorInc: integer;
  SkipFactorCnt: integer;
  LastSkipInc: integer;
  IsNOCASE: boolean;
  InitialFetched: integer;
  FAGen: integer;
begin
  Result := false;
  FAGen := FetchingAbortedGen;
// FIncSearchCurRow is the row prior to the row about to be checked for a match.
  FIncSearchNearestRowNum := FIncSearchCurRow;
  tmpCol := BufferFieldByName( ACol );
  case tmpCol.CharCase of
    ccUpper: AString := AnsiUpperCase( AString );
    ccLower: AString := AnsiLowerCase( AString );
  end;
  tmpOrdered := FieldByName( ACol ) = OrderingField;
  IsNOCASE := tmpCol.NoCaseFieldName <> '';
  if tmpOrdered and
     ( not Assigned( OrderingParam )) and
     (( CursorIsKeyFields ) or
      ( BufferRowCount > 1000 ) or
      ( not BufferHasEof )) then
    SkipFactorInc := 10
  else
    SkipFactorInc := 1;
  LastSkipInc := SkipFactorInc;
  SkipFactorCnt := 1;
  InitialFetched := BufferRowCount;
  if InitialFetched > 10 then
    InitialFetched := 10;

  while ( FIncSearchRowNum = InvalidNodePos ) and
        ( not Result ) and
        ( not (( FIncSearchCurRow >= EofRowNum - 1 ) and
               ( BufferHasEof ) and
               ( SkipFactorInc = 1 ))) and
        ( FAGen = FetchingAbortedGen ) do
  begin
    FIncSearchLastTick := GetTickCount;
    if ( IncSearchSeekInt = 0 ) or
       ( CallbackInitTick = 0 ) or
       ( FIncSearchLastTick < CallbackInitTick + IncSearchSeekInt ) then
    begin
      if ( LastSkipInc < 1 ) and ( SkipFactorInc >= 10 ) then
      begin
      // Was moved to proper buffer row already by backing up.
        LastSkipInc := 1;
        SkipFactorInc := SkipFactorInc div 10;
      end
      else
      begin
      // Allow the increment of records skipped to increase.
        if ( SkipFactorCnt >= InitialFetched{was: 10} ) and
           ( SkipFactorInc >= 1 ) and
          ((( SkipFactorInc < 100 ) and ( not Assigned( OrderingParam ))) or
           (( SkipFactorInc < 10000 ) and CursorIsKeyFields )) then
        begin
          SkipFactorInc := SkipFactorInc * 10;
          SkipFactorCnt := 1;
        end
        {next line CHANGED: Willibald Krenn - 13.08.2000}
        else
        if SkipFactorCnt < InitialFetched {was: 10} then
          Inc( SkipFactorCnt );
      // Move to next buffer row to be checked.
        BufferRowNum := FIncSearchCurRow + SkipFactorInc;
      // Since callbacks allow interruption there may be a need to terminate
      // the loop at this point.
        if FIncSearchRowNum > BofRowNum then
          Break
        else
        begin
          LastSkipInc := BufferRowNum - FIncSearchCurRow;
          FIncSearchCurRow := BufferRowNum;
        end;
      end;
      FIncSearchLastTick := GetTickCount;
    end
    else
    begin
      MessageBeep( 0 );
      if SeekNearest then
        FIncSearchRowNum := FIncSearchCurRow;
      AbortFetching;
      Break;
    end;

    // Perform the comparison of the text.
    prtStr := Copy( tmpCol.AsString, 1, Length(AString));
    tmpStr := tmpCol.AsString;

    prtStr := PadRight( prtStr, #0, Length( AString ), false );
    tmpStr := PadRight( tmpStr, #0, Length( AString ), false );
    
    if tmpCol.IsText then
    begin
      if IsNoCase then
      begin
        tmpCmp := DoCompareText( tmpCol, tmpStr, AString );
        prtCmp := DoCompareText( tmpCol, prtStr, AString );
      end
      else
      begin
        tmpCmp := DoCompareStr( tmpCol, tmpStr, AString );
        prtCmp := DoCompareStr( tmpCol, prtStr, AString );
      end
    end
    else
    if tmpCol.IsNumeric then
    begin
      tmpEx1 := Trunc( tmpCol.AsExtended );
      tmpEx2 := Trunc( StrToFloat( AString ));
      if tmpEx1 = tmpEx2 then begin tmpCmp := 0;
      end else if tmpEx1 < tmpEx2 then begin tmpCmp := -1;
      end else begin tmpCmp := 1; end;
      prtCmp := tmpCmp;
    end
    else
    if tmpCol.IsDateTime then
    begin
      tmpDT1 := tmpCol.AsDate;
      tmpDT2 := Trunc( StrToDateTime( AString ));
      if tmpDT1 = tmpDT2 then tmpCmp := 0 else
      if tmpDT1 < tmpDT2 then tmpCmp := -1 else
                              tmpCmp := 1;
      prtCmp := tmpCmp;
    end
    else
    begin
      tmpCmp := DoCompareText( tmpCol, tmpStr, AString );
      prtCmp := DoCompareText( tmpCol, prtStr, AString );
    end;

    if tmpCol.IsText and PartialMatch and ( Abs(SkipFactorInc) = 1 ) then
      tmpCmp := prtCmp;
                         
    // See where we are at.
    
    if ( BufferEof ) or
       ( tmpCmp = 0 ) or
       (( prtCmp = 0 ) and ( OrderingItemNo < 0 )) or
       ( tmpOrdered and
        ((( tmpCmp > 0 ) and ( OrderingItemNo > 0 )) or
         (( tmpCmp < 0 ) and ( OrderingItemNo < 0 )) or
         (( tmpCol.IsNull ) {and ( OrderingItemNo > 0 )}))) then
    begin
      if ( Abs( SkipFactorInc ) > 1 ) and
         ( Abs( LastSkipInc ) > 1 ) then
      begin
      // Backup since we scrolled past potentially valid records.
        BufferRowNum := FIncSearchCurRow - LastSkipInc + 1;
        FIncSearchCurRow := BufferRowNum;
      // Set flag to indicate a backwards movement was performed.
        LastSkipInc := 0;
      end
      else
      if tmpCmp = 0 then
      begin
      // Found it!!
        Result := true;
        FIncSearchRowNum := FIncSearchCurRow;
      end
      else
      begin
        if SeekNearest then
          FIncSearchRowNum := FIncSearchCurRow;
        Break;
      end;
    end
    else
    if FIncSearchNearestRowNum <= FIncSearchCurRow then
      FIncSearchNearestRowNum := FIncSearchCurRow + 1;
    if ( not Result ) and
       ( CallbackInc >= 0 ) and
       ( CallbackFreezeLevel = 0 ) then
      IB_Session.DoAppCallback;
  end;
  FIncSearchLastTick := GetTickCount;
end;

function TIB_BDataset.GetFound: boolean;
begin
  Result := FFound;
end;

procedure TIB_BDataset.SetFound( AValue: boolean );
begin
  FFound := AValue;
end;

function TIB_BDataset.FindRecord( Restart, GoForward: boolean ): boolean;
  procedure ProcessRequest( ADataset: TIB_Dataset );
  begin
    with ADataset do
    begin
      if GoForward then
      begin
        if Restart then
          First
        else
        if ( not Eof ) then
          Next;
        Result := not Eof;
      end
      else
      begin
        if Restart then
          Last
        else
        if not Bof then
          Prior;
        Result := not Bof;
      end;
    end;
  end;
begin
  CheckBrowseMode;
  DoBeforeScroll;
  SetFound( false );
  if Filtered or ( Filter = '' ) then
  begin
    ProcessRequest( Self );
    if Result and ( not Filtered ) and Assigned( OnFilterRecord ) then
    begin
      Result := Assigned( Fields.RowNode );
      while Result and ( rfFiltered in Fields.RowNode.RowFlags ) do
      begin
        if GoForward then
        begin
          Next;
          if Eof then
            Result := false;
        end
        else
        begin
          Prior;
          if Bof then
            Result := false;
        end;
      end;
    end;
  end
  else
  begin
    if not Assigned( FFilterCursor ) then
    begin
      FFilterCursor := TIB_FilterCursor.Create( Self );
      FFilterCursor.OnError := Self.DoHandleError;
    end;
    with FFilterCursor do
    begin
      IB_Connection := Self.IB_Connection;
      IB_Transaction := Self.IB_Transaction;
      AutoFetchAll := Self.AutoFetchAll;
      if ( not Prepared ) or ( not SQLIsValid ) then
      begin
        AssignSQLWithSearch( Self );
        SQLSelect.Text := Self.KeySQLSelect;
        OrderingItemNo := Self.OrderingItemNo;
        if OrderingItemNo = 0 then
          SQLOrder.Text := Self.SQLOrder.Text;
        if Self.FilterPlan <> '' then
          SQLPlan.Text := Self.FilterPlan;
        Filter := '';
        Filtered := false;
        FetchWholeRows := true;
        KeyLinksAutoDefine := false;
        ReadOnly := true;
        CalculatedFields.Clear;
        OnCalculateField := nil;
        Prepare;
      end;
    end;
    if not FFilterCursor.Active then 
      FFilterCursor.Open;
    if Restart then
      ProcessRequest( FFilterCursor )
    else
    begin
      if FFilterCursor.Eof then
        FFilterCursor.Last;
      if FFilterCursor.Bof then
        FFilterCursor.First;
      if FFilterCursor.Bof or FFilterCursor.Eof then
        Result := false
      else
      begin
        CheckTransaction( true );
        FFilterCursor.CheckTransaction( true );
        BufferBookmark := FFilterCursor.Fields.RowData;
        if GoForward then
        begin
          while ( BufferRowNum < RowNum ) and ( not FFilterCursor.Eof ) do
          begin
            FFilterCursor.Next;
            BufferBookmark := FFilterCursor.Fields.RowData;
          end;
          while ( BufferRowNum > RowNum ) and ( not FFilterCursor.Bof ) do
          begin
            FFilterCursor.Prior;
            BufferBookmark := FFilterCursor.Fields.RowData;
          end;
        end
        else
        begin
          while ( BufferRowNum > RowNum ) and ( not FFilterCursor.Bof ) do
          begin
            FFilterCursor.Prior;
            BufferBookmark := FFilterCursor.Fields.RowData;
          end;
          while ( BufferRowNum < RowNum ) and ( not FFilterCursor.Eof ) do
          begin
            FFilterCursor.Next;
            BufferBookmark := FFilterCursor.Fields.RowData;
          end;
        end;
        ProcessRequest( FFilterCursor );
        if Result and Assigned( OnFilterRecord ) then
        begin
          BufferBookmark := FFilterCursor.Fields.RowData;
          Result := Assigned( BufferFields.RowNode );
          while Result and ( rfFiltered in BufferFields.RowNode.RowFlags ) do
          begin
            if GoForward then
            begin
              FFilterCursor.Next;
              if FFilterCursor.Eof then
                Result := false;
            end
            else
            begin
              FFilterCursor.Prior;
              if FFilterCursor.Bof then
                Result := false;
            end;
            if Result then
              BufferBookmark := FFilterCursor.Fields.RowData;
          end;
        end;
      end;
    end;
    if Result then
    begin
      KeyFields.RowData := FFilterCursor.Fields.RowData;
      Result := LookupKeyForFields;
    end;
  end;
  if Result then
    SetFound( true );
end;

function TIB_BDataset.FindFirst: Boolean;
begin Result := FindRecord(True, True); end;
function TIB_BDataset.FindLast: Boolean;
begin Result := FindRecord(True, False); end;
function TIB_BDataset.FindNext: Boolean;
begin Result := FindRecord(False, True); end;
function TIB_BDataset.FindPrior: Boolean;
begin Result := FindRecord(False, False); end;

function TIB_BDataset.GetAsXml: string;
var
  C: Integer;
  XML: TStringList;
begin
  RESULT := '';
  XML := TStringList.Create;
  try
    BufferFirst;
    while not BufferEof do
    begin
      XML.Add( '<record>' );
      For C := 0 to BufferFields.ColumnCount -1 do
      begin
        With BufferFields.Columns[ C ] do
        begin
          XML.Add( '<'+lowercase( FieldName )+'>' );
          XML.Add( AsString );
          XML.Add( '</'+lowercase( FieldName )+'>' );
        end;
      end;
      XML.Add( '</record>' );
      BufferNext;
    end;
    RESULT := XML.Text;
  finally
    XML.Free;
  end;
end;

procedure PutRecord( ANode: PIB_Node; ARow: TIB_Row );

// I am taking advantage of my buffering approach of storing each record in
// a seperate allocation of memory to allow only the data that needs to be
// stored for each reocrd to occupy memory.

// If a column is nullable and null I do not store any of the sqldata info.
// If a column is SQL_VARYING then I only store the specified data.

  function GetRecordLen: integer;
  var
    ii: integer;
  begin
    Result := 0; //SizeOf( word );
    with ARow.PSQLDA^ do
      for ii := 0 to SQLn - 1 do
        with SQLVAR[ ii ] do
        begin
          Inc( Result, SizeOf( smallint ));
          if ( sqlind^ <> IB_Null ) or ( not Odd( SQLType )) then
          begin
            if ( SQLType = SQL_VARYING  ) or
               ( SQLType = SQL_VARYING_ ) then
              Inc( Result, byte(sqldata^) +
                           byte(ptr(longint(sqldata) + 1)^) * 256 + 2 )
            else
              Inc( Result, sqllen );
          end;
        end;
  end;

var
  ii: integer;
  NewLen: integer;
  tmpDst: pointer;
begin
  NewLen := GetRecordLen;
  if ANode.RecordLen <> NewLen then
  begin
    FreeMem( ANode.RecordData );
    GetMem( ANode.RecordData, NewLen );
  end;
  ANode.RecordLen := NewLen;
  tmpDst := ANode.RecordData;
  for ii := 0 to ARow.PSQLDA^.sqln - 1 do
    with ARow.PSQLDA^.SQLVAR[ ii ] do
    begin
      Move( sqlind^, tmpDst^, SizeOf( smallint ));
      Inc( longint(tmpDst), SizeOf( smallint ));
      if ( sqlind^ <> IB_Null ) or ( not Odd( SQLType )) then
      begin
        if ( SQLType = SQL_VARYING  ) or
           ( SQLType = SQL_VARYING_ ) then
        begin  // Store only valid sqldata.
          NewLen := pbyte(pchar(sqldata) + 1)^ * 256 + pbyte(pchar(sqldata))^;
          Move( sqldata^, tmpDst^, NewLen + 2 );
          Inc( longint(tmpDst), NewLen + 2 );
        end
        else
        begin
          Move( sqldata^, tmpDst^, SQLLen );
          Inc( longint(tmpDst), SQLLen );
        end;
      end;
    end;
end;

function GetRecord( ANode: PIB_Node; ARow: TIB_Row ): word;
var
  ii: integer;
  tmpSrc: pointer;
  NewLen: integer;
begin
  Result := 0;
  tmpSrc := ANode.RecordData;
  if Assigned( ARow.PSQLDA ) then
    for ii := 0 to ARow.PSQLDA.SQLn - 1 do
      with ARow.PSQLDA.SQLVAR[ ii ] do
      begin
        if Assigned( tmpSrc ) then
        begin
          Move( tmpSrc^, sqlind^, SizeOf( smallint ));
          Inc( longint(tmpSrc), SizeOf( smallint ));
          if ( sqlind^ <> IB_Null ) or ( not Odd( SQLType )) then
          begin // sqldata info was stored.
            if ( SQLType = SQL_VARYING  ) or
               ( SQLType = SQL_VARYING_ ) then
            begin
              NewLen := pbyte(longint(tmpSrc) + 1)^ * 256 + pbyte(tmpSrc)^;
              if NewLen > SQLLen then
                NewLen := SQLLen;
              Move( tmpSrc^, sqldata^, NewLen + 2 );
              Inc( longint(tmpSrc), NewLen + 2 );
            end
            else
            begin
              Move( tmpSrc^, sqldata^, SQLLen );
              Inc( longint(tmpSrc), SQLLen );
            end;
          end;
        end
        else
        begin
          sqlind^ := IB_Null;
          if ( SQLType = SQL_VARYING  ) or
             ( SQLType = SQL_VARYING_ ) then
            with SQL_VARCHAR( sqldata^ ) do
            begin
              vary_len_low := 0;
              vary_len_high := 0;
              FillChar( vary_string, sqllen, #32 ); {!! Performance hack.}
            end
          else
          if ( SQLType = SQL_TEXT  ) or
             ( SQLType = SQL_TEXT_ ) then
            FillChar( sqldata^, sqllen, #32 ) {!! Performance hack.}
          else
            FillChar( sqldata^, sqllen, #0 ); {!! Performance hack.}
        end;
      end;
end;

procedure TIB_BDataset.TraceBufferNodes(       Ascending,
                                               AllowDeleted,
                                               AllowFiltered: boolean;
                                         const AStrings: TStrings );
begin
  NodeList.TraceBufferNodes( Ascending, AllowDeleted, AllowFiltered, AStrings );
end;

procedure TIB_BDataset.SetIsTree(const Value: boolean);
begin
  FIsTree := false; //Value;
  if FIsTree then
    AutoFetchAll := true;
end;

(* Tree stuff held back
procedure TIB_BDataset.RebuildNodeList;
var
  List, Roots, Trashes: TList;  //trashes are nodes always added as a last
  // roots, therefore there position in the grid is not dependant on sorting
  i: integer;
  bfldID, bfldParID: TIB_Column;
  PN, Found: PIB_Node;

  function AddChilds(P: PIB_Node): PIB_Node; //returns last child
  var
    j: integer;
  begin
    Result := P;
    j := 0;
    while j < List.Count do
    begin
      if (PIB_Node(List.Items[j]).ParID <> FTreeRoot) and (PIB_Node(List.Items[j]).ParID = P.ID) then
      begin
        Found := PIB_Node(List.Items[j]);
        Found.Level := P.Level + 1;
        if FFirstTreeRebuild and (Found.Level <= FTreeExpandLevel) then
          Found.RowFlags := Found.RowFlags + [rfExpanded];

        Result.Next := Found;  //connect the found node to the last found node
        Found.Prev := Result;
        Result := AddChilds(Found);
      end;
      Inc(j);
    end;
  end;

begin
  if not IsTree then
    Exit;

  bfldID := BufferFieldByName(TreeID);
  bfldParID := BufferFieldByName(TreeParID);

  BufferFirst;
  List := TList.Create;
  Roots := TList.Create;
  Trashes := TList.Create;
  try
    while not BufferEof do
    begin
      NodeList.BufRef.Node.ID := bfldID.AsInteger;
      NodeList.BufRef.Node.ParID := bfldParID.AsInteger;
      if NodeList.BufRef.Node.ID = FTreeTrash then
        Trashes.Add(NodeList.BufRef.Node)
      else
      if NodeList.BufRef.Node.ParID = FTreeRoot then   //remember the tree roots
        Roots.Add(NodeList.BufRef.Node);
      NodeList.BufRef.Node.Level := 0;
      List.Add(NodeList.BufRef.Node);
      BufferNext;
    end;

    if List.Count > 0 then
    begin
      // just to move the CurRef out of any record
      try
        First;
        Prior;
      except
      end;

      PN := NodeList.BofRef.Node;
      for i := 0 to Trashes.Count-1 do
        Roots.Add(Trashes[i]);

      for i := 0 to Roots.Count-1 do
      begin
        PN.Next := Roots[i];
        PIB_Node(Roots[i]).Prev := PN;
        if FFirstTreeRebuild and (FTreeExpandLevel >= 0) then
          PIB_Node(Roots[i]).RowFlags := PIB_Node(Roots[i]).RowFlags + [rfExpanded];
        PN := AddChilds(PIB_Node(Roots[i]));
      end;
      PN.Next := NodeList.EofRef.Node;
      NodeList.EofRef.Node.Prev := PN;

      if not Filtered then
        Filtered := true;
      RefreshFilteredRows;

      if FFirstTreeRebuild and (List.Count > 0) then
      begin
        Selected[1] := not Selected[1];
        Selected[1] := not Selected[1];
      end;

      FFirstTreeRebuild := false;
    end;
  finally
    List.Free;
    Roots.Free;
    Trashes.Free;
  end;
end;

//insert a new child, it would be handy to add a new button to the TIB_UpdateBar
//that would call this method
procedure TIB_BDataset.InsertChild;
begin
  if TreeID <> '' then
  begin
    FNewAsChild := true;
    try
      FNewParID := FieldByName(TreeID).AsInteger;
      FNewLevel := NodeList.CurRef.Node.Level + 1;
      NodeList.CurRef.Node.RowFlags := NodeList.CurRef.Node.RowFlags + [rfExpanded];
      if Assigned(FOnCanInsertChild) then
        FOnCanInsertChild(Self, FNewAsChild);
      if FNewAsChild then
        Next;
      Insert;
    finally
      FNewAsChild := false;
    end;
  end
  else
    Insert;
end;

procedure TIB_BDataset.ExpandAll(AExpandLevel: integer {$ifdef IBO_VCL60_OR_GREATER}
= 99999 {$ENDIF} );
var
  tmp: integer;
begin
  tmp := FTreeExpandLevel;
  try
    FTreeExpandLevel := AExpandLevel;
    FFirstTreeRebuild := true;
    Filtered := false;
    RefreshFilteredRows;
    RebuildNodeList;
  finally
    FTreeExpandLevel := tmp;
  end;
end;

function TIB_BDataset.GetBufferLevel: integer;
begin
  Result := NodeList.BufRef.Node.Level;
end;

function TIB_BDataset.GetLevel: integer;
begin
  Result := NodeList.CurRef.Node.Level;
end;

function TIB_BDataset.IsParentOf(Id: integer; ANode: PIB_Node): boolean;
var
  P: PIB_Node;
begin
//  Result := false;
  P := ANode;
  if P.ParID = Id then //direct parent
  begin
    Result := true;
    Exit;
  end
  else
  begin
    while Assigned(P) and (P <> NodeList.BofRef.Node) and ((P.Level >= ANode.Level) or (P.ParID <> Id)) do
      P := P.Prev;
    Result := P.ParID = Id;
  end;
end;
*)


// IBA_FilterCursor.INT

constructor TIB_FilterCursor.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  FBDataset := AOwner as TIB_BDataset;
end;

procedure TIB_FilterCursor.SysPrepareSQL;
begin
  inherited SysPrepareSQL;
  ExtractFilterClause( BDataset, SQLWhereItems, ParamValueLinks );
end;

procedure TIB_FilterCursor.SysBeforeOpen;
var
  tmpInt: integer;
begin
  inherited SysBeforeOpen;
  tmpInt := BDataset.Params.BufferLength;
  if ( tmpInt = Params.BufferLength ) then
  begin
    Move( BDataset.Params.FRowBuffer^, Params.FRowBuffer^, tmpInt );
    Move( BDataset.Params.FOldBuffer^, Params.FOldBuffer^, tmpInt );
  end
  else
    raise EIB_StatementError.CreateWithSender( Self,
                                               E_Params_buffer_size_mismatch );
end;

// IBA_UpdateSQL.INT

constructor TIB_UpdateSQL.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  FDeleteSQL := TIB_StringList.Create;
  FModifySQL := TIB_StringList.Create;
  FLockSQL := TIB_StringList.Create;
  FInsertSQL := TIB_StringList.Create;
  FDeleteSQL.OnChange := SQLChanged;
  FModifySQL.OnChange := SQLChanged;
  FLockSQL.OnChange := SQLChanged;
  FInsertSQL.OnChange := SQLChanged;
  FPreparedInserts := true;
  FNeedRecordResync := false;
end;

destructor TIB_UpdateSQL.Destroy;
begin
  FDeleteSQL.Free;
  FModifySQL.Free;
  FLockSQL.Free;
  FInsertSQL.Free;
  FDeleteSQL := nil;
  FModifySQL := nil;
  FLockSQL := nil;
  FInsertSQL := nil;
  inherited Destroy;
end;

procedure TIB_UpdateSQL.CancelQuery;
begin
  if Assigned( FEditDSQL   ) then FEditDSQL.CancelQuery;
  if Assigned( FLockDSQL   ) then FLockDSQL.CancelQuery;
  if Assigned( FInsertDSQL ) then FInsertDSQL.CancelQuery;
  if Assigned( FDeleteDSQL ) then FDeleteDSQL.CancelQuery;
end;

procedure TIB_UpdateSQL.SetPreparedEdits( AValue: boolean );
begin
  if PreparedEdits <> AValue then 
    FPreparedEdits := AValue;
end;

procedure TIB_UpdateSQL.SetPreparedInserts( AValue: boolean );
begin
  if PreparedInserts <> AValue then
    FPreparedInserts := AValue;
end;

procedure TIB_UpdateSQL.SetSearchedDeletes( AValue: boolean );
begin
  if SearchedDeletes <> AValue then
  begin
    FSearchedDeletes := AValue;
    if Assigned( FDeleteDSQL ) then
      FDeleteDSQL.SysUnprepare;
  end;
end;

procedure TIB_UpdateSQL.SetSearchedEdits( AValue: boolean );
begin
  if SearchedEdits <> AValue then
  begin
    FSearchedEdits := AValue;
    if Assigned( FEditDSQL ) then
      FEditDSQL.SysUnprepare;
    if Assigned( FLockDSQL ) then
      FLockDSQL.SysUnprepare;
  end;
end;

procedure TIB_UpdateSQL.SysUnprepare;
begin
  if Assigned( FEditDSQL ) then
    FEditDSQL.SysUnprepare;
  if Assigned( FLockDSQL ) then
    FLockDSQL.SysUnprepare;
  if Assigned( FInsertDSQL ) then
    FInsertDSQL.SysUnprepare;
  if Assigned( FDeleteDSQL ) then
    FDeleteDSQL.SysUnprepare;
end;

procedure TIB_UpdateSQL.SysDeallocate;
begin
  if Assigned( FEditDSQL ) then
    FEditDSQL.SysDeallocate( true );
  if Assigned( FLockDSQL ) then
    FLockDSQL.SysDeallocate( true );
  if Assigned( FInsertDSQL ) then
    FInsertDSQL.SysDeallocate( true );
  if Assigned( FDeleteDSQL ) then
    FDeleteDSQL.SysDeallocate( true );
end;

procedure TIB_UpdateSQL.CheckKeyRelation;
var
  ii, kk: integer;
begin
  if Dataset.KeyRelation <> '' then
  begin
    ii := -1;
    for kk := 0 to Dataset.Fields.RelationCount - 1 do
    begin
      if CompareText( Dataset.KeyRelation,
                      Dataset.Fields.RelationNames[ kk ] ) = 0 then
      begin
        ii := kk;
        Break;
      end;
    end;
    if ii = -1 then
      raise EIB_DatasetError.CreateWithSender( Self, E_Invalid_KeyRelation );
  end;
end;

function TIB_UpdateSQL.CheckDSQLNeedsInit( var ADSQL: TIB_Statement ): boolean;
begin
  if not Assigned( ADSQL ) then
  begin
    ADSQL := TIB_Statement.Create( Self );
    ADSQL.OnError := DoHandleError;
  end;
  if Dataset.Prepared and ( not ADSQL.Prepared ) then
    with ADSQL do
    begin
      FBindingCursor := Dataset;
      IB_Connection := Dataset.IB_Connection;
      IB_Transaction := Dataset.IB_Transaction;
    end;
  Result := Dataset.Prepared and ( not ADSQL.Prepared );
end;

procedure TIB_UpdateSQL.AssignKeyRefs( ADSQL: TIB_Statement );
var
  ii: integer;
  jj: integer;
  tmpAliasName: TIB_Identifier;
begin
// Adjust so that the columns of the KeyFields will move into the last
// columns of the ADSQL.Params since the Where clause params will be last.
  jj := ADSQL.Params.ColumnCount - Dataset.KeyFields.ColumnCount;
  for ii := 0 to Dataset.KeyFields.ColumnCount - 1 do
  begin
    with ADSQL.Params[ ii + jj ] do
    begin
      tmpAliasName := PSQLVAR^.AliasName;
      PSQLVAR^ := Dataset.KeyFields[ii].PSQLVAR^;
      PSQLVAR^.SQLInd := pointer( Dataset.KeyFields[ii].FNewColumnInd );
      PSQLVAR^.SQLData := Dataset.KeyFields[ii].FNewColumnBuffer;
      PSQLVAR^.AliasName := tmpAliasName;
      PSQLVAR^.AliasName_length := Length( tmpAliasName );
      if PSQLVAR^.SQLname = IBO_DB_KEY then
      begin
        PSQLVAR^.SQLname := IBO_RDB + IBO_DB_KEY;
        PSQLVAR^.SQLname_length := Length( IBO_RDB + IBO_DB_KEY );
      end;
      if not Dataset.KeyFields.IsKeyFields then
      begin
      // This means that the data buffer is being used as the KeyFields
      // So, in this case we want to look at the OLD values.
        PSQLVAR^.SQLInd := pointer( Dataset.KeyFields[ii].FOldColumnInd );
        PSQLVAR^.SQLData := Dataset.KeyFields[ii].FOldColumnBuffer;
      end;
    end;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_UpdateSQL.PrepareCustomDML(       ADSQL: TIB_Statement;
                                          const ASQL: string );
var
  ii: smallint;
  tmpParam: TIB_Column;
  tmpCol: TIB_Column;
  tmpName: string;
  ucName: string;
begin
  with ADSQL do
  begin
    FCombineDuplicateParams := true;
    SQL.Text := ASQL;
    Prepare;
    try
      for ii := 0 to ParamCount - 1 do
      begin
        tmpParam := Params[ ii ];
        tmpName := tmpParam.FieldName;
        tmpCol := Dataset.FindField( tmpName );
        if Assigned( tmpCol ) then
        begin
          tmpParam.PSQLVAR^.SQLtype := tmpCol.PSQLVAR^.SQLtype;
          tmpParam.PSQLVAR^.SQLscale := tmpCol.PSQLVAR^.SQLscale;
          tmpParam.PSQLVAR^.SQLsubtype := tmpCol.PSQLVAR^.SQLsubtype;
          tmpParam.PSQLVAR^.SQLlen := tmpCol.PSQLVAR^.SQLlen;
          tmpParam.PSQLVAR^.SQLInd := pointer( tmpCol.PSQLVAR^.SQLind );
          tmpParam.PSQLVAR^.SQLData := tmpCol.PSQLVAR^.SQLdata;
        end
        else
        begin
          ucName := UpperCase( tmpName );
          if ( Pos( '"OLD_', ucName ) = 1 ) or
             ( Pos( '"OLD.', ucName ) = 1 ) then
          begin
            System.Delete( tmpName, 2, 4 );
            tmpCol := Dataset.FindField( tmpName );
            ucName := '';
          end
          else
          if ( Pos( 'OLD_', ucName ) = 1 ) or
             ( Pos( 'OLD.', ucName ) = 1 ) then
          begin
            System.Delete( tmpName, 1, 4 );
            tmpCol := Dataset.FindField( tmpName );
            ucName := '';
          end;
          if Assigned( tmpCol ) then
          begin
            tmpParam.PSQLVAR^.SQLtype := tmpCol.PSQLVAR^.SQLtype;
            tmpParam.PSQLVAR^.SQLscale := tmpCol.PSQLVAR^.SQLscale;
            tmpParam.PSQLVAR^.SQLsubtype := tmpCol.PSQLVAR^.SQLsubtype;
            tmpParam.PSQLVAR^.SQLlen := tmpCol.PSQLVAR^.SQLlen;
            tmpParam.PSQLVAR^.SQLInd := pointer( tmpCol.FOldColumnInd );
            tmpParam.PSQLVAR^.SQLData := tmpCol.FOldColumnBuffer;
          end
          else
          if ucName <> '' then
          begin
            tmpCol := Dataset.FindKeyField( tmpName );
            if Assigned( tmpCol ) then
            begin
              tmpParam.PSQLVAR^.SQLtype := tmpCol.PSQLVAR^.SQLtype;
              tmpParam.PSQLVAR^.SQLscale := tmpCol.PSQLVAR^.SQLscale;
              tmpParam.PSQLVAR^.SQLsubtype := tmpCol.PSQLVAR^.SQLsubtype;
              tmpParam.PSQLVAR^.SQLlen := tmpCol.PSQLVAR^.SQLlen;
              tmpParam.PSQLVAR^.SQLInd := pointer( tmpCol.PSQLVAR^.SQLind );
              tmpParam.PSQLVAR^.SQLData := tmpCol.PSQLVAR^.SQLdata;
              if tmpParam.PSQLVAR^.SQLname = IBO_DB_KEY then
              begin
                tmpParam.PSQLVAR^.SQLname := IBO_RDB+IBO_DB_KEY;
                tmpParam.PSQLVAR^.SQLname_length := Length( IBO_RDB+IBO_DB_KEY );
              end;
              if not Dataset.KeyFields.IsKeyFields then
              begin
              // This means that the data buffer is being used as the KeyFields
              // So, in this case we want to look at the OLD values.
                tmpParam.PSQLVAR^.SQLInd := pointer( tmpCol.FOldColumnInd );
                tmpParam.PSQLVAR^.SQLData := tmpCol.FOldColumnBuffer;
              end;
            end;
          end;
        end;
        if not Assigned( tmpCol ) then
          raise EIB_DatasetError.CreateWithSender( Self,
                                                   Format( E_Inv_Custom_DML,
                                                   [tmpParam.FullFieldName] ));
      end;
    except
      Unprepare;
      raise;
    end;
  end;
end;

const
  OldPfx = 'OLD.';

function TIB_UpdateSQL.GetEditDSQL: TIB_Statement;
var
  ii, jj: smallint;
  tmpS: string;
  edSQL: string;
  SKR: string;
begin
  if CheckDSQLNeedsInit( FEditDSQL ) then
  begin
    if ModifySQL.Count > 0 then
      PrepareCustomDML( FEditDSQL, FModifySQL.Text )
    else
    with Dataset do
    begin
      CheckKeyRelation;
      SKR := SysKeyRelation;
      tmpS := GetRelNameByRelAlias( SKR );
      if tmpS = '' then
        tmpS := SKR
      else
        tmpS := tmpS + ' ' + SKR;
      edSQL := 'UPDATE ' + tmpS + #13#10;
      jj := 0;
      for ii := 0 to FieldCount - 1 do with Fields[ii] do
      begin
        if ( not ReadOnly ) and
           ( not Computed ) and
           ( RelAliasName = SKR ) then
        begin
          if jj = 0 then
            edSQL := edSQL + '  SET ' + RelAliasName + '.' + SQLName + ' = ' +
                                        ParamChar + SQLName
          else
            edSQL := edSQL + #13#10'    , ' +
                     RelAliasName + '.' + SQLName + ' = ' +
                     ParamChar + SQLName;
          Inc( jj );
        end;
      end;
      if jj = 0 then
        raise EIB_DatasetError.CreateWithSender( Self,
                                              Format( E_Init_Default_SQL_Failed,
                                               [ 'EditDSQL' ] ));
      edSQL := edSQL + #13#10'WHERE ';
      if SearchedEdits then
      begin
        if FCachedUpdates then
        begin
          for ii := 0 to Fields.ColumnCount - 1 do
          begin
            with Fields[ii] do
            begin
              if ( not IsArray ) and
                 ( not IsBlob ) and
                 ( not Computed ) and
                 ( RelAliasName = SKR ) then
              begin
                if ii > 0 then
                  edSQL := edSQL + IBO_AND;
                edSQL := edSQL + RelAliasName + '.' + SQLName + ' = ' +
                                 ParamChar + OldPfx + SQLName;
              end;
            end;
          end;
          PrepareCustomDML( FEditDSQL, edSQL );
        end
        else
        begin
          for ii := 0 to KeyFields.ColumnCount - 1 do
            with KeyFields[ii] do
            begin
              if ii > 0 then edSQL := edSQL + IBO_AND;
              edSQL := edSQL + RelAliasName + '.' + SQLName + ' = ' +
                               ParamChar + OldPfx + SQLName;
            end;
          FEditDSQL.SQL.Text := edSQL;
          FEditDSQL.FCombineDuplicateParams := false;
          FEditDSQL.Prepare;
          AssignKeyRefs( FEditDSQL );
        end;
      end
      else
      begin
        edSQL := edSQL + 'CURRENT OF ' + Fields.CursorName;
        FEditDSQL.SQL.Text := edSQL;
        FEditDSQL.Prepare;
      end;
      jj := 0;
      for ii := 0 to FieldCount - 1 do
        with Fields[ii] do
          if ( not ReadOnly ) and
             ( not Computed ) and
             ( RelAliasName = SKR ) then
          begin
            FEditDSQL.Params.PSQLDA.SQLVAR[jj] := PSQLVAR^;
            Inc( jj );
          end;
    end;
  end;
  Result := FEditDSQL;
end;

function TIB_UpdateSQL.GetLockDSQL: TIB_Statement;
var
  ii, jj: smallint;
  lkSQL: string;
  tmpS: string;
  SKR: string;
begin
  if CheckDSQLNeedsInit( FLockDSQL ) then
  begin
    if LockSQL.Count > 0 then
      PrepareCustomDML( FLockDSQL, FLockSQL.Text )
    else
    with Dataset do
    begin
      CheckKeyRelation;
      SKR := SysKeyRelation;
      tmpS := GetRelNameByRelAlias( SKR );
      if tmpS = '' then
        tmpS := SKR
      else
        tmpS := tmpS + ' ' + SKR;
      lkSQL := 'UPDATE ' + tmpS;
      jj := 0;
      for ii := 0 to FieldCount - 1 do
      begin
        with Fields[ii] do
        begin
          if ( not ReadOnly ) and
             ( not Computed ) and
             ( RelAliasName = SKR ) then
          begin
            lkSQL := lkSQL + ' SET ' + RelAliasName + '.' + SQLName + ' = ' +
                                       RelAliasName + '.' + SQLName;
            Inc( jj );
            Break;
          end;
        end;
      end;
      if jj = 0 then
      begin
        raise EIB_DatasetError.CreateWithSender( Self,
                                              Format( E_Init_Default_SQL_Failed,
                                               [ 'LockSQL' ] ));
      end;
      lkSQL := lkSQL + #13#10'WHERE ';
      if SearchedEdits then
      begin
        if FCachedUpdates then
        // Actually, this indicates a problem here!
          for ii := 0 to Fields.ColumnCount - 1 do with Fields[ii] do
          begin
            if ( not IsArray ) and
               ( not IsBlob ) and
               ( not Computed ) and
               ( RelAliasName = SKR ) then
            begin
              if ii > 0 then lkSQL := lkSQL + IBO_AND;
              lkSQL := lkSQL + RelAliasName + '.' +
                               SQLName + ' = ' + ParamChar + OldPfx + SQLName;
            end;
          end
        else
          for ii := 0 to KeyFields.ColumnCount - 1 do
            with KeyFields[ii] do
            begin
              if ii > 0 then lkSQL := lkSQL + IBO_AND;
              lkSQL := lkSQL + RelAliasName + '.' +
                               SQLName + ' = ' + ParamChar + OldPfx + SQLName;
            end;
        if FCachedUpdates then
          PrepareCustomDML( FLockDSQL, lkSQL )
        else
        begin
          FLockDSQL.SQL.Text := lkSQL;
          FLockDSQL.FCombineDuplicateParams := false;
          FLockDSQL.Prepare;
          AssignKeyRefs( FLockDSQL );
        end;
      end else begin
        lkSQL := lkSQL + 'CURRENT OF ' + Fields.CursorName;
        FLockDSQL.SQL.Text := lkSQL;
        FLockDSQL.Prepare;
      end;
    end;
  end;
  Result := FLockDSQL;
end;

function TIB_UpdateSQL.GetDeleteDSQL: TIB_Statement;
var
  ii: smallint;
  tmpS: string;
  dlSQL: string;
  SKR: string;
begin
  if CheckDSQLNeedsInit( FDeleteDSQL ) then
  begin
    if DeleteSQL.Count > 0 then
      PrepareCustomDML( FDeleteDSQL, FDeleteSQL.Text )
    else
    with Dataset do
    begin
      CheckKeyRelation;
      SKR := SysKeyRelation;
      tmpS := GetRelNameByRelAlias( SKR );
      if tmpS = '' then
        tmpS := SKR
      else
        tmpS := tmpS + ' ' + SKR;
      dlSQL := 'DELETE FROM ' + tmpS + ' '#13#10'WHERE ';
      if SearchedDeletes then
      begin
        if FCachedUpdates then
          for ii := 0 to Fields.ColumnCount - 1 do with Fields[ii] do
          begin
            if ( not IsArray ) and
               ( not IsBlob ) and
               ( not Computed ) and
               ( RelAliasName = SKR ) then //!!! This may need help
            begin
              tmpS := RelAliasName;
              if tmpS = '' then
                tmpS := SQLName
              else
                tmpS := tmpS + '.' + SQLName;
              if ii > 0 then
                dlSQL := dlSQL + IBO_AND;
              dlSQL := dlSQL + tmpS + ' = ' + ParamChar + OldPfx + SQLName;
            end;
          end
        else
          for ii := 0 to KeyFields.ColumnCount - 1 do with KeyFields[ii] do
          begin
            tmpS := RelAliasName;
            if tmpS = '' then
              tmpS := SQLName
            else
              tmpS := tmpS + '.' + SQLName;
            if ii > 0 then
              dlSQL := dlSQL + IBO_AND;
            dlSQL := dlSQL + tmpS + ' = ' + ParamChar + OldPfx + SQLName;
          end;
        if FCachedUpdates then
          PrepareCustomDML( FDeleteDSQL, dlSQL )
        else
        begin
          FDeleteDSQL.SQL.Text := dlSQL;
          FDeleteDSQL.FCombineDuplicateParams := false;
          FDeleteDSQL.Prepare;
          AssignKeyRefs( FDeleteDSQL );
        end;
      end
      else
      begin
        dlSQL := dlSQL + 'CURRENT OF ' + Fields.CursorName;
        FDeleteDSQL.SQL.Text := dlSQL;
        FDeleteDSQL.Prepare;
      end;
    end;
  end;
  Result := FDeleteDSQL;
end;

function TIB_UpdateSQL.GetInsertDSQL: TIB_Statement;
var
  jj, kk: smallint;
  ss, tt: string;
  tmpPrm: TIB_Column;
  tmpS: string;
  SKR: string;
begin
  if CheckDSQLNeedsInit( FInsertDSQL ) then
  begin
    if InsertSQL.Count > 0 then
      PrepareCustomDML( FInsertDSQL, FInsertSQL.Text )
    else
    begin
      CheckKeyRelation;
      SKR := Dataset.SysKeyRelation;
      kk := 0;
      ss := '';
      tt := '';
      for jj := 0 to Dataset.FieldCount - 1 do
      begin
        if ( not Dataset.Fields[jj].ReadOnly ) and
           ( not Dataset.Fields[jj].Computed ) and
           ( Dataset.Fields[jj].RelAliasName = SKR ) then
        begin
          if kk > 0 then
          begin
            ss := ss + #13#10', ' + Dataset.Fields[jj].SQLName;
            tt := tt + #13#10', ' + Dataset.ParamChar +
                                    Dataset.Fields[jj].FieldName;
          end else
          begin
            ss := ss + #13#10'( ' + Dataset.Fields[jj].SQLName;
            tt := tt + #13#10'( ' + Dataset.ParamChar +
                                    Dataset.Fields[jj].FieldName;
          end;
          Inc( kk );
        end;
      end;
      if kk > 0 then
      begin
        ss := ss + ')'#13#10;
        tt := tt + ')';

        tmpS := Dataset.GetRelNameByRelAlias( SKR );

        FInsertDSQL.SQL.Text := 'INSERT INTO ' +
                                tmpS + ss +
                                'VALUES ' + tt;
        FInsertDSQL.FCombineDuplicateParams := false;
        FInsertDSQL.Prepare;
        try
          if ( kk = FInsertDSQL.ParamCount ) then
          begin
            kk := 0;
            for jj := 0 to Dataset.FieldCount - 1 do
              with Dataset.Fields[jj] do
                if ( not ReadOnly ) and
                   ( not Computed ) and
                   ( RelAliasName = SKR ) then
                begin
                  FInsertDSQL.Params[kk].PSQLVAR^ := PSQLVAR^;
                  if not Odd( FInsertDSQL.Params[kk].PSQLVAR^.SQLType ) then
                    Inc( FInsertDSQL.Params[kk].PSQLVAR^.SQLType );
                  Inc( kk );
                end;
          end
          else
          begin
            for jj := 0 to FInsertDSQL.ParamCount - 1 do
              with FInsertDSQL.Params[jj] do
              begin
                tmpPrm := Dataset.FieldByName( FieldName );
                PSQLVAR^ := tmpPrm.PSQLVAR^;
                if not Odd( PSQLVAR^.SQLType ) then
                  Inc( PSQLVAR^.SQLType );
              end;
          end;
        except
          FInsertDSQL.Unprepare;
          raise;
        end;
      end;
    end;
  end;
  Result := FInsertDSQL;
end;

{------------------------------------------------------------------------------}

procedure TIB_UpdateSQL.SQL_Edit;
var
  ii, jj, kk: smallint;
  ASQLDA: PXSQLDA;
  edSQL: string;
  WasUpdated: boolean;
  tmpS: string;
  SKR: string;
begin
  WasUpdated := false;
  try
  if ( ModifySQL.Count > 0 ) or
     ( PreparedEdits and ( not Dataset.FCachedUpdates )) then
  with EditDSQL do
  begin
    CheckTransaction( true );
    if FieldCount = 0 then
      API_Execute
    else
      API_Execute2;
    if ( RowsAffected = 0 ) and ( EditDSQL.StatementType = stUpdate ) then
      raise EIB_DatasetError.CreateWithSender( Self,
                                               E_Record_Not_Located_For_Update )
    else
    if RowsAffected > 1 then
    begin
      WasUpdated := true;
      raise EIB_DatasetError.CreateWithSender( Self,
                                               E_Multiple_Records_Updated );
    end;
    WasUpdated := true;
    if FieldCount > 0 then
      HandleOutputValues( EditDSQL );
  end
  else
  with Dataset do
  begin
    SKR := SysKeyRelation;
    if SearchedEdits then
    begin
      CheckKeyRelation;
      tmpS := GetRelNameByRelAlias( SKR );
      if tmpS = '' then
        tmpS := SKR
      else
        tmpS := tmpS + ' ' + SKR;
      edSQL := 'UPDATE ' + tmpS + #13#10;
      jj := 0;
      for ii := 0 to FieldCount - 1 do
      begin
        with Fields[ii] do
        begin
          if IsModified and ( not ReadOnly ) and ( not Computed ) then
          begin
            if jj = 0 then
              edSQL := edSQL + ' SET '
            else
              edSQL := edSQL + #13#10'   , ';
            edSQL := edSQL + SQLName + ' = ?'; // Server
            Inc( jj );
          end;
        end;
      end;
      if jj > 0 then
      begin
        if FCachedUpdates then
        begin
          for ii := 0 to Fields.ColumnCount - 1 do
            with Fields[ii] do
              if ( not IsBlob ) and
                 ( not IsArray ) and
                 ( RelAliasName = SKR ) then
                if ( not OldIsNull ) then
                  Inc( jj );
        end
        else
          Inc( jj, KeyFields.ColumnCount );

        GetMem( ASQLDA, XSQLDA_LENGTH( jj ));
        try
          ASQLDA.version := SQLDA_VERSION1;
          ASQLDA.sqln := jj;
          ASQLDA.sqld := jj;

          edSQL := edSQL + #13#10'WHERE ';
          if FCachedUpdates then
          begin
            kk := 0;
            for ii := 0 to Fields.ColumnCount - 1 do with Fields[ii] do
              if ( not IsBlob ) and
                 ( not IsArray ) and
                 ( RelAliasName = SKR ) then
              begin
                if kk > 0 then edSQL := edSQL + IBO_AND;
                edSQL := edSQL + RelAliasName + '.';
                if stLitCriteria( SQLName ) = IBO_DB_KEY then
                  edSQL := edSQL + IBO_RDB;
                if OldIsNull then
                  edSQL := edSQL + SQLName + ' IS NULL'
                else
                  edSQL := edSQL + SQLName + ' = ?'; // Server
                Inc( kk );
              end;
          end
          else
            for ii := 0 to KeyFields.ColumnCount - 1 do
              with KeyFields[ii] do
              begin
                if ii > 0 then
                  edSQL := edSQL + IBO_AND;
                if RelAliasName <> '' then
                  edSQL := edSQL + RelAliasName + '.';
                if stLitCriteria( SQLName ) = IBO_DB_KEY then
                  edSQL := edSQL + IBO_RDB;
                edSQL := edSQL + SQLName + ' = ?'; // Server
              end;
          jj := 0;
          for ii := 0 to FieldCount - 1 do
            with Fields[ii] do
              if IsModified and ( not ReadOnly ) and ( not Computed ) then
              begin
                ASQLDA.SQLVAR[jj] := PSQLVAR^;
                Inc( jj );
              end;
          if FCachedUpdates then
            for ii := 0 to Fields.ColumnCount - 1 do with Fields[ii] do
            begin
              if ( not IsBlob ) and
                 ( not IsArray ) and
                 ( RelAliasName <> '' ) and
                 ( not OldIsNull ) then
              begin
                ASQLDA.SQLVAR[jj] := PSQLVAR^;
                if ASQLDA.SQLVAR[jj].sqlname = IBO_DB_KEY then
                begin
                  ASQLDA.SQLVAR[jj].sqlname := IBO_RDB+IBO_DB_KEY;
                  ASQLDA.SQLVAR[jj].sqlname_length := Length(IBO_RDB+IBO_DB_KEY);
                end;
                ASQLDA.SQLVAR[jj].SQLInd  := pointer( FOldColumnInd );
                ASQLDA.SQLVAR[jj].SQLData := FOldColumnBuffer;
                Inc( jj );
              end;
            end
          else
            for ii := 0 to KeyFields.ColumnCount - 1 do with KeyFields[ii] do
            begin
              ASQLDA.SQLVAR[jj] := PSQLVAR^;
              if ASQLDA.SQLVAR[jj].sqlname = IBO_DB_KEY then
              begin
                ASQLDA.SQLVAR[jj].sqlname := IBO_RDB+IBO_DB_KEY;
                ASQLDA.SQLVAR[jj].sqlname_length := Length(IBO_RDB+IBO_DB_KEY);
              end;
              if not KeyFields.IsKeyFields then
              begin
              // This means that the data buffer is being used as the KeyFields
                ASQLDA.SQLVAR[jj].SQLInd  := pointer( FOldColumnInd );
                ASQLDA.SQLVAR[jj].SQLData := FOldColumnBuffer;
              end;
              Inc( jj );
            end;
          CheckTransaction( true );
          with IB_Session do
          begin
            if not Assigned( isc_dsql_execute_immediate ) then
              RevertToOriginalHooks; // does an acquire of hooks if necessary
            errcode := isc_dsql_execute_immediate( @status,
                                                   PdbHandle,
                                                   PtrHandle,
                                                   Length(edSQL),
                                                   PChar(edSQL),
                                                   IB_Connection.SQLDialect,
                                                   ASQLDA );
            if errcode = 0 then
              WasUpdated := true
            else
              HandleException( Dataset );
          end;
        finally
          FreeMem( ASQLDA, XSQLDA_LENGTH( ASQLDA.sqln ));
        end;
      end;
    end
    else
    begin
      for ii := 0 to Fields.RelationCount - 1 do
      begin
        kk := 0;
        tmpS := GetRelNameByRelAlias( Fields.RelationNames[ ii ] );
        edSQL := 'UPDATE ' + tmpS {+ ' ' + Fields.RelationNames[ ii ]} + #13#10;
        for jj := 0 to FieldCount - 1 do
        begin
          with Fields[jj] do
          begin
            if ( IsModified ) and
               ( not ReadOnly ) and
               ( not Computed ) and
               ( tmpS = RelName ) and
               ( Fields.RelationNames[ ii ] = RelAliasName ) then
            begin
              if kk = 0 then
                edSQL := edSQL + '  SET '
              else
                edSQL := edSQL + '    , ';
              edSQL := edSQL + SQLName + ' = ?'; // Server
              Inc( kk );
            end;
          end;
        end;
        if kk > 0 then
        begin
          GetMem( ASQLDA, XSQLDA_LENGTH( kk ));
          try
            ASQLDA.version := SQLDA_VERSION1;
            ASQLDA.sqln := kk;
            ASQLDA.sqld := kk;
            edSQL := edSQL + #13#10'WHERE CURRENT OF ' + Fields.CursorName;
            kk := 0;
            for jj := 0 to FieldCount - 1 do
            begin
              with Fields[jj] do
              begin
                if ( IsModified ) and
                   ( not ReadOnly ) and
                   ( not Computed ) and
                   ( tmpS = RelName ) and
                   ( Fields.RelationNames[ ii ] = RelAliasName ) then
                begin
                  ASQLDA.SQLVAR[kk] := PSQLVAR^;
                  Inc( kk );
                end;
              end;
            end;
            CheckTransaction( true );
            with IB_Session do
            begin
              if not Assigned( isc_dsql_execute_immediate ) then
                RevertToOriginalHooks; // does an acquire of hooks if necessary
              errcode := isc_dsql_execute_immediate( @status,
                                                     PdbHandle,
                                                     PtrHandle,
                                                     Length(edSQL),
                                                     PChar(edSQL),
                                                     IB_Connection.SQLDialect,
                                                     ASQLDA );
              if errcode = 0 then
                WasUpdated := true
              else
                HandleException( Dataset );
            end;
          finally
            FreeMem( ASQLDA, XSQLDA_LENGTH( ASQLDA.sqln ));
          end;
        end;
      end;
    end;
  end;
  finally
    if WasUpdated then
    begin
      Dataset.FPostToServerSucceeded := true;
      try
        if dcfAnnounceEdit in Dataset.DMLCacheFlags then
          Dataset.DoDMLCacheAnnounceItem( ditEdit );
      finally
        Dataset.IB_Transaction.Activate;
      end;
    end;
  end;
end;

function TIB_UpdateSQL.SQL_Lock: boolean;
begin
  with LockDSQL do
  begin
    CheckTransaction( true );
    if FieldCount = 0 then API_Execute
                      else API_Execute2;
    Result := ( RowsAffected = 1 ) or ( LockDSQL.StatementType <> stUpdate );
    if Result and ( FieldCount > 0 ) then
      HandleOutputValues( LockDSQL );
  end;
end;

procedure TIB_UpdateSQL.SQL_Insert;
var
  ii, jj: integer;
  ss, tt: string;
  ASQLDA: PXSQLDA;
  WasUpdated: boolean;
  tmpS: string;
  SKR: string;
begin
  WasUpdated := false;
  FNeedRecordResync := false;
  try
  if ( InsertSQL.Count > 0 ) or PreparedInserts then
  begin
    with InsertDSQL do
    begin
      CheckTransaction( true );
      if FieldCount = 0 then API_Execute
                        else API_Execute2;
      if ( RowsAffected = 0 ) and ( InsertDSQL.StatementType = stInsert ) then
        raise EIB_DatasetError.CreateWithSender( Self, E_Record_Not_Inserted )
      else
      if ( RowsAffected > 1 ) and ( InsertDSQL.StatementType = stInsert ) then
      begin
        WasUpdated := true;
        raise EIB_DatasetError.CreateWithSender( Self,
                                                 E_Multiple_Records_Inserted );
      end;
      WasUpdated := true;
      if FieldCount > 0 then
        HandleOutputValues( InsertDSQL );
    end;
  end
  else
  with Dataset do
  begin
    CheckKeyRelation;
    SKR := SysKeyRelation;
    jj := 0;
    ss := '';
    tt := '';
    for ii := 0 to FieldCount - 1 do
    begin
      if ( not Fields[ii].ReadOnly ) and
         ( not Fields[ii].Computed ) and
         ( Fields[ii].RelAliasName = SKR ) then
      begin
        if not Fields[ii].IsNull then
        begin
          if jj > 0 then
          begin
            ss := ss + #13#10', ' +           Fields[ii].SQLName;
            tt := tt + #13#10', ' + '? /* ' + Fields[ii].FieldName + ' */';
          end
          else
          begin
            ss := ss + #13#10'( ' +           Fields[ii].SQLName;
            tt := tt + #13#10'( ' + '? /* ' + Fields[ii].FieldName + ' */';
          end;
          Inc( jj );
        end
        else
        if Fields[ii].IsDefaulted then
          FNeedRecordResync := true;
      end;
    end;
    if jj = 0 then
      raise EIB_DatasetError.CreateWithSender( Dataset,
                                        E_Invalid_INSERT_statement_No_columns );
    ss := ss + ' )';
    tt := tt + ' )';

    tmpS := GetRelNameByRelAlias( SKR );
    if tmpS = '' then
      tmpS := SKR;

    ss := 'INSERT INTO ' + tmpS + ss + #13#10'VALUES ' + tt;
    GetMem( ASQLDA, XSQLDA_LENGTH( jj ));
    try
      ASQLDA.version := SQLDA_VERSION1;
      ASQLDA.sqln := jj;
      ASQLDA.sqld := jj;
      jj := 0;
      for ii := 0 to FieldCount - 1 do
        if ( not Fields[ii].ReadOnly ) and
           ( not Fields[ii].Computed ) and
           ( not Fields[ii].IsNull ) and
           ( Fields[ii].RelAliasName = SKR ) then
        begin
          ASQLDA.SQLVAR[jj] := Fields[ii].PSQLVAR^;
          Inc( jj );
        end;                  
        CheckTransaction( true );
      with IB_Session do
      begin
        if not Assigned( isc_dsql_execute_immediate ) then
          RevertToOriginalHooks; // does an acquire of hooks if necessary
        errcode := isc_dsql_execute_immediate( @status,
                                               PdbHandle,
                                               PtrHandle,
                                               Length( ss ),
                                               PChar( ss ),
                                               IB_Connection.SQLDialect,
                                               ASQLDA );
        if errcode = 0 then
          WasUpdated := true
        else
          HandleException( Dataset );
      end;
    finally
      FreeMem( ASQLDA, XSQLDA_LENGTH( ASQLDA.sqln ));
    end;
  end;
  finally
    if WasUpdated then
    begin
      Dataset.FPostToServerSucceeded := true;
      try
        if dcfAnnounceInsert in Dataset.DMLCacheFlags then
          Dataset.DoDMLCacheAnnounceItem( ditInsert );
      finally
        Dataset.IB_Transaction.Activate;
      end;
    end;
  end;
end;

procedure TIB_UpdateSQL.SQL_Delete;
var
  ii, jj, kk: smallint;
  ASQLDA: PXSQLDA;
  dlSQL: string;
  WasUpdated: boolean;
  tmpS: string;
  SKR: string;
begin
  WasUpdated := false;
  try
    if ( DeleteSQL.Count > 0 ) or ( not Dataset.FCachedUpdates ) then
      with DeleteDSQL do
      begin
        CheckTransaction( true );
        if FieldCount = 0 then API_Execute
                          else API_Execute2;
        if ( RowsAffected = 0 ) and ( DeleteDSQL.StatementType = stDelete ) then
          raise EIB_DatasetError.CreateWithSender( Self,
                                               E_Record_Not_Located_For_Delete )
        else
        if RowsAffected > 1 then
        begin
          WasUpdated := true;
          raise EIB_DatasetError.CreateWithSender( Self,
                                                    E_Multiple_Records_Deleted )
        end
        else
          WasUpdated := true;
      end
    else
    with Dataset do
    begin
      if SearchedDeletes then
      begin
        CheckKeyRelation;
        SKR := SysKeyRelation;
        tmpS := GetRelNameByRelAlias( SKR );
        if tmpS = '' then
          tmpS := SKR
        else
          tmpS := tmpS + ' ' + SKR;
        dlSQL := 'DELETE FROM ' + tmpS + #13#10;
        jj := 0;
        if FCachedUpdates then
        begin
          for ii := 0 to Fields.ColumnCount - 1 do
            with Fields[ii] do
              if ( RelAliasName = SKR ) and ( not OldIsNull ) then
                Inc( jj );
        end
        else
          Inc( jj, KeyFields.ColumnCount );
        GetMem( ASQLDA, XSQLDA_LENGTH( jj ));
        try
          ASQLDA.version := SQLDA_VERSION1;
          ASQLDA.sqln := jj;
          ASQLDA.sqld := jj;
          dlSQL := dlSQL + #13#10'WHERE ';
          if FCachedUpdates then
          begin
            kk := 0;
            for ii := 0 to Fields.ColumnCount - 1 do with Fields[ii] do
              if RelAliasName = SKR then
              begin
                if kk > 0 then dlSQL := dlSQL + IBO_AND;
                dlSQL := dlSQL + RelAliasName + '.';
                if stLitCriteria( SQLName ) = IBO_DB_KEY then
                  dlSQL := dlSQL + IBO_RDB;
                if OldIsNull then
                  dlSQL := dlSQL + SQLName + ' IS NULL'
                else
                  dlSQL := dlSQL + SQLName + ' = ?'; // Server
                Inc( kk );
              end;
          end
          else
            for ii := 0 to KeyFields.ColumnCount - 1 do with KeyFields[ii] do
            begin
              if ii > 0 then
                dlSQL := dlSQL + IBO_AND;
              if RelAliasName = SKR then
                dlSQL := dlSQL + RelAliasName + '.';
              if stLitCriteria( SQLName ) = IBO_DB_KEY then
                dlSQL := dlSQL + IBO_RDB;
              dlSQL := dlSQL + SQLName + ' = ?'; // Server
            end;
          jj := 0;
          if FCachedUpdates then
            for ii := 0 to Fields.ColumnCount - 1 do with Fields[ii] do
            begin
              if ( RelAliasName = SKR ) and ( not OldIsNull ) then
              begin
                ASQLDA.SQLVAR[jj] := PSQLVAR^;
                if ASQLDA.SQLVAR[jj].sqlname = IBO_DB_KEY then
                begin
                  ASQLDA.SQLVAR[jj].sqlname := IBO_RDB+IBO_DB_KEY;
                  ASQLDA.SQLVAR[jj].sqlname_length := Length(IBO_RDB+IBO_DB_KEY);
                end;
                ASQLDA.SQLVAR[jj].SQLInd  := pointer( FOldColumnInd );
                ASQLDA.SQLVAR[jj].SQLData := FOldColumnBuffer;
                Inc( jj );
              end;
            end
          else
            for ii := 0 to KeyFields.ColumnCount - 1 do with KeyFields[ii] do
            begin
              ASQLDA.SQLVAR[jj] := PSQLVAR^;
              if ASQLDA.SQLVAR[jj].sqlname = IBO_DB_KEY then
              begin
                ASQLDA.SQLVAR[jj].sqlname := IBO_RDB+IBO_DB_KEY;
                ASQLDA.SQLVAR[jj].sqlname_length := Length(IBO_RDB+IBO_DB_KEY);
              end;
              if not KeyFields.IsKeyFields then
              begin
              // This means that the data buffer is being used as the KeyFields
                ASQLDA.SQLVAR[jj].SQLInd  := pointer( FOldColumnInd );
                ASQLDA.SQLVAR[jj].SQLData := FOldColumnBuffer;
              end;
              Inc( jj );
            end;
          CheckTransaction( true );
          with IB_Session do
          begin
            if not Assigned( isc_dsql_execute_immediate ) then
              RevertToOriginalHooks; // does an acquire of hooks if necessary
            errcode := isc_dsql_execute_immediate( @status,
                                                   PdbHandle,
                                                   PtrHandle,
                                                   Length(dlSQL),
                                                   PChar(dlSQL),
                                                   IB_Connection.SQLDialect,
                                                   ASQLDA );
            if errcode = 0 then
              WasUpdated := true
            else
              HandleException( Dataset );
          end;
        finally
          FreeMem( ASQLDA, XSQLDA_LENGTH( ASQLDA.sqln ));
        end;
      end
      else
      begin
        raise EIB_DatasetError.CreateWithSender( Self, E_Not_ready_yet );
{
        for ii := 0 to Fields.RelationCount - 1 do begin
          kk := 0;
          dlSQL := 'DELETE FROM ' + Fields.RelationNames[ ii ] + #13#10;
          for jj := 0 to FieldCount - 1 do begin
            with Fields[jj] do begin
              if IsModified and not ReadOnly and not Computed and
                ( Fields.RelationNames[ ii ] = RelAliasName ) then
              begin
                if kk = 0 then begin
                  dlSQL := dlSQL + '  SET ';
                 end else begin
                  dlSQL := dlSQL + '    , ';
                end;
                dlSQL := dlSQL + SQLNameD + ' = ?'; // Server
                Inc( kk );
              end;
            end;
          end;
          if kk > 0 then begin
            GetMem( ASQLDA, XSQLDA_LENGTH( kk ));
            try
              ASQLDA.version := SQLDA_VERSION1;
              ASQLDA.sqln := kk;
              ASQLDA.sqld := kk;
              dlSQL := dlSQL +
                           #13#10'WHERE CURRENT OF ' + Fields.CursorName;
              kk := 0;
              for jj := 0 to FieldCount - 1 do begin
                with Fields[jj] do begin
                  if IsModified and not ReadOnly and not Computed and
                    ( Fields.RelationNames[ ii ] = RelAliasName ) then
                  begin
                    ASQLDA.SQLVAR[kk] := PSQLVAR^;
                    Inc( kk );
                  end;
                end;
              end;
              with IB_Session do
              begin
                if not Assigned( isc_dsql_execute_immediate ) then
                  RevertToOriginalHooks; // does an acquire of hooks if necessary
                errcode := isc_dsql_execute_immediate( @status,
                                                       PdbHandle,
                                                       PtrHandle,
                                                       Length(dlSQL),
                                                       PChar(dlSQL),
                                                       IB_Connection.SQLDialect,
                                                       ASQLDA );
                if errcode = 0 then begin
                  WasUpdated := true;
                end else begin
                  HandleException( Dataset );
                end;
              end;
            finally
              FreeMem( ASQLDA, XSQLDA_LENGTH( ASQLDA.sqln ));
            end;
          end;
        end;
}
      end;
    end;
  finally
    if WasUpdated then
    begin
      Dataset.FPostToServerSucceeded := true;
      try
        if dcfAnnounceDelete in Dataset.DMLCacheFlags then
          Dataset.DoDMLCacheAnnounceItem( ditDelete );
      finally
        Dataset.IB_Transaction.Activate;
      end;
    end;
  end;
end;

procedure TIB_UpdateSQL.HandleOutputValues( ADSQL: TIB_Statement );
var
  ii: integer;
  tmpCol: TIB_Column;
  KeyAffected: boolean;
begin
  KeyAffected := false;
  ADSQL.Fields.CleanBuffers( true );
  ADSQL.Fields.FRowState := rsUnmodified;
  for ii := 0 to ADSQL.FieldCount - 1 do
  begin
    tmpCol := Dataset.FindField( ADSQL.Fields[ ii ].FieldName );
    if Assigned( tmpCol ) then
      tmpCol.ColData := ADSQL.Fields[ ii ].ColData;
    tmpCol := Dataset.FindKeyField( ADSQL.Fields[ ii ].FieldName );
    if Assigned( tmpCol ) then
    begin
      tmpCol.ColData := ADSQL.Fields[ ii ].ColData;
      KeyAffected := true;
    end;
  end;
  if KeyAffected then
    Dataset.SysUpdateKeyData( false );
end;

procedure TIB_UpdateSQL.DoHandleError(       Sender: TObject;
                                       const errcode: isc_long;
                                             ErrorMessage,
                                             ErrorCodes: TStringList;
                                       const SQLCODE: isc_long;
                                             SQLMessage,
                                             SQL: TStringList;
                                         var RaiseException: boolean);
begin
  if RaiseException and Assigned( Dataset ) then
  begin
    Dataset.DoHandleError( Sender,
                           errcode,
                           ErrorMessage,
                           ErrorCodes,
                           SQLCODE,
                           SQLMessage,
                           SQL,
                           RaiseException );
  end;
end;

procedure TIB_UpdateSQL.SetDeleteSQL( AValue: TIB_StringList );
begin
  FDeleteSQL.Assign( AValue );
end;
procedure TIB_UpdateSQL.SetModifySQL( AValue: TIB_StringList );
begin
  FModifySQL.Assign( AValue );
end;
procedure TIB_UpdateSQL.SetLockSQL( AValue: TIB_StringList );
begin
  FLockSQL.Assign( AValue );
end;
procedure TIB_UpdateSQL.SetInsertSQL( AValue: TIB_StringList );
begin
  FInsertSQL.Assign( AValue );
end;

procedure TIB_UpdateSQL.SQLChanged( Sender: TObject );
begin
  if ( Sender = FDeleteSQL ) and Assigned( FDeleteSQL ) and
                                 Assigned( FDeleteDSQL ) then
    FDeleteDSQL.Unprepare;
  if ( Sender = ModifySQL ) and Assigned( FModifySQL ) and
                                Assigned( FEditdSQL ) then 
    FEditDSQL.Unprepare;
  if ( Sender = LockSQL ) and Assigned( FLockSQL ) and
                              Assigned( FLockDSQL ) then
    FLockDSQL.Unprepare;
  if ( Sender = InsertSQL ) and Assigned( FInsertSQL ) and
                                Assigned( FInsertDSQL ) then
    FInsertDSQL.Unprepare;
end;

// IBA_DataSource.INT

constructor TIB_DataSource.Create(AOwner: TComponent);
begin
  inherited Create( AOwner );
  FDataLinkList := TList.Create;
  FAutoEdit := true;
  FAutoInsert := true;
  FEnabled := true;
  FControlsDisabledLevel := 0;
  FInterfaceDisabledLevel := 0;
end;

destructor TIB_DataSource.Destroy;
begin
  Dataset := nil;
  while DataLinkCount > 0 do
    TIB_DataLink(FDataLinkList.Items[ 0 ]).DataSource := nil;
  FDataLinkList.Free;
  FDataLinkList := nil;
  inherited Destroy;
end;

{------------------------------------------------------------------------------}

function TIB_DataSource.Edit: boolean;
begin
  if State <> dssEdit then
  begin
    if Prepared and AutoEdit then
      Dataset.SysEdit;
  end;
  Result := State = dssEdit;
end;

function TIB_DataSource.Insert: boolean;
begin
  if ( State <> dssInsert ) and
     Prepared and
     AutoInsert and (( Dataset.Fields.RowState = rsNone ) or
                     ( not Dataset.Unidirectional )) then
    Dataset.SysInsert;
  Result := State = dssInsert;
end;

function TIB_DataSource.Modify: boolean;
begin
  if ( not Modifying ) and Assigned( Dataset ) then
    if not Edit then
      if Dataset.Fields.RowState = rsNone then
        Insert;
  Result := Modifying;
end;

function TIB_DataSource.Modifying: boolean;
begin
  Result := State in [ dssEdit, dssInsert, dssSearch ];
end;

procedure TIB_DataSource.Reset;
begin
  ProcessEvent( setFieldsDataChange, 0 );
end;

{------------------------------------------------------------------------------}

procedure TIB_DataSource.SetStatement( AValue: TIB_Statement );
begin
  if ( AValue <> nil ) and ( not ( AValue is TIB_Dataset )) then
    raise EIB_DatasetError.CreateWithSender( Self, E_InvalidClassType )
  else
    inherited SetStatement( AValue );
end;

procedure TIB_DataSource.SetDataset( AValue: TIB_Dataset );
begin
  if IsLinkedTo( AValue ) then
    raise EIB_DatasetError.CreateWithSender( Self, E_NoCircularRef );
  if AValue <> Dataset then
  begin
    if Dataset <> nil then
      with Dataset do
        if Assigned( FDataSourceList ) then
          FDataSourceList.Remove( Self );
    Statement := AValue;
    if Dataset <> nil then
      with Dataset do
        if Assigned( FDataSourceList ) then
          FDataSourceList.Add( Self );
  end;
end;

function TIB_DataSource.GetDataset: TIB_Dataset;
begin
  Result := FStatement as TIB_Dataset;
end;

function TIB_DataSource.GetReadOnly: boolean;
begin
  if State = dssSearch then
    Result := false
  else
  if Dataset <> nil then
    Result := Dataset.ReadOnly
  else
    Result := true;
end;

function TIB_DataSource.GetCanModify: boolean;
begin
  if not Prepared then
    Result := false
  else
  if Modifying then
    Result := true
  else
  if Dataset.CanModify then
  begin
    if RowState = rsNone then
      Result := AutoInsert
    else
      Result := AutoEdit;
  end
  else
    Result := false;
end;

function TIB_DataSource.GetNeedToPost: boolean;
begin
  Result := Prepared and Dataset.NeedToPost;
end;

function TIB_DataSource.GetEof: boolean;
begin
  if Dataset <> nil then
    Result := Dataset.Eof
  else
    Result := true;
end;

function TIB_DataSource.GetBof: boolean;
begin
  if Dataset <> nil then
    Result := Dataset.Bof
  else
    Result := true;
end;

{------------------------------------------------------------------------------}

function TIB_DataSource.GetState: TIB_DatasetState;
begin
  if Dataset <> nil then
    Result := Dataset.State
  else
    Result := dssInactive;
end;

function TIB_DataSource.GetRowState: TIB_RowState;
begin
  if Dataset <> nil then
    Result := Dataset.Fields.RowState
  else
    Result := rsNone;
end;

{------------------------------------------------------------------------------}

procedure TIB_DataSource.SysPrepareSQL;
var
  ii: integer;
begin
  inherited SysPrepareSQL;
  for ii := 0 to DataLinkCount - 1 do
    TIB_DataLink(FDataLinkList.Items[ ii ]).SysPrepareSQL;
end;

procedure TIB_DataSource.ProcessEvent( AEvent: TIB_StatementEventType;
                                       Info: longint );
var
  ii: integer;
begin
// Keeping this portion above makes it so that all of the linking mechanisms
// between datasets, like KeyLinks relationships, are all worked out prior to
// the events on the TIB_Datasource component being activated.
{ -- }
  if ( ControlsDisabledLevel = 0 ) or
     ( AEvent in [ setBeforeAssignment,
                   setAfterAssignment,
                   setParamsRefsChanged,
                   setFieldsRefsChanged ] ) then
  begin
    ii := 0;
    while ii < DataLinkCount do
    begin
      if FInterfaceDisabledLevel > 0 then
      begin
        if not( TIB_DataLink( FDataLinkList.Items[ ii ] ).FIsCtrlLink ) then
          TIB_DataLink(FDataLinkList.Items[ ii ]).ProcessStatementEvent( AEvent,
                                                                         Info );
        if AEvent = setFieldsDataChange then
          FDSDataChange := true; // indicate refresh still needed
      end
      else
        TIB_DataLink(FDataLinkList.Items[ ii ]).ProcessStatementEvent( AEvent,
                                                                       Info );
      Inc( ii );
    end;
  end;
{ -- }
  inherited ProcessEvent( AEvent, Info );
  if ( not ( csLoading    in ComponentState )) and
     ( not ( csDestroying in ComponentState )) then
  case AEvent of
    setBeforeAssignment:
    if Assigned( FBeforeAssignment ) then
      FBeforeAssignment( Self, Dataset );
    setAfterAssignment:
    begin
      if Assigned( FAfterAssignment ) then
        FAfterAssignment( Self, Dataset );
      SysStateChanged;
      ProcessEvent( setFieldsDataChange, 0 );
    end;
    setFieldsDataChange:
    begin
      if ControlsDisabledLevel > 0 then
        FDSDataChange := true
      else
        SysDataChange( TIB_Column( Info ));
    end;
    setFieldsUpdateData: SysUpdateData( TIB_Column( Info ));
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_DataSource.AddDataLink( ADataLink: TObject );
begin
  FDataLinkList.Add( ADataLink );
end;

procedure TIB_DataSource.RemoveDataLink( ADataLink: TObject );
begin
  FDataLinkList.Remove( ADataLink );
end;

function TIB_DataSource.GetDataLinkCount: integer;
begin
  if Assigned( FDataLinkList ) then
    Result := FDataLinkList.Count
  else
    Result := 0;
end;

{------------------------------------------------------------------------------}

function TIB_DataSource.IsLinkedTo( Dataset: TIB_Dataset ): boolean;
var
  DataSource: TIB_DataSource;
begin
  Result := True;
  while Assigned( Dataset ) do
  begin
    DataSource := Dataset.MasterSource;
    if DataSource = nil then Break;
    if DataSource = Self then Exit;
    Dataset := DataSource.Dataset;
  end;
  Result := False;
end;

{------------------------------------------------------------------------------}

procedure TIB_DataSource.SetAnnounceFocus( AValue: boolean );
begin
  if AnnounceFocus <> AValue then
  begin
    FAnnounceFocus := AValue;
    if AnnounceFocus then
      AllowFocus := true;
  end;
end;

procedure TIB_DataSource.SetAllowFocus( AValue: boolean );
begin
  if AllowFocus <> AValue then
  begin
    FAllowFocus := AValue;
    if not AllowFocus then
      AnnounceFocus := false;
  end;
end;

function TIB_DataSource.IsAllowFocusStored: boolean;
begin
  Result := AllowFocus and ( not AnnounceFocus );
end;

procedure TIB_DataSource.SetFocus;
begin
  if AllowFocus then
    IB_Session.FocusedDataSource := Self;
end;

procedure TIB_DataSource.DoGainFocus;
begin
  if ( not ( csLoading    in ComponentState )) and
     ( not ( csDestroying in ComponentState )) then
    if Assigned( OnGainFocus ) then
      OnGainFocus( Self, Dataset );
end;

procedure TIB_DataSource.DoLoseFocus;
begin
  if ( not ( csLoading    in ComponentState )) and
     ( not ( csDestroying in ComponentState )) then
    if Assigned( OnLoseFocus ) then
      OnLoseFocus( Self, Dataset );
end;

{------------------------------------------------------------------------------}

procedure TIB_DataSource.SetEnabled( AValue: boolean );
begin
  if Enabled <> AValue then
  begin
    FEnabled := AValue;
    if Enabled then
      EnableControls
    else
      DisableControls;
  end;
end;

procedure TIB_DataSource.DoStateChanged;
begin
  if ( not ( csLoading    in ComponentState )) and
     ( not ( csDestroying in ComponentState )) then
    if Assigned( FOnStateChanged ) then
      FOnStateChanged( Self, Dataset );
end;

procedure TIB_DataSource.SysUpdateData( AField: TIB_Column );
begin
  if ControlsDisabledLevel = 0 then
    if Assigned( FOnUpdateData ) then
      FOnUpdateData( Self, Dataset, AField );
end;

procedure TIB_DataSource.SysDataChange( AField: TIB_Column );
begin
  if Assigned( FOnDataChange ) then
    FOnDataChange( Self, Dataset, AField );
end;

procedure TIB_DataSource.SysStateChanged;
var
  ii: integer;
  tmpList: TList;
begin
  if ControlsDisabledLevel > 0 then
    FDSStateChange := true
  else
  if FInterfaceDisabledLevel > 0 then
  begin
    DoStateChanged;
    FDSStateChange := true; // Still need refresh
    tmpList := TList.create;
    try
      for ii := 0 to DataLinkCount - 1 do
        if not( TIB_DataLink( FDataLinkList.Items[ ii ] ).FIsCtrlLink ) then
          tmpList.Add( FDataLinkList.Items[ ii ] );
      for ii := 0 to tmpList.Count - 1 do
        if FDataLinkList.IndexOf( tmpList[ ii ] ) >= 0 then
          TIB_DataLink(tmpList[ ii ]).SysStateChanged;
      for ii := 0 to DataLinkCount - 1 do
        if not( TIB_DataLink( FDataLinkList.Items[ ii ] ).FIsCtrlLink ) and
           (tmpList.IndexOf( FDataLinkList[ ii ] ) = -1) then
          TIB_DataLink(FDataLinkList[ ii ]).SysStateChanged;
    finally
      tmpList.Free;
    end;
  end
  else
  begin
    DoStateChanged;
    tmpList := TList.create;
    try
      for ii := 0 to DataLinkCount - 1 do
        tmpList.Add( FDataLinkList.Items[ ii ] );
      for ii := 0 to tmpList.Count - 1 do
        if FDataLinkList.IndexOf( tmpList[ ii ] ) >= 0 then
          TIB_DataLink(tmpList[ ii ]).SysStateChanged;
      for ii := 0 to DataLinkCount - 1 do
        if tmpList.IndexOf( FDataLinkList[ ii ] ) = -1 then
          TIB_DataLink(FDataLinkList[ ii ]).SysStateChanged;
    finally
      tmpList.Free;
    end;
  end;
end;

procedure TIB_DataSource.DisableControls;
begin
  if (FControlsDisabledLevel = 0) and
     (FInterfaceDisabledLevel = 0) then
  begin
    FDSStateChange := false;
    FDSDataChange := false;
  end;
  Inc( FControlsDisabledLevel );
end;

procedure TIB_DataSource.EnableControls;
begin
  Dec( FControlsDisabledLevel );
  if (FControlsDisabledLevel = 0) and
     (FInterfaceDisabledLevel = 0) then
  begin
    if FDSStateChange then
    begin
      SysStateChanged;
      SysDataChange( nil );
    end
    else
    if FDSDataChange then
      SysDataChange( nil );
  end;
end;

procedure TIB_DataSource.DisableInterface;
begin
  if (FControlsDisabledLevel = 0) and
     (FInterfaceDisabledLevel = 0) then
  begin
    FDSStateChange := false;
    FDSDataChange := false;
  end;
  Inc( FInterfaceDisabledLevel );
end;

procedure TIB_DataSource.EnableInterface;
begin
  Dec( FInterfaceDisabledLevel );
  if (FControlsDisabledLevel = 0) and
     (FInterfaceDisabledLevel = 0) then
  begin
    if FDSStateChange then
    begin
      SysStateChanged;
      SysDataChange( nil );
    end
    else
    if FDSDataChange then
      SysDataChange( nil );
  end;
end;

// IBA_DataLink.INT

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  09-apr-2002                                                                 }
{     Fixed the Append code that inverted the calling order of the events      }
{     OnStateChanged and AfterInsert (of datasource and datasets)              }
{                                                                              }
{  Wassim Haddad                                                               }
{  10-Aug-2001                                                                 }
{                                                                              }
{  if Dataset's CalculateAllFields property is set, then an OnCalculateField   }
{  event (with AField=nil) is fired on each DataLink SysUpdateData. This       }
{  effectively makes the calculated fields act more dynamically.               }
{                                                                              }
{                                                                              }
{******************************************************************************}

constructor TIB_DataLink.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  FIsCtrlLink := false;
  FIsGridLink := false;
  FSearchAlways := false;
  FSearchBuffer := '';
  FSearchSaved := '';
  FSearchLast := '';
  FAdvSearchDisplay := '';
  FAdvSearchDisplaySaved := '';
  FAdvSearchDisplayLast := '';
  FAdvSearchWhere := '';
  FAdvSearchWhereSaved := '';
  FAdvSearchWhereLast := '';
  FAdvSearchData := '';
  FAdvSearchDataSaved := '';
  FAdvSearchDataLast := '';
  FInternalSearchUpdate := 0;
  FSearchUpdateRequired := false;
end;

destructor TIB_DataLink.Destroy;
begin
  UnlockSessionCursor;
  DataSource := nil;
  inherited Destroy;
end;

procedure TIB_DataLink.Loaded;
begin
  inherited Loaded;
  DoReceiveFocus( IB_Session.FocusedDataSource );
end;

function TIB_DataLink.GetBufferHasBof: boolean;
var
  tmpDataset: TIB_Dataset;
begin
  tmpDataset := Dataset;
  if Assigned( tmpDataset ) then
    Result := tmpDataset.BufferHasBof
  else
    Result := false;
end;

function TIB_DataLink.GetBufferHasEof: boolean;
var
  tmpDataset: TIB_Dataset;
begin
  tmpDataset := Dataset;
  if Assigned( tmpDataset ) then
    Result := tmpDataset.BufferHasEof
  else
    Result := false;
end;

function TIB_DataLink.GetBufferActive: boolean;
var
  tmpDataset: TIB_Dataset;
begin
  tmpDataset := Dataset;
  if Assigned( tmpDataset ) then
    Result := tmpDataset.BufferActive
  else
    Result := false;
end;

function TIB_DataLink.GetDataChanging: boolean;
begin
  Result := FChangingLevel > 0;
end;

procedure TIB_DataLink.CheckBrowseMode;
begin
// Abstract.
end;

{------------------------------------------------------------------------------}

procedure TIB_DataLink.ActiveChanged;
begin
// Abstract.
end;

procedure TIB_DataLink.DatasetChanged;
begin
  RecordChanged( nil );
end;

function TIB_DataLink.Edit: boolean;
begin
  if Assigned( DataSource ) then
    Result := DataSource.Edit
  else
    Result := false;
end;

procedure TIB_DataLink.Insert;
begin
  if Assigned( DataSource ) then
    DataSource.Insert;
end;

procedure TIB_DataLink.Append;
begin
  if Prepared and Dataset.CanInsert then
  begin
    try
      Dataset.DisableControls;
      if Dataset.NeedToPost then
        Dataset.Post;
      if ( not Dataset.Unidirectional ) and ( Dataset is TIB_BDataset ) then
        TIB_BDataset(Dataset).GotoEof;
    finally
      Dataset.EnableControls;
    end;
    Insert;
  end;
end;

function TIB_DataLink.Modify: boolean;
begin
  Inc( FChangingLevel );
  try
    if ( not ReadOnly ) and Assigned( DataSource ) then
      Result := DataSource.Modify
    else
      Result := false;
  finally
    Dec( FChangingLevel );
  end;
end;

procedure TIB_DataLink.SetSearchAlways( AValue: boolean );
begin
  if SearchAlways <> AValue then
  begin
    FSearchAlways := AValue;
    if State <> dssSearch then
    begin
      SysStateChanged;
      SysDataChange( nil );
    end;
  end;
end;

{------------------------------------------------------------------------------}

function TIB_DataLink.GetKeySource: TIB_DataSource;
begin
  if Assigned( Dataset ) then
    Result := Dataset.KeySource
  else
    Result := nil;
end;

function TIB_DataLink.GetMasterSource: TIB_DataSource;
begin
  if Assigned( Dataset ) then
    Result := Dataset.MasterSource
  else
    Result := nil;
end;

function TIB_DataLink.GetEditing: boolean;
begin
  Result := FEditing and
            ( not ReadOnly ) and
            Assigned( DataSource ) and DataSource.Modifying;
end;

procedure TIB_DataLink.SetEditing( AValue: boolean );
begin
  if FEditing <> AValue then
  begin
    FEditing := AValue;
    SysEditingChanged;
  end;
end;

procedure TIB_DataLink.SetFocus;
begin
  if Assigned( DataSource ) then
    DataSource.SetFocus;
end;

procedure TIB_DataLink.UpdateData;
begin
  if Assigned( DataSource ) then
    SysUpdateData( nil );
end;

procedure TIB_DataLink.UpdateRecord;
begin
// This caused problems in the IB_Grid
// when arrowing down and implicitely posting.
//  FUpdating := True;
//  try
    UpdateData;
//  finally
//    FUpdating := False;
//  end;
end;

procedure TIB_DataLink.Reset;
begin
  if Assigned( DataSource ) then
    SysDataChange( nil );
end;

procedure TIB_DataLink.EditingChanged;
begin
// Abstract.
end;

procedure TIB_DataLink.LayoutChanged;
begin
  DataSetChanged;
end;

procedure TIB_DataLink.RecordChanged( Field: TIB_Column );
begin
// Abstract.
end;

procedure TIB_DataLink.DataChange;
begin
  SysDataChange( nil );
end;

procedure TIB_DataLink.FocusControl( AField: TIB_ColumnRef );
begin
// Abstract.
end;

{------------------------------------------------------------------------------}

function TIB_DataLink.GetSearchEntryName: string;
begin
  Result := '';
end;

function TIB_DataLink.GetField: TIB_Column;
begin
  Result := nil;
end;

function TIB_DataLink.GetSearchBuffer: string;
begin
  Result := FSearchBuffer;
end;

procedure TIB_DataLink.SetSearchBuffer( const AValue: string );
begin
  if SearchBuffer <> AValue then
  begin
    FSearchBuffer := AValue;
    if FInternalSearchUpdate = 0 then
    begin
      if Assigned( Dataset ) then
        Dataset.InvalidateSQL;
      if State = dssSearch then
        SysDataChange( GetField );
      FSearchUpdateRequired := false;
    end
    else
      FSearchUpdateRequired := true;
  end;
end;

procedure TIB_DataLink.SetAdvSearchDisplay( const AValue: string );
begin
  if FAdvSearchDisplay <> AValue then
  begin
    FAdvSearchDisplay := AValue;
    if FInternalSearchUpdate = 0 then
    begin
      if State = dssSearch then
        SysDataChange( GetField );
      FSearchUpdateRequired := false;
    end
    else
      FSearchUpdateRequired := true;
  end;
end;

function TIB_DataLink.GetAdvSearchDisplay: string;
begin
  Result := FAdvSearchDisplay;
  if Result = '' then
    Result := SearchBuffer;
end;

procedure TIB_DataLink.SetAdvSearchWhere( const AValue: string );
begin
  if FAdvSearchWhere <> AValue then
  begin
    FAdvSearchWhere := AValue;
    if FInternalSearchUpdate = 0 then
    begin
      if Assigned( Dataset ) then
        Dataset.InvalidateSQL;
      FSearchUpdateRequired := false;
    end
    else
      FSearchUpdateRequired := true;
  end;
end;

function TIB_DataLink.GetAdvSearchWhere: string;
begin
  Result := FAdvSearchWhere;
end;

procedure TIB_DataLink.SetAdvSearchData( const AValue: string );
begin
  if FAdvSearchData <> AValue then
    FAdvSearchData := AValue;
end;

function TIB_DataLink.GetAdvSearchData: string;
begin
  Result := FAdvSearchData;
end;

procedure TIB_DataLink.ClearSearchCriteria( WithUpdate: boolean );

  procedure SearchUpdate;
  begin
    if FSearchUpdateRequired then
      if Assigned( Dataset ) then
      begin
        Dataset.InvalidateSQL;
        if Dataset.State = dssSearch then
          SysDataChange( GetField );
      end;
  end;

begin
  Inc( FInternalSearchUpdate );
  try
    SearchBuffer := '';
    SetAdvSearchDisplay( '' );
    AdvSearchWhere := '';
    AdvSearchData := '';
    if WithUpdate then
      SearchUpdate;
  finally
    Dec( FInternalSearchUpdate );
  end;
end;

function TIB_DataLink.GetBof: boolean;
begin
  if Assigned( Dataset ) then
    Result := Dataset.Bof
  else
    Result := true;
end;

function TIB_DataLink.GetEof: boolean;
begin
  if Assigned( Dataset ) then
    Result := Dataset.Eof
  else
    Result := true;
end;

{------------------------------------------------------------------------------}

procedure TIB_DataLink.SetBoundToBuffer( AValue: boolean );
begin
  if BoundToBuffer <> AValue then
  begin
    FBoundToBuffer := AValue;
    SysBindingChanged;
  end;
end;

procedure TIB_DataLink.SetDataSource( AValue: TIB_DataSource );
var
  LastVal: longint;
begin
  if DataSource <> AValue then
  begin
    ProcessStatementEvent( setBeforeAssignment, longint(AValue) );
    if Assigned( DataSource ) then
      DataSource.RemoveDataLink( Self );
    LastVal := longint( FDataSource );
    FDataSource := AValue;
    if Assigned( DataSource ) then
      DataSource.AddDataLink( Self );
    ProcessStatementEvent( setAfterAssignment, LastVal );
  end;
end;

function TIB_DataLink.GetDataset;
begin
  if Assigned( DataSource ) then
    Result := DataSource.Dataset
  else
    Result := nil;
end;

procedure TIB_DataLink.SetIgnoreColorScheme( AValue: boolean );
begin
  if IgnoreColorScheme <> AValue then
  begin
    FIgnoreColorScheme := AValue;
    SysStateChanged;
  end;
end;

function TIB_DataLink.GetPrepared: boolean;
begin
  Result := Assigned( DataSource ) and DataSource.Prepared;
end;

function TIB_DataLink.GetActive: boolean;
begin
  Result := Assigned( DataSource ) and DataSource.Active;
end;

function TIB_DataLink.GetState: TIB_DatasetState;
begin
  if Assigned( Dataset ) then
    Result := Dataset.State
  else
    Result := dssInactive;
  if BoundToBuffer and Active then
    Result := dssBrowse;
end;

function TIB_DataLink.GetCanModify: boolean;
begin
  if ( not Self.ReadOnly ) and Assigned( DataSet ) then
  begin
    with DataSource do
      Result := ( CanModify ) and
                (( AutoEdit and ( Dataset.Fields.RowState <> rsNone )) or
                 ( AutoInsert and Prepared ) or
                 ( State in [ dssEdit, dssInsert, dssSearch ] ));
    if Result then
    begin
      if State = dssSearch then
        Result := not PreventSearching
      else
      if ( Dataset.Fields.RowState <> rsNone ) and
         ( State <> dssInsert ) then
        Result := not PreventEditing
      else
        Result := not PreventInserting;
    end;
  end
  else
    Result := false;
end;

function TIB_DataLink.GetNeedToPost: boolean;
begin
  Result := Assigned( DataSource ) and DataSource.NeedToPost;
end;

function TIB_DataLink.GetRowState: TIB_RowState;
begin
  if Assigned( Dataset ) then
  begin
    if BoundToBuffer then
      Result := Dataset.BufferFields.RowState
    else
      Result := Dataset.Fields.RowState;
  end
  else
    Result := rsNone;
end;

{                                                                              }

function TIB_DataLink.GetReadOnly: boolean;
begin
  Result := false;
  if ( not Result ) then
    if Assigned( Dataset ) then
      if State <> dssSearch then
        Result := DataSource.ReadOnly;
  if ( not Result ) then
    case State of
      dssEdit:   Result := PreventEditing;
      dssInsert: Result := PreventInserting;
      dssSearch: Result := PreventSearching;
    end;
end;

function TIB_DataLink.GetPreventDeleting: boolean;
begin
  Result := ( not Assigned( Dataset )) or Dataset.PreventDeleting;
end;

function TIB_DataLink.GetPreventEditing: boolean;
begin
  Result := ( not Assigned( Dataset )) or Dataset.PreventEditing;
end;

function TIB_DataLink.GetPreventInserting: boolean;
begin
  Result := ( not Assigned( Dataset )) or Dataset.PreventInserting;
end;

function TIB_DataLink.GetPreventSearching: boolean;
begin
  Result := ( not Assigned( Dataset )) or Dataset.PreventSearching;
end;

function TIB_DataLink.GetDisabled: boolean;
begin
  Result := Assigned( DataSet ) and
            ( (not Dataset.Unidirectional) and
             ( DataSource.ControlsDisabledLevel > 0 ));
end;

{------------------------------------------------------------------------------}

function TIB_DataLink.GetColorScheme: boolean;
begin
  Result := ( not IgnoreColorScheme ) and
            Assigned( Dataset ) and
            Dataset.ColorScheme and
            ( Dataset.Unidirectional or
            ((( Dataset.ControlsDisabledLevel > 0 ) and Dataset.IsPosting ) or
            ( Dataset.ControlsDisabledLevel = 0 )) and
            ( DataSource.ControlsDisabledLevel = 0 ));
end;

function TIB_DataLink.GetColor: TColor;
begin
  if BoundToBuffer then
    Result := GetActiveColor
  else
  if SearchAlways then
    Result := GetSearchingColor
  else
  case State of
    dssPrepared,
    dssBrowse:
    begin
      Result := GetActiveColor;
      if Dataset is TIB_BDataset then with Dataset as TIB_BDataset do
      begin
        if rfInserted in RowFlags then Result := GetInsertingColor else
        if rfDeleted  in RowFlags then Result := GetDeletingColor  else
        if rfEdited   in RowFlags then Result := GetEditingColor;
      end;
    end;
    dssEdit:     Result := GetEditingColor;
    dssInsert:   Result := GetInsertingColor;
    dssDelete:   Result := GetDeletingColor;
    dssSearch:   Result := GetSearchingColor;
    else         Result := GetInvalidColor;
  end;
  IB_Session.DoGetDataLinkColor( Self, Result );
end;

function TIB_DataLink.GetActiveColor: TColor;
begin
  if RowState = rsNone then
    Result := IB_Session.PreparedColor
  else
  if ReadOnly then
    Result := IB_Session.ReadOnlyColor
  else
    Result := IB_Session.BrowsingColor;
end;

function TIB_DataLink.GetEditingColor: TColor;
begin
  if ReadOnly then
    Result := IB_Session.ReadOnlyColor
  else
    Result := IB_Session.EditingColor;
end;

function TIB_DataLink.GetInsertingColor: TColor;
begin
  if ReadOnly then
    Result := IB_Session.ReadOnlyColor
  else
    Result := IB_Session.InsertingColor;
end;

function TIB_DataLink.GetDeletingColor: TColor;
begin
  Result := IB_Session.DeletingColor;
end;

function TIB_DataLink.GetSearchingColor: TColor;
begin
  if not ReadOnly then
    Result := IB_Session.SearchingColor
  else
    Result := IB_Session.ReadOnlyColor;
end;

function TIB_DataLink.GetInvalidColor: TColor;
begin
  Result := IB_Session.InvalidColor;
end;

function TIB_DataLink.GetActiveRecord: longint;
begin
  if Assigned( Dataset ) then begin
    Result := Dataset.RowNum;
  end else begin
    Result := 0;
  end;
end;

procedure TIB_DataLink.SetActiveRecord( AValue: longint );
begin
  if Assigned( Dataset ) then begin
    Dataset.RowNum := AValue;
  end;
end;

function TIB_DataLink.GetBufferRecord: longint;
begin
  if Assigned( Dataset ) then begin
    Result := Dataset.BufferRowNum;
  end else begin
    Result := 0;
  end;
end;

procedure TIB_DataLink.SetBufferRecord( AValue: longint );
begin
  if Assigned( Dataset ) then begin
    Dataset.BufferRowNum := AValue;
  end;
end;

function TIB_DataLink.GetRecordCount: longint;
begin
  if Assigned( Dataset ) then begin
    Result := Dataset.RecordCount;
  end else begin
    Result := -1;
  end;
end;

function TIB_DataLink.GetBufferRowCount: longint;
begin
  if Assigned( Dataset ) then begin
    Result := Dataset.BufferRowCount;
  end else begin
    Result := 0;
  end;
end;

procedure TIB_DataLink.SetBufferCount( AValue: longint );
begin
// Ignore this since it does not apply in IB Objects.
// You always see all of the records in the buffer!
end;

function TIB_DataLink.GetBufferCount: longint;
begin
  if Assigned( Dataset ) then begin
    Result := Dataset.BufferRowCount;
  end else begin
    Result := -1;
  end;
end;

procedure TIB_DataLink.SysPrepareSQL;
begin
  DoPrepareSQL;
  if AdvSearchWhere <> '' then begin
    Dataset.SQLWhereItems.Add( AdvSearchWhere );
  end;
end;

procedure TIB_DataLink.DoPrepareSQL;
begin
  if Assigned( OnPrepareSQL ) then begin
    OnPrepareSQL( Self, DataSource );
// This code causes problems after doing a ReadSearch() with a bookmark
// string from a KeyLinks relationship.
//  end else if SearchBuffer <> '' then begin
//    Dataset.AddSQLWhereClause( SearchBuffer );
  end;
end;

procedure TIB_DataLink.SysPreparedChanged;
begin
  if Assigned( OnPreparedChanged ) then
  begin
    OnPreparedChanged( Self, DataSource );
  end;
  if not Prepared then
  begin
    UnlockSessionCursor;
  end;
end;

procedure TIB_DataLink.SysBeforeExecute;
begin
  if Assigned( BeforeExecute ) then
  begin
    BeforeExecute( Self, DataSource );
  end;
end;

procedure TIB_DataLink.SysAfterExecute;
begin
  if Assigned( AfterExecute ) then
  begin
    AfterExecute( Self, DataSource );
  end;
end;

procedure TIB_DataLink.SysActiveChange;
begin
  ActiveChanged;
  if Assigned( OnActiveChange ) then
  begin
    OnActiveChange( Self, DataSource );
  end;
end;

procedure TIB_DataLink.SysLayoutChanged;
begin
  if Assigned( OnLayoutChanged ) then
  begin
    OnLayoutChanged( Self, DataSource );
  end;
end;

procedure TIB_DataLink.SysEditingChanged;
begin
  EditingChanged;
  if Assigned( OnEditingChanged ) then
  begin
    OnEditingChanged( Self, DataSource );
  end;
end;

procedure TIB_DataLink.SysStateChanged;
begin
  SetEditing(( NeedToPost or ( State = dssSearch )) and ( not ReadOnly ));
  if Assigned( OnStateChanged ) then
  begin
    OnStateChanged( Self, DataSource );
  end;
end;

procedure TIB_DataLink.SysDataChange( AField: TIB_Column );
begin
  if ( not FUpdating ) and ( FChangingLevel = 0 ) and ( not Disabled ) then
  begin
    Inc( FChangingLevel );
    try
      DoDataChange( AField );
    finally
      Dec( FChangingLevel );
    end;
  end;
end;

procedure TIB_DataLink.SysUpdateData( AField: TIB_Column );
begin
  if not FUpdating then
  begin
    FUpdating := true;
    try
      DoUpdateData( AField );
      if Assigned( DataSet ) and DataSet.CalculateAllFields then
        DataSet.DoCalculateField( DataSet.Fields, nil );
    finally
      FUpdating := false;
    end;
  end;
end;

procedure TIB_DataLink.SysBindingChanged;
begin
  SysStateChanged;
  SysDataChange( nil );
end;

procedure TIB_DataLink.SysAssignField;
begin
  //~TC~ See notes in DoBeforeAssignment
  SearchBuffer := '';
end;

{------------------------------------------------------------------------------}

procedure TIB_DataLink.DoBeforeAssignment;
begin
  if Assigned( BeforeAssignment ) then
    BeforeAssignment( Self, DataSource );
  //~TC~ SearchBuffer := ''; Causes problems during FieldRefsChanged events
  // ie. When the field is being cleared during destruction.  So we do
  // this in the SysAssignField after the field reference has changed
end;

procedure TIB_DataLink.DoAfterAssignment;
begin
  DatasetChanged;
  if Assigned( AfterAssignment ) then
    AfterAssignment( Self, DataSource );
  if not Prepared then
    UnlockSessionCursor;
  ActiveChanged;
end;

procedure TIB_DataLink.DoDataChange( AField: TIB_Column );
begin
  if Assigned( OnDataChange ) then
    OnDataChange( Self, Datasource, AField );
end;

procedure TIB_DataLink.DoUpdateData( AField: TIB_Column );
begin
  if Assigned( OnUpdateData ) then
    OnUpdateData( Self, Datasource, AField );
end;

procedure TIB_DataLink.DoReceiveFocus( DS: TIB_DataSource );
begin
  if ReceiveFocus then
  begin
    if Assigned( OnReceiveFocus ) then
      OnReceiveFocus( Self, DS )
    else
      DataSource := DS;
  end;
end;

procedure TIB_DataLink.SetReceiveFocus( AValue: boolean );
begin
  if ReceiveFocus <> AValue then
  begin
    FReceiveFocus := AValue;
    DoReceiveFocus( IB_Session.FocusedDataSource );
  end;
end;

procedure TIB_DataLink.ProcessStatementEvent( AEvent: TIB_StatementEventType;
                                              Info: longint );
  procedure SearchUpdate;
  begin
    if FSearchUpdateRequired then
    begin
      if Assigned( Dataset ) then
        Dataset.InvalidateSQL;
      if State = dssSearch then
        SysDataChange( nil );
    end;
  end;

  procedure SaveCriteria;
  begin
    FSearchSaved := SearchBuffer;
    FAdvSearchDisplaySaved := AdvSearchDisplay;
    FAdvSearchWhereSaved := AdvSearchWhere;
    FAdvSearchDataSaved := AdvSearchData;
  end;

  procedure RecallCriteria;
  begin
    Inc(FInternalSearchUpdate);
    try
      SearchBuffer := FSearchSaved;
      SetAdvSearchDisplay( FAdvSearchDisplaySaved );
      AdvSearchWhere := FAdvSearchWhereSaved;
      AdvSearchData := FAdvSearchDataSaved;
      SearchUpdate;
    finally
      Dec(FInternalSearchUpdate);
    end;
  end;

  procedure SaveLastCriteria;
  begin
    FSearchLast := SearchBuffer;
    FAdvSearchDisplayLast := AdvSearchDisplay;
    FAdvSearchWhereLast := AdvSearchWhere;
    FAdvSearchDataLast := AdvSearchData;
  end;

  procedure RecallLastCriteria;
  begin
    Inc(FInternalSearchUpdate);
    try
      SearchBuffer := FSearchLast;
      SetAdvSearchDisplay( FAdvSearchDisplayLast );
      AdvSearchWhere := FAdvSearchWhereLast;
      AdvSearchData := FAdvSearchDataLast;
      SearchUpdate;
    finally
      Dec(FInternalSearchUpdate);
    end;
  end;

  procedure WriteCriteria;
  var
    EName: string;
  begin
    EName := GetSearchEntryName;
    if ( EName <> '' ) and Assigned( Dataset.SearchCriteria ) then
    begin
      with Dataset.SearchCriteria do begin
        if SearchBuffer <> '' then
          Values[ EName ] := SearchBuffer;
        if AdvSearchWhere <> '' then
          Values[ EName + '.Where' ] := AdvSearchWhere;
        if AdvSearchData <> '' then
          Values[ EName + '.Data' ] := AdvSearchData;
        // Direct look at display buffer value!!!
        if FAdvSearchDisplay <> '' then begin
          Values[ EName + '.Display' ] := AdvSearchDisplay;
        end;
      end;
    end;
  end;

  procedure ReadCriteria;
  var
    EName: string;
  begin
    Inc(FInternalSearchUpdate);
    try
      EName := GetSearchEntryName;
      if ( EName <> '' ) and Assigned( Dataset.SearchCriteria ) then
      begin
        with Dataset.SearchCriteria do
        begin
          EName := GetSearchEntryName;
          SearchBuffer := Values[ EName ];
          SetAdvSearchDisplay( Values[ EName + '.Display' ] );
          AdvSearchWhere := Values[ EName + '.Where' ];
          AdvSearchData := Values[ EName + '.Data' ];
        end;
      end;
      SearchUpdate;
    finally
      Dec(FInternalSearchUpdate);
    end;
  end;

begin
  case AEvent of
    setBeforeAssignment: DoBeforeAssignment;
    setAfterAssignment: DoAfterAssignment;
    setPreparedChanged: SysPreparedChanged;
    setBeforeExecute: SysBeforeExecute;
    setAfterExecute: SysAfterExecute;
    setActiveChange: SysActiveChange;
    setLayoutChanged: SysLayoutChanged;
    setCheckBrowseMode: CheckBrowseMode;
    setFieldsUpdateData: SysUpdateData( TIB_Column( Info ));
    setFieldsDataChange: SysDataChange( TIB_Column( Info ));
    setFieldsRefsChanged: SysAssignField;
    setFocusControl: FocusControl( TIB_ColumnRef( Info ));
    setUpdateSearchCriteria:
    begin
      if State = dssSearch then
      begin
        SysUpdateData( nil );
      end;
      case TIB_SearchingEventType( Info ) of
        setClearCriteria: ClearSearchCriteria( true );
        setSaveCriteria: SaveCriteria;
        setRecallCriteria: RecallCriteria;
        setSaveLastCriteria: SaveLastCriteria;
        setRecallLastCriteria: RecallLastCriteria;
        setWriteCriteria: WriteCriteria;
        setReadCriteria: ReadCriteria;
      end;
    end;
    setOrderingChanged:
    begin
      if Assigned( OnOrderingChanged ) then
        OnOrderingChanged( Self, DataSource );
    end;
    setOrderingLinkChanged:
    begin
      if Assigned( OnOrderingLinkChanged ) then
        OnOrderingLinkChanged( Self, DataSource );
    end;
    setSearchingLinkChanged:
    begin
      if Assigned( OnSearchingLinkChanged ) then
        OnSearchingLinkChanged( Self, DataSource );
    end;
    setInvalidateRows:
    begin
      if Dataset.RowNum = Info then
        SysDataChange( nil );
    end;
  end;
  if FIsGridLink then
    ProcessGridEvent( AEvent, Info );;
end;

procedure TIB_DataLink.ProcessGridEvent( AEvent: TIB_StatementEventType;
                                         Info: longint );
begin
// do nothing, here as a base for grid links sub-class only.
end;

procedure TIB_DataLink.LockSessionCursor;
begin
  if ( not SessionCursorIsLocked ) and Assigned( IB_Session ) then
  begin
    IB_Session.BeginLockCursor;
    IB_Session.BeginBusy( true );
    FSessionCursorIsLocked := true;
  end;
end;

procedure TIB_DataLink.UnlockSessionCursor;
begin
  if SessionCursorIsLocked and Assigned( IB_Session ) then
  begin
    IB_Session.EndBusy;
    IB_Session.EndLockCursor;
    FSessionCursorIsLocked := false;
  end;
end;

// IBA_KeyDataLink.INT

procedure TIB_KeyDataLink.DoBeforeAssignment;
begin
  inherited DoBeforeAssignment;
  Inc( ChildDataset.FPreventKeySeeking );
  try
    ClearSearchCriteria( false );
  finally
    Dec( ChildDataset.FPreventKeySeeking );
  end;
  if Assigned( Dataset ) and ( FSearchUpdateRequired ) then
    Dataset.InvalidateSQL;
end;

procedure TIB_KeyDataLink.DatasetChanged;
begin
  ChildDataset.InvalidateKeyLinksMaps;
  inherited DatasetChanged;
end;

procedure TIB_KeyDataLink.DoAfterAssignment;
begin
  inherited DoAfterAssignment;
  ChildDataset.SysKeyStateChanged;
  ChildDataset.SysKeyDataChange( nil );
end;

procedure TIB_Dataset.InvalidateKeyLinksMaps;
var
  ii: integer;
  tt: TNotifyEvent;
begin
  tt := FKeyLinks.OnChange;
  try
    FKeyLinks.OnChange := nil;
    for ii := 0 to FKeyLinks.Count - 1 do
      FKeyLinks.Objects[ii] := nil;
  finally
    FKeyLinks.OnChange := tt;
  end;
  tt := FKeyDescLinks.OnChange;
  try
    FKeyDescLinks.OnChange := nil;
    for ii := 0 to FKeyDescLinks.Count - 1 do
      FKeyDescLinks.Objects[ii] := nil;
  finally
    FKeyDescLinks.OnChange := tt;
  end;
  if Assigned( FKeyLinksFieldsMap ) then
  begin
    FKeyLinksFieldsMap.Free;
    FKeyLinksFieldsMap := nil;
  end;
  if Assigned( FKeyDescLinksFieldsMap ) then
  begin
    FKeyDescLinksFieldsMap.Free;
    FKeyDescLinksFieldsMap := nil;
  end;
  FKeyLinksMapsValid := false;
end;

procedure TIB_Dataset.InvalidateMasterLinksMaps;
var
  ii: integer;
  tt: TNotifyEvent;
begin
  tt := FMasterLinks.OnChange;
  try
    FMasterLinks.OnChange := nil;
    for ii := 0 to FMasterLinks.Count - 1 do
      FMasterLinks.Objects[ii] := nil;
  finally
    FMasterLinks.OnChange := tt;
  end;
  tt := FMasterParamLinks.OnChange;
  try
    FMasterParamLinks.OnChange := nil;
    for ii := 0 to FMasterParamLinks.Count - 1 do
      FMasterParamLinks.Objects[ii] := nil;
  finally
    FMasterParamLinks.OnChange := tt;
  end;
  if Assigned( FMasterLinksParamsMap ) then
  begin
    FMasterLinksParamsMap.Free;
    FMasterLinksParamsMap := nil;
  end;
  if Assigned( FMasterLinksFieldsMap ) then
  begin
    FMasterLinksFieldsMap.Free;
    FMasterLinksFieldsMap := nil;
  end;
  if Assigned( FMasterParamLinksParamsMap ) then
  begin
    FMasterParamLinksParamsMap.Free;
    FMasterParamLinksParamsMap := nil;
  end;
  if Assigned( FMasterParamLinksFieldsMap ) then
  begin
    FMasterParamLinksFieldsMap.Free;
    FMasterParamLinksFieldsMap := nil;
  end;
  FMasterLinksMapsValid := false;
end;

procedure TIB_Dataset.CheckKeyLinksMaps;
var
  ii: integer;
  tmpS: string;
  tmpCol: TIB_Column;
  tmpKey: TIB_Column;
  tmpFld: TIB_Column;
  tmpEvent1: TNotifyEvent;
  tmpEvent2: TNotifyEvent;
begin
  if FKeyLinksMapsValid then
    Exit;
  if Assigned( FKeyLinksFieldsMap ) then
    FKeyLinksFieldsMap.Clear
  else
    FKeyLinksFieldsMap := TList.Create;
  if Assigned( FKeyDescLinksFieldsMap ) then
    FKeyDescLinksFieldsMap.Clear
  else
    FKeyDescLinksFieldsMap := TList.Create;
  if Prepared and ( not Assigned( FBindingCursor )) then
  begin
    tmpEvent1 := KeyLinks.OnChange;
    tmpEvent2 := KeyDescLinks.OnChange;
    try
      KeyLinks.OnChange := nil;
      KeyDescLinks.OnChange := nil;
      for ii := 0 to KeyLinks.Count - 1 do
      begin
        if KeyLinks.Objects[ii] = nil then
        begin
          Fields.GetByName( KeyLinks.IndexNames[ii], tmpFld );
          if Assigned( tmpFld ) then
          begin
            if ( tmpFld.RelName <> '' ) then
            begin
              KeyFields.GetByName( tmpFld.RelName + '.' +
                                   tmpFld.SQLName, tmpKey );
              if not Assigned( tmpKey ) then
                KeyFields.GetByName( tmpFld.RelName + '.' +
                                     tmpFld.FieldName, tmpKey );
            end;
            if not Assigned( tmpKey ) and ( tmpFld.SQLName <> '' ) then
              KeyFields.GetByName( tmpFld.SQLName, tmpKey );
            if not Assigned( tmpKey ) then
              KeyFields.GetByName( tmpFld.FieldName, tmpKey );
          end
          else
            KeyFields.GetByName( KeyLinks.IndexNamesCol[ii], tmpKey );
          if Assigned( tmpKey ) and Assigned( tmpFld ) and
             ( tmpKey.RelName = tmpFld.RelName ) then
            tmpKey.FRelAliasName := tmpFld.FRelAliasName;          
          if Assigned( tmpFld ) then
            KeyLinks.Objects[ii] := tmpFld
          else
          if Assigned( tmpKey ) then
            KeyLinks.Objects[ii] := tmpKey
          else
            KeyLinks.Objects[ii] := pointer( -1 );
        end;
        tmpS := KeyLinks.IndexValues[ii];
        if ( tmpS <> '' ) and
           Assigned( KeyDataset ) and
           KeyDataset.Prepared and
           KeyDataset.Fields.GetByName( tmpS, tmpCol ) then
          FKeyLinksFieldsMap.Add( tmpCol )
        else
          FKeyLinksFieldsMap.Add( pointer(-1) );
      end;
      for ii := 0 to KeyDescLinks.Count - 1 do
      begin
        tmpS := KeyDescLinks.IndexNames[ii];
        if Fields.GetByName( tmpS, tmpCol ) then
          KeyDescLinks.Objects[ii] := tmpCol
        else
          KeyDescLinks.Objects[ii] := pointer( -1 );
        tmpS := KeyDescLinks.IndexValues[ii];
        if ( tmpS <> '' ) and
           Assigned( KeyDataset ) and
           KeyDataset.Prepared and
           KeyDataset.Fields.GetByName( tmpS, tmpCol ) then
          FKeyDescLinksFieldsMap.Add( tmpCol )
        else
          FKeyDescLinksFieldsMap.Add( pointer(-1) );
      end;
    finally
      KeyLinks.OnChange := tmpEvent1;
      KeyDescLinks.OnChange := tmpEvent2;
    end;
    FKeyLinksMapsValid := true;
  end;
end;

procedure TIB_Dataset.CheckMasterLinksMaps;
var
  ii: integer;
  tmpS: string;
  tmpCol: TIB_Column;
  tmpEvent1: TNotifyEvent;
  tmpEvent2: TNotifyEvent;
begin
  if FMasterLinksMapsValid then Exit;
  if Assigned( FMasterLinksFieldsMap ) then
    FMasterLinksFieldsMap.Clear
  else
    FMasterLinksFieldsMap := TList.Create;
  if Assigned( FMasterParamLinksFieldsMap ) then
    FMasterParamLinksFieldsMap.Clear
  else
    FMasterParamLinksFieldsMap := TList.Create;
  if Assigned( FMasterLinksParamsMap ) then
    FMasterLinksParamsMap.Clear
  else
    FMasterLinksParamsMap := TList.Create;
  if Assigned( FMasterParamLinksParamsMap ) then
    FMasterParamLinksParamsMap.Clear
  else
    FMasterParamLinksParamsMap := TList.Create;
  if Prepared and ( not Assigned( FBindingCursor )) then
  begin
    tmpEvent1 := MasterLinks.OnChange;
    tmpEvent2 := MasterParamLinks.OnChange;
    try
      MasterLinks.OnChange := nil;
      MasterParamLinks.OnChange := nil;
      for ii := 0 to MasterLinks.Count - 1 do
      begin
        tmpS := MasterLinks.IndexNames[ ii ];
        if Fields.GetByName( tmpS, tmpCol ) then
          MasterLinks.Objects[ii] := tmpCol
        else
          MasterLinks.Objects[ii] := pointer( -1 );
        tmpS := MasterLinks.IndexValues[ ii ];
        if ( tmpS <> '' ) and
           Assigned( MasterDataset ) and
           MasterDataset.Prepared and
           MasterDataset.Fields.GetByName( tmpS, tmpCol ) then
          FMasterLinksFieldsMap.Add( tmpCol )
        else
          FMasterLinksFieldsMap.Add( pointer(-1) );
        tmpS := GetMasterLinkParamName( ii );
        if Params.GetByName( tmpS, tmpCol ) then
          FMasterLinksParamsMap.Add( tmpCol )
        else
          FMasterLinksParamsMap.Add( pointer(-1) );
      end;
      for ii := 0 to MasterParamLinks.Count - 1 do
      begin
        tmpS := MasterParamLinks.IndexNames[ii];
        if Fields.GetByName( tmpS, tmpCol ) then
          MasterParamLinks.Objects[ii] := tmpCol
        else
          MasterParamLinks.Objects[ii] := pointer( -1 );
        tmpS := MasterParamLinks.IndexValues[ii];
        if ( tmpS <> '' ) and
           Assigned( MasterDataset ) and
           MasterDataset.Prepared and
           MasterDataset.Fields.GetByName( tmpS, tmpCol ) then
          FMasterParamLinksFieldsMap.Add( tmpCol )
        else
          FMasterParamLinksFieldsMap.Add( pointer(-1) );
        tmpS := MasterParamLinks.IndexNames[ii];
        if Params.GetByName( tmpS, tmpCol ) then
          FMasterParamLinksParamsMap.Add( tmpCol )
        else
          FMasterParamLinksParamsMap.Add( pointer(-1) );
      end;
    finally
      MasterLinks.OnChange := tmpEvent1;
      MasterParamLinks.OnChange := tmpEvent2;
    end;
    FMasterLinksMapsValid := true;
  end;
end;

procedure TIB_KeyDataLink.SysPreparedChanged;
begin
  ChildDataset.InvalidateKeyLinksMaps;
  inherited SysPreparedChanged;
  if not Dataset.Prepared then
    ClearSearchCriteria( false );
end;

procedure TIB_KeyDataLink.CheckBrowseMode;
begin
  if ChildDataset.Active then
    ChildDataset.CheckBrowseMode;
  inherited CheckBrowseMode;
end;

procedure TIB_KeyDataLink.SysStateChanged;
begin
  inherited SysStateChanged;
  if ChildDataset.Prepared then
  begin
    ChildDataset.SysKeyStateChanged;
    if Assigned( Dataset ) and ( Dataset.State = dssSearch ) then
    begin
      Inc(FInternalSearchUpdate);
      try
        // if the child dataset is already active then always assign search data
        if ChildDataset.Active then
        begin
          ChildDataset.Bookmark := AdvSearchData;
        end
        else
        begin
          if AdvSearchData <> '' then
          begin
            // The assumption is that if we previously saved some keydata
            // then we want to reactivate the dataset to synchronise it.
            // (Primarily to do with lookups that deactivate the dataset
            // when it is not in use.  eg. IB_LookupEnh.)
            ChildDataset.Open;
            ChildDataset.Bookmark := AdvSearchData;
          end;
        end;
      finally
        Dec(FInternalSearchUpdate);
      end;
    end;
  end;
end;

procedure TIB_KeyDataLink.SysDataChange( AField: TIB_Column );
begin
//  if not FDataUpdating and not FDataChanging and not Disabled then begin
    Inc( FChangingLevel );
    try
      DoDataChange( AField );
    finally
      Dec( FChangingLevel );
    end;
//  end;
end;

procedure TIB_KeyDataLink.SetAdvSearchData( const AValue: string );
begin
  inherited SetAdvSearchData( AValue );
  if ChildDataset.Active and
     (not FSearchSync) and
     (ChildDataset.FPreventKeySeeking = 0) then
  begin
    Inc(FInternalSearchUpdate);
    try
      if ( ChildDataset.Bookmark <> AdvSearchData ) then
      begin
        if State = dssSearch then
          ChildDataset.Bookmark := AdvSearchData
        else
        if Prepared then
          Dataset.InvalidateSQL;
      end;
    finally
      Dec(FInternalSearchUpdate);
    end;
  end;
end;

procedure TIB_KeyDataLink.SetSearchFromKeyData;
  function GetWhereClause: string;
  var
    ii: integer;
  begin
    Result := '';
    with ChildDataset do
    begin
      if Fields.RowState <> rsNone then
      begin
        with KeyLinks do
          for ii := 0 to Count - 1 do
          begin
            if Result = '' then
              Result := '('
            else
              Result := Result + ' AND ';
            Result := Result +
{!!!  This can be optimized with the maps. !!!}            
                      GetColumnValueSQLLiteral( IndexValues[ii],
                                                FieldByName( IndexNames[ii] ));
          end;
        if Result <> '' then
        begin
          Result := Result + ')';
        end;
      end;
    end;
  end;
begin
  if ( FInternalSearchUpdate = 0 ) and ( not FSearchSync ) then
  begin
    FSearchSync := true;
    try
      if ChildDataset.Active then
      begin
        AdvSearchData := ChildDataset.Bookmark;
        AdvSearchWhere := GetWhereClause;
      end
      else
      begin
        ClearSearchCriteria( false );
      end;
    finally
      FSearchSync := false;
    end;
  end;
end;

function TIB_KeyDataLink.Modify: boolean;
var
  ii: integer;
  tmpCol: TIB_Column;
begin
  Result := true;
  Inc( FChangingLevel );
  try
    if ChildDataset.Active and ( not ReadOnly ) and
       Assigned( Dataset ) and Dataset.Prepared then
    begin
      ChildDataset.CheckKeyLinksMaps;
      for ii := 0 to ChildDataset.FKeyLinksFieldsMap.Count - 1 do
      begin
        if ChildDataset.FKeyLinksFieldsMap[ii] = pointer(-1) then
        begin
          Result := false;
          Break;
        end
        else
        begin
          tmpCol := TIB_Column( ChildDataset.FKeyLinksFieldsMap[ii] );
          if tmpCol.ControlsReadOnly then
          begin
            Result := false;
            Break;
          end;
        end;
      end;
      if Result then
        Result := inherited Modify;
    end
    else
      Result := false;
  finally
    Dec( FChangingLevel );
  end;
end;

function TIB_KeyDataLink.GetSearchEntryName: string;
begin
  if not Assigned( Dataset ) then
    Result := ''
  else
  if not Assigned( Dataset.Owner ) then
    Result := 'KLNK.' + Dataset.Name
  else
    Result := 'KLNK.' + Dataset.Owner.Name + '.' + Dataset.Name;
end;


// IBA_MasterDataLink.INT

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Jason Wharton <jwharton@jwharton.com>                                       }
{  06/13/2002                                                                  }
{     Made it so that when a master dataset is posting an insert that a        }
{     detail dataset won't cause the master to do a post retaining, post the   }
{     child dataset and then post the master. It should simply post the master }
{     dataset and leave the detail dataset in insert state.                    }
{            Copyright (C) 2002 Jason Wharton                                  }
{                                                                              }
{******************************************************************************}


procedure TIB_BDataset.SynchronizeCachedUpdates(
                                      ASyncType: TIB_SynchronizeCachedUpdates;
                                      AField: TIB_Column );
var
  tmpParam: TIB_Column;
  tmpField: TIB_Column;
  procedure MakeAssignment;
  begin
    if ( tmpParam <> pointer(-1)) then
    begin
      try
       if ( tmpField <> pointer(-1)) then
         tmpParam.Assign( tmpField )
       else
         tmpParam.Clear;
      except
        tmpParam.Clear;
      end;
    end;
  end;
var
  ii: integer;
{
  we: boolean;
  wb: boolean;
  bk1: string;
  bk2: string;
  rn1: integer;
  rn2: integer;
  ft: boolean;
}
begin
  if not Active then
    Exit;
  // Purge out the inserts for this master record that is about
  // to be deleted.  This will avoid orphaned records.
  if ( CachedInsertCount > 0 ) or
     ( CachedEditCount   > 0 ) or
     ( CachedDeleteCount > 0 ) then
  begin
    DisableControls;
    try
{
      bk1 := Bookmark;
      rn1 := RowNum;
      ft := Filtered;
      Filtered := false;
      bk2 := Bookmark;
      rn2 := RowNum;
      try
}
        First;
        while not Eof do
        begin

          if ( ASyncType = scuMasterDeleted ) then
          begin
            if rfInserted in Fields.RowNode.RowFlags then
            begin
              RevertRecord;
              Continue;
            end
            else
            if ( rfEdited  in Fields.RowNode.RowFlags ) or
               ( rfDeleted in Fields.RowNode.RowFlags ) then
              RevertRecord;
          end
          else
          if ( ASyncType = scuMasterDataChanged ) then
          begin
            if ( rfInserted in Fields.RowNode.RowFlags ) or
               ( rfEdited   in Fields.RowNode.RowFlags ) or
               ( rfDeleted  in Fields.RowNode.RowFlags ) then
            begin
              for ii := 0 to MasterLinks.Count - 1 do
              begin
                tmpParam := TIB_Column( MasterLinks.Objects[ii] );
                tmpField := TIB_Column( FMasterLinksFieldsMap[ii] );
                if Assigned( AField ) and ( tmpField = AField ) then
                begin
                  MakeAssignment;
                  Break;
                end;
              end;
              if NeedToPost then
                Post;
            end;
          end;
          Next;
        end;
{
        Bookmark := bk2;
        if Bookmark <> bk2 then
        begin
          RowNum := rn2;
          if Eof then
            RowNum := EofRowNum - 1;
          bk2 := Bookmark;
        end;
      finally
        Filtered := ft;
        if Bookmark <> bk1 then
        begin
          Bookmark := bk1;
          if ( Bookmark <> bk1 ) then
          begin
            Bookmark := bk2;
            if Bookmark <> bk2 then
              RowNum := rn1;
          end;
        end;
      end;
}
    finally
      EnableControls;
      if not Active then
        Open;
    end;
  end;

end;

procedure TIB_MasterDataLink.CheckBrowseMode;
var
  NeedPurge: boolean;
begin
  if ChildDataset.Active then
  begin
    if FCheckBrowseModeLevel < 2 then
    begin
      Inc( FCheckBrowseModeLevel );
      try
        if not (( Dataset.State = dssInsert ) and
                ( Dataset.IsPosting ) and
                ( ChildDataset.State = dssInsert )) then
          ChildDataset.CheckBrowseMode;

        NeedPurge := ( Dataset.State = dssDelete ) and
                     ( Dataset.IsPosting );
        if not NeedPurge and ( Dataset is TIB_BDataset ) then
          with Dataset as TIB_BDataset do
            NeedPurge := ( Assigned( Fields.RowNode )) and
                         ( rfInserted in Fields.RowNode.RowFlags ) and
                         ( RevertingRecord );
        if NeedPurge and ( ChildDataset is TIB_BDataset ) then
          with ChildDataset as TIB_BDataset do
            SynchronizeCachedUpdates( scuMasterDeleted, nil );
      finally
        Dec( FCheckBrowseModeLevel );
      end;
    end;
  end;
end;

procedure TIB_MasterDataLink.DoAfterAssignment;
var
  tmpIdx: integer;
begin
  with ChildDataset do
  begin
    InvalidateMasterLinksMaps;
    if Assigned( MasterDataset ) and Assigned( DataSource ) then
    begin
      with MasterDataset.FDataSourceList do
      begin
        tmpIdx := IndexOf( DataSource );
        if tmpIdx > 0 then
          Move( tmpIdx, 0 );
      end;
      with DataSource.FDataLinkList do
      begin
        tmpIdx := IndexOf( Self );
        if tmpIdx > 0 then
          Move( tmpIdx, 0 );
      end;
    end;
    SysMasterDataChange( nil );
  end;
  inherited DoAfterAssignment;
end;

procedure TIB_MasterDataLink.SysPreparedChanged;
begin
  ChildDataset.InvalidateMasterLinksMaps;
  inherited SysPreparedChanged;
end;

procedure TIB_MasterDataLink.SysPrepareSQL;
var
  DetailSQLWhere: string;
  ii, kk: integer;
begin
  with ChildDataset do
  begin
    if MasterSearch and Prepared and SQLWhereChanged then
    begin
      DetailSQLWhere := 'WHERE ';
      kk := 0;
      for ii := 0 to MasterLinks.Count - 1 do
      begin
        if kk > 0 then
          DetailSQLWhere := DetailSQLWhere + IBO_AND;
        DetailSQLWhere := DetailSQLWhere + MasterLinks[ ii ];
        Inc( kk );
      end;
      for ii := 0 to JoinLinks.Count - 1 do
      begin
        if kk > 0 then
          DetailSQLWhere := DetailSQLWhere + IBO_AND;
        DetailSQLWhere := DetailSQLWhere + JoinLinks[ ii ];
        Inc( kk );
      end;
      if kk > 0 then
      begin
        AddWhereClause( DetailSQLWhere, FMasterWhere );
        MasterDataset.SQLWhereLow.Add( 'EXISTS ( '#13#10#13#10 + 'SELECT * ' +
                                       Trim( SQLFrom.Text ) + #13#10 +
                                       Trim( DetailSQLWhere ) +
                                       ')'#13#10#13#10 );
        MasterDataset.ParamValueLinks.AddStrings( ParamValueLinks );
      end;
    end;
  end;
end;

procedure TIB_MasterDataLink.SysStateChanged;
begin
  inherited SysStateChanged;
  if Assigned( ChildDataset ) then 
    with ChildDataset do
      SysMasterStateChanged;
end;


// IBA_Row.INT

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Marco Krause <mkrause@hellmut-springer.de> - 19.05.2002                     }
{    Improved performance in GetByName - It is important, that calls to				 }
{		 FieldByName() comes in the same order, the columns are typed in the sql   }
{		 statement.                                                                }
{                                                                              }
{  Jason Wharton - 9 Sep 2001                                                  }
{    I made it so that the int64 based numerics use the extended data type to  }
{    store and retrieve their values into variants.                            }
{                                                                              }
{    I also made it so that you can use the '*' character in the variant       }
{    values array to indicate all fields in the row.                           }
{                                                                              }
{  Wassim Haddad - 10 Aug 2001                                                 }
{    Made modifications to control the firing of OnCalculateField depending on }
{    the DataSet's CalculateAllFields property.                                }
{                                                                              }
{******************************************************************************}

constructor TIB_Row.Create( AStatement: TIB_Statement;
                            DescType: TIB_RowType );
begin
  inherited Create;
  FStatement := AStatement;
  FRowType := DescType;
  FColumnList := TList.Create;
  FBlobList := TList.Create;
  FArrayList := TList.Create;
  FCalcList := TList.Create;
  FUpdatedColumns := TList.Create;
  FRelationList := TIB_StringList.Create;
  FRelationList.Sorted := true;
  FRelationList.Duplicates := dupIgnore;
  case DescType of
    rtParam: FRowState := rsUnmodified;
    else FRowState := rsNone;
  end;
  FOldBuffer := nil;
  FRowBuffer := nil;
  FPSQLDA := nil;
  FSysBlobHead := nil;
  lastColumnIndex := -1;
end;

destructor TIB_Row.Destroy;
begin
  FreeBlobRefList( @FSysBlobHead );
  FreeVarList;
  inherited Destroy;
  FRelationList.Free;
  FRelationList := nil;
  FColumnList.Free;
  FColumnList := nil;
  FBlobList.Free;
  FBlobList := nil;
  FCalcList.Free;
  FCalcList := nil;
  FArrayList.Free;
  FArrayList := nil;
  FUpdatedColumns.Free;
  FUpdatedColumns := nil;
end;

procedure TIB_Row.FreeVARList;
var
  tmpCol: TIB_Column;
  ii: integer;
  tmpBlobNode: PIB_BlobNode;
begin

// Free up all the data associated with the permanent BlobNodes.
  if Assigned( FPSQLDA ) then
    for ii := 0 to PSQLDA.sqln - 1 do
    begin
      tmpBlobNode := GetBlobNode( ii );
      if Assigned( tmpBlobNode ) then
        FreeBlobNodeData( tmpBlobNode );
    end;
  FreeMem( FBlobNodeList );
  FBlobNodeList := nil;
  FreeMem( FBlobFieldMap );
  FBlobFieldMap := nil;

// Free up all of the TIBOColumn instances.
  while FColumnList.Count > 0 do
  begin
    tmpCol := Columns[ 0 ];
    FColumnList.Remove( tmpCol );
    tmpCol.Free;
  end;
  FColumnCount := 0;
  FSysColumnCount := 0;
  FBlobCount := 0;
  FArrayCount := 0;
  FBufferLength := 0;
  FRelationList.Clear;
  FColumnList.Clear;
  FCalcList.Clear;
  FBlobList.Clear;
  FArrayList.Clear;
  try
    if Assigned( FPSQLDA ) then
      FreeMem( FPSQLDA );
    FPSQLDA := nil;
  except
  end;
  try
    if Assigned( FRowBuffer ) then
      FreeMem( FRowBuffer );
    FRowBuffer := nil;
  except
  end;
  try
    if Assigned( FOldBuffer ) then
      FreeMem( FOldBuffer );
    FOldBuffer := nil;
  except
  end;
end;

procedure TIB_Row.SysUpdate( NewColumnCount: word );
begin
  FreeVarList;
  try
    FCalcCount := 0;
    if ( NewColumnCount > 0 ) and
       ( RowType = rtField ) and
       (( not IsKeyFields ) or IsKeyFieldsWhole ) then
      FCalcCount := Statement.CalculatedFields.Count;
    FPSQLDA := AllocMem( XSQLDA_LENGTH( NewColumnCount + FCalcCount));
    FPSQLDA.version := SQLDA_VERSION1;
    FPSQLDA.sqln := NewColumnCount + FCalcCount;
    FPSQLDA.sqld := NewColumnCount;
    if NewColumnCount > 0 then
    begin
      Statement.SysDescribeVARList( Self );
      if RowType = rtParam then
        PrepDuplicateParamList;
      AllocateBlobNodes;
      CreateVARList;
      FBDENamesValid := false;
      AllocateVARList;
      UpdateBufferPointers;
      SysClearBuffers;
    end;
  except
    FreeVarList;
    raise;
  end;
  if ( RowType <> rtParam ) and ( RowState <> rsNone ) then
    SetRowState( rsNone )
  else
  if ( RowType = rtParam ) and ( RowState <> rsUnmodified ) then
    SetRowState( rsUnmodified );
end;

procedure TIB_Row.AllocateBlobNodes;
var
  ii: integer;
  tmpInt: integer;
begin
  FBlobCount := 0;
  FArrayCount := 0;
  for ii := 0 to PSQLDA.SQLn - 1 do
    case PSQLDA.SQLVAR[ ii ].SQLType of
      SQL_BLOB,
      SQL_BLOB_: Inc( FBlobCount );
      SQL_ARRAY,
      SQL_ARRAY_: Inc( FArrayCount );
    end;
  FBlobNodeList := AllocMem(( FBlobCount +
                              FArrayCount ) * SizeOf( TIB_BlobNode ));
  FBlobFieldMap := AllocMem( SizeOf( integer ) * PSQLDA.SQLn );
  tmpInt := 0;
  for ii := 0 to PSQLDA.SQLn - 1 do
    case PSQLDA.SQLVAR[ ii ].SQLType of
      SQL_BLOB,
      SQL_BLOB_,
      SQL_ARRAY,
      SQL_ARRAY_:
      begin
        FBlobFieldMap.BlobIndex[ ii ] :=  tmpInt;
        Inc( tmpInt );
      end
      else
        FBlobFieldMap.BlobIndex[ ii ] :=  -1;
    end;
end;

procedure TIB_Row.NameParams;
var
  ii: integer;
  RelPos: integer;
  ARelName,
  AColName: string;
  tmpStr: string;
begin
  if RowType = rtParam then
  begin
    for ii := 0 to PSQLDA.sqld - 1 do
    with PSQLDA.SQLVAR[ ii ] do
    begin
      FillChar( relname, 32, #0 );
      relname_length := -1;
      FillChar( sqlname, 32, #0 );
      sqlname_length := -1;
      FillChar( aliasname, 32, #0 );
      aliasname_length := 0;
      if ii < Statement.FSysParamNames.Count then
        AColName := Trim( Statement.FSysParamNames[ ii ] )
      else
        if Statement.FUnExPrmCnt > 0 then
          try
            AColName := Statement.FSysParamNames[ ii - Statement.FUnExPrmCnt ];
            AColName := Trim( AColName );
          except
            AColName := IBO_PARAMETER + '_' + IntToStr( ii );
          end
        else
          AColName := IBO_PARAMETER + '_' + IntToStr( ii );
      RelPos := getLitSafePos( '.', AColName, 1 );
      if RelPos > 0 then
      begin
        ARelName := Trim( Copy( AColName, 1, RelPos - 1 ));
        AColName := Trim( Copy( AColName, RelPos + 1, maxint ));
        if ( Statement.SQLDialect < 3 ) or
           ( Pos( '"', ARelName ) = 0 ) then
          ARelName := AnsiUpperCase( ARelName );
        ARelName := stLitCriteria( ARelName );
        relname_length := Length( ARelName );
        if relname_length > 32 then
          relname_length := 32;
        Move( Pchar(ARelName)^, relname, relname_length );
      end;
      tmpStr := UpperCase( Copy( AColName, 1, 5 ));
      if tmpStr = 'OLD_"' then
        AColName := '"OLD_' + Copy( AColName, 6, MaxInt )
      else
      if tmpStr = 'OLD."' then
        AColName := '"OLD.' + Copy( AColName, 6, MaxInt )
      else
      if ( Statement.SQLDialect < 3 ) or
         ( Pos( '"', AColName ) = 0 ) then
        AColName := AnsiUpperCase( AColName );
      AColName := stLitCriteria( AColName );
      if CompareText( IBO_DB_KEY, AColName ) = 0 then
        AColName := IBO_RDB + IBO_DB_KEY;
      aliasname_length := Length( AColName );
      if aliasname_length > 32 then
        aliasname_length := 32;
      Move( Pchar(AColName)^, aliasname, aliasname_length );
    end;
  end;
end;

procedure TIB_Row.CreateVARList;
var
  tmpCol: TIB_Column;
  procedure DoCreateVAR( const ANewIndex: smallint;
                         const PSQLVAR: PXSQLVAR );
  var
    tmpStr: string;
  begin
    Inc( FColumnCount );
    tmpCol := nil;
    if Assigned( Statement.FOnCreateColumn ) then
      Statement.FOnCreateColumn( Statement,
                                 Self,
                                 ANewIndex,
                                 PSQLVAR,
                                 tmpCol )
    else
      DefaultColumnCreation( ANewIndex,
                             PSQLVAR,
                             tmpCol );
    if Assigned( tmpCol ) then
    begin
      tmpStr := stLitCriteria( tmpCol.FieldName );
      if ( RowType = rtParam ) and
         (( Pos( IBO_MASTERLINK, tmpStr ) = 1 ) or
          ( Pos( IBO_QBE_PARAMETER, tmpStr ) = 1 )) then
      begin
        FColumnList.Insert( FColumnCount - 1, tmpCol );
        Inc( FSysColumnCount );
      end
      else
        FColumnList.Insert( FColumnCount - 1 - FSysColumnCount, tmpCol );
    end
    else
      raise EIB_Error.CreateWithSender( Statement,
                                        E_Must_create_a_TIB_Column_instance );
    if ANewIndex < 0 then
      FCalcList.Add( tmpCol );
    case PSQLVAR.SQLType of
      SQL_ARRAY,
      SQL_ARRAY_: FArrayList.Add( tmpCol );
      SQL_BLOB,
      SQL_BLOB_: FBlobList.Add( tmpCol );
    end;
    tmpCol := nil;
  end;
var
  tmpS: string;
  ii, jj: integer;
begin
{ Create the IB_Column instances for fields and parameters. }
  tmpCol := nil;
  for ii := 0 to FPSQLDA.sqld - 1 do
  begin
    if Statement.FCombineDuplicateParams and
       ( RowType = rtParam ) then
    begin
      tmpS := Copy( FPSQLDA.sqlvar[ii].relname, 1,
                    FPSQLDA.sqlvar[ii].relname_length );
      if tmpS <> 'OLD' then
      begin
        if tmpS <> '' then
          tmpS := Statement.IB_Connection.mkVarIdent( tmpS ) + '.';
        tmpS := tmpS + Statement.IB_Connection.mkVarIdent(
                         Copy( FPSQLDA.sqlvar[ii].aliasname, 1,
                               FPSQLDA.sqlvar[ii].aliasname_length ));
        lastColumnIndex := -1;
        if GetByName( tmpS, tmpCol ) and
           SQLTypesEqual( tmpCol.PSQLVAR.SQLType,
                          PSQLDA.sqlvar[ii].SQLType ) then
          Continue;
      end;
    end;
    DoCreateVAR( ii, @FPSQLDA.sqlvar[ii] );
  end;
  for ii := FPSQLDA.sqld to FPSQLDA.sqln - 1 do
  begin
    jj := ii - FPSQLDA.sqld;
    SetXSQLVAR( Statement.CalculatedFields[jj],
                Statement.SQLDialect,
                FPSQLDA.sqlvar[ii] );
    DoCreateVAR( -jj - 1, @FPSQLDA.sqlvar[ii] );
  end;
  
  { Maintain a list of relations joined in the query }
  FRelationList.Clear;
  for ii := 0 to FColumnList.Count - 1 do
    with Columns[ ii ] do
      if RelAliasName <> '' then
        FRelationList.Add( RelAliasName );
end;

procedure TIB_Row.FillRelAliasInfo;
var
  InfoBuffer: array[0..32760] of char;
  Ptr: integer;
  Index: integer;
  Item: PChar;
  tmp: array[0..4] of char;
  rn: string;
  function API_DSQL_SQL_INFO( var Items, Buffer: array of char ): boolean;
  begin
    if Statement.PstHandle^ = FakePointer then
      Result := false
    else
    with Statement.IB_Session do
    begin
      errcode := isc_dsql_sql_info( @status,
                                    Statement.PstHandle,
                                    SizeOf( Items ),
                                    Items,
                                    SizeOf( Buffer ),
                                    @Buffer );
      Result := errcode = 0;
    end;
  end;
  function GetNumeric: Integer;
  var
    K: Integer;
  begin
    K := isc_vax_integer( @InfoBuffer[ Ptr ], 2 );
    Inc( Ptr, 2 );
    Result := isc_vax_integer( @InfoBuffer[ Ptr ], K );
    Inc( Ptr, K );
  end;

  function GetString( DestBuffer: PChar;
                      DestLength: integer ): integer;
  var
    tmpItem: PChar;
  begin
    FillChar( DestBuffer[0], DestLength, 0 );
    tmpItem := @InfoBuffer[ Ptr ];
    Result := isc_vax_integer( tmpItem, 2 );
    if Result >= DestLength then
      Result := DestLength - 1;
    Move( InfoBuffer[ Ptr + 2 ], DestBuffer[0], Result );
    Inc( Ptr, Result + 2 );
  end;
var
  ii: integer;
//  tmpCol: TIB_Column;
  sql_rel_alias: TIB_Identifier_V2;
begin
  if RowType = rtField then
  begin
    tmp[0]:= Char(isc_info_sql_select);
    tmp[1]:= Char(isc_info_sql_describe_vars);
    tmp[2]:= Char(isc_info_sql_sqlda_seq);
    tmp[3]:= Char(isc_info_sql_relation_alias);
    tmp[4]:= Char(isc_info_sql_describe_end);
    if ( Statement.IB_Connection.Characteristics.dbFBVersion = '' ) or
       ( Statement.IB_Connection.Characteristics.dbServer_Major_Version < 2 ) or
       ( not ( API_DSQL_SQL_INFO( tmp, InfoBuffer ))) then
    begin
      for index := 0 to Pred( ColumnCount ) do
      begin
        rn := Columns[index].RelName;
        // This is potentially ambiguous!!!
        // Should examine the source SQL statement for precision.
        if Length( rn ) > 0 then
          Columns[index].FRelAliasName := Statement.GetRelAliasByRelName( rn )
        else
          Columns[index].FRelAliasName := '';
      end;
    end
    else
    begin
    // Firebird 2 end higher
      if ( InfoBuffer[0] <> Char( isc_info_sql_select        )) or
         ( InfoBuffer[1] <> Char( isc_info_sql_describe_vars )) then
        Exit;
      Ptr := 2;
      GetNumeric;
      index := 0;
      while InfoBuffer[ Ptr ] <> Char( isc_info_end ) do
      begin
        item := @InfoBuffer[ Ptr ];
        if item[0] = Char( isc_info_sql_describe_end ) then
          Inc( Ptr, 1 )
        else
          while item[0] <> Char( isc_info_sql_describe_end ) do
          begin
            Inc( Ptr, 1 );
            case Byte( item[0] ) of
              isc_info_sql_sqlda_seq:
                index := GetNumeric - 1;
              isc_info_sql_relation_alias:
              begin
                GetString( @sql_rel_alias[0], SizeOf( sql_rel_alias ));
                if Assigned( Columns[index] ) then
                  Columns[index].FRelAliasName := sql_rel_alias;
              end;
            end;
            item := @InfoBuffer[ Ptr ];
          end;
      end;
    end;

    { Maintain a list of relations joined in the query }
    FRelationList.Clear;
    for ii := 0 to FColumnList.Count - 1 do
    begin
      with Columns[ ii ] do
      begin
        if RelAliasName <> '' then
          FRelationList.Add( RelAliasName )
        else
        if RelName <> '' then
        begin
          FRelationList.Add( RelName );
        end;
      end;
    end;
{
  end
  else
  if RowType = rtKey then
  begin
    for ii := 0 to ColumnCount - 1 do
    begin
      if Statement.Fields.GetByName( Columns[ii].FullFieldName, tmpCol ) then
        Columns[ii].FRelAliasName := tmpCol.FRelAliasName;
    end;
}
  end;
end;

procedure TIB_Row.NameVARListForBDE;
var
  ii, jj: integer;
  AName: string;
  ADBFName: string;
  ACnt: integer;
  ADBFCnt: integer;
  Len: integer;
  tmpStr: string;
begin
  for ii := 0 to ColumnCount - 1 do
  begin
    tmpStr := stLitCriteria( Columns[ ii ].FieldName );
    if tmpStr = '' then
      tmpStr := 'COLUMN' + IntToStr( ii );
    Columns[ ii ].FBDEFieldName := tmpStr;
    Columns[ ii ].FDBFFieldName := Copy( tmpStr, 1, 10 );
  end;
  for ii := 0 to ColumnCount - 2 do
  begin
    AName := Columns[ ii ].FBDEFieldName;
    ADBFName := Columns[ ii ].FDBFFieldName;
    ACnt := 1;
    ADBFCnt := 1;
    for jj := ii + 1 to ColumnCount - 1 do
      with Columns[ jj ] do
      begin
        if CompareText( AName, FBDEFieldName ) = 0 then
        begin
          FBDEFieldName := FieldName + '_' + IntToStr( ACnt );
          Len := Length( FBDEFieldName );
          if Len > 31 then
            System.Delete( FBDEFieldName,
                           31 - Length( IntToStr( ACnt )),
                           31 - Len );
          Inc( ACnt );
        end;
        if CompareText( AName, FDBFFieldName ) = 0 then
        begin
          FDBFFieldName := FieldName + '_' + IntToStr( ADBFCnt );
          Len := Length( FDBFFieldName );
          if Len > 10 then
            System.Delete( FDBFFieldName,
                           10 - Length( IntToStr( ADBFCnt )),
                           10 - Len );
          Inc( ADBFCnt );
        end;
      end;
  end;
  FBDENamesValid := true;
end;

procedure TIB_Row.PrepDuplicateParamList;
var
  ii, jj: integer;
begin
// Cause parameters of the same name and type to be coersed into the
// largest of the duplicate entries. This way when they are bound to the same
// point in memory there will be compatible references to the column buffer
// for all members.
// Smallints will cast up to integers if they are duplicate too.
  if Statement.FCombineDuplicateParams and ( RowType = rtParam ) then
    with PSQLDA^ do
      for ii := 0 to SQLd - 2 do
        for jj := ii + 1 to SQLd - 1 do
          if ( CompareText( SQLVAR[ ii ].relname,
                            SQLVAR[ jj ].relname ) = 0 ) and
             ( CompareText( SQLVAR[ ii ].aliasname,
                            SQLVAR[ jj ].aliasname ) = 0 ) then
            if SQLTypesEqual( PSQLDA.SQLVAR[ ii ].SqlType,
                              PSQLDA.SQLVAR[ jj ].SqlType ) then
              if SQLVAR[ jj ].SqlLen > SQLVAR[ ii ].SqlLen then
                SQLVAR[ ii ] := SQLVAR[ jj ]
              else
              if SQLVAR[ ii ].SqlLen > SQLVAR[ jj ].SqlLen then
                SQLVAR[ jj ] := SQLVAR[ ii ];
end;

procedure TIB_Row.AllocateVARList;
var
  ii: integer;
begin
  FBufferLength := 0;
  for ii := 0 to ColumnCount - 1 do
    Inc( FBufferLength, SizeOf( smallint ) + Columns[ ii ].DataSize );
  if BufferLength > 0 then
  begin
    FOldBuffer := AllocMem( FBufferLength );
    FRowBuffer := AllocMem( FBufferLength );
  end;
end;

procedure TIB_Row.UpdateBufferPointers;
var
  ii: integer;
  tempOld: pointer;
  tempNew: pointer;
  tmpCol: TIB_Column;
  procedure SetPointers;
  begin
    with tmpCol do
    begin
      FOldColumnInd := tempOld;
      FNewColumnInd := tempNew;
      FPXSQLVAR.SqlInd := tempNew;
      Inc( longint(tempOld), SizeOf( smallint ));
      Inc( longint(tempNew), SizeOf( smallint ));
      FOldColumnBuffer := tempOld;
      FNewColumnBuffer := tempNew;
      FPXSQLVAR.SqlData := tempNew;
      Inc( longint(tempOld), DataSize );
      Inc( longint(tempNew), DataSize );
    end;
  end;
begin
  tempOld := FOldBuffer;
  tempNew := FRowBuffer;
  if Assigned( FPSQLDA ) then
  begin
    for ii := 0 to FPSQLDA.sqld - 1 do
      if GetBySQLNo( ii, tmpCol ) then
        SetPointers;
    for ii := 0 to FCalcList.Count - 1 do
    begin
      tmpCol := FCalcList.Items[ ii ];
      SetPointers;
    end;
    SetDuplicateBufferPointers;
  end;
end;

procedure TIB_Row.SetDuplicateBufferPointers;
var
  ii: integer;
  tmpCol: TIB_Column;
  tmpS: string;
begin
// This makes duplicate parameters of same data size bind to the same place.
  if Statement.FCombineDuplicateParams and ( RowType = rtParam ) then
    for ii := 0 to FPSQLDA.sqld - 1 do
    begin
      tmpS := Copy( FPSQLDA.sqlvar[ii].relname, 1,
                    FPSQLDA.sqlvar[ii].relname_length );
      if tmpS <> '' then
        tmpS := Statement.IB_Connection.mkVarIdent( tmpS ) + '.';
      tmpS := tmpS + Statement.IB_Connection.mkVarIdent(
                       Copy( FPSQLDA.sqlvar[ii].aliasname, 1,
                             FPSQLDA.sqlvar[ii].aliasname_length ));
      lastColumnIndex := -1;
      if GetByName( tmpS, tmpCol ) then
        if Addr( tmpCol.PSQLVAR^ ) <> Addr( PSQLDA.sqlvar[ii] ) then
          if SQLTypesEqual( tmpCol.PSQLVAR.SQLType,
                            PSQLDA.sqlvar[ii].sqltype ) then
          begin
            FPSQLDA.sqlvar[ii].SQLInd := tmpCol.FPXSQLVAR.SQLInd;
            FPSQLDA.sqlvar[ii].SQLData := tmpCol.FPXSQLVAR.SQLData;
          end;
    end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Row.DefaultColumnCreation( const NewIndex: smallint;
                                         const PSQLVAR: PXSQLVAR;
                                         var NewIB_Column: TIB_Column );
begin
  with PSQLVAR^ do
  begin
    if ( SQLScale <> 0 ) and (( SQLType = SQL_DOUBLE  ) or
                              ( SQLType = SQL_DOUBLE_ ) or
                              ( SQLType = SQL_SHORT   ) or
                              ( SQLType = SQL_SHORT_  ) or
                              ( SQLType = SQL_LONG    ) or
                              ( SQLType = SQL_LONG_   ) or
                              ( SQLType = SQL_QUAD    ) or
                              ( SQLType = SQL_QUAD_   ) or
                              ( SQLType = SQL_INT64   ) or
                              ( SQLType = SQL_INT64_  )) then
      NewIB_Column := TIB_ColumnNumeric.Create( Self, PSQLVAR, NewIndex )
    else
    case SQLtype of
      SQL_BOOLEAN,
      SQL_BOOLEAN_:
        NewIB_Column := TIB_ColumnBoolean.Create( Self, PSQLVAR, NewIndex );
      SQL_FLOAT,
      SQL_FLOAT_:
        NewIB_Column := TIB_ColumnFloat.Create( Self, PSQLVAR, NewIndex );
      SQL_DOUBLE,
      SQL_DOUBLE_:
        NewIB_Column := TIB_ColumnDouble.Create( Self, PSQLVAR, NewIndex );
      SQL_SHORT,
      SQL_SHORT_:
        NewIB_Column := TIB_ColumnSmallInt.Create( Self, PSQLVAR, NewIndex );
      SQL_LONG,
      SQL_LONG_:
        NewIB_Column := TIB_ColumnInteger.Create( Self, PSQLVAR, NewIndex );
      SQL_QUAD,
      SQL_QUAD_:
        NewIB_Column := TIB_ColumnQuad.Create( Self, PSQLVAR, NewIndex );
      SQL_INT64,
      SQL_INT64_:
        NewIB_Column := TIB_ColumnInt64.Create( Self, PSQLVAR, NewIndex );
      SQL_TYPE_DATE,
      SQL_TYPE_DATE_:
        NewIB_Column := TIB_ColumnDate.Create( Self, PSQLVAR, NewIndex );
      SQL_TYPE_TIME,
      SQL_TYPE_TIME_:
        NewIB_Column := TIB_ColumnTime.Create( Self, PSQLVAR, NewIndex );
      SQL_TIMESTAMP,
      SQL_TIMESTAMP_:
        NewIB_Column := TIB_ColumnTimeStamp.Create( Self, PSQLVAR, NewIndex );
      SQL_TEXT,
      SQL_TEXT_:
        if ( SQLName = IBO_DB_KEY ) or
           ( SQLName = IBO_RDB + IBO_DB_KEY ) or
           ((( AliasName = IBO_DB_KEY ) or
             ( AliasName = IBO_RDB + IBO_DB_KEY )) and
            (( SQLLen mod 8 ) = 0 )) then
          NewIB_Column := TIB_ColumnDB_KEY.Create( Self, PSQLVAR, NewIndex )
        else
          NewIB_Column := TIB_ColumnText.Create( Self, PSQLVAR, NewIndex );
      SQL_VARYING,
      SQL_VARYING_: NewIB_Column := TIB_ColumnVarText.Create( Self,
                                                              PSQLVAR,
                                                              NewIndex );
      SQL_BLOB,
      SQL_BLOB_:
      begin
        if sqlsubtype = ISC_BLOB_TEXT then
          NewIB_Column := TIB_ColumnMemo.Create( Self, PSQLVAR, NewIndex )
        else
        if sqlsubtype = ISC_BLOB_BLR then
          NewIB_Column := TIB_ColumnBLR.Create( Self, PSQLVAR, NewIndex )
        else
          NewIB_Column := TIB_ColumnBinary.Create( Self, PSQLVAR, NewIndex );
      end;
      SQL_ARRAY,
      SQL_ARRAY_: NewIB_Column := TIB_ColumnArray.Create( Self,
                                                          PSQLVAR,
                                                          NewIndex );
      else
        raise EIB_StatementError.CreateWithSender( Statement,
                                                   Format( E_UNSUPPORTED_COLUMN,
                                                   [ SQLType ] ));
    end;
  end;
end;

{------------------------------------------------------------------------------}

function TIB_Row.GetBySQLNo(     ASQLNo: smallint;
                             var AColumn: TIB_Column ): boolean;
var
  ii: integer;
begin
  AColumn := nil;
  Result := false;
  for ii := 0 to ColumnCount - 1 do
  begin
    if ASQLNo = Columns[ ii ].FieldNo then
    begin
      AColumn := Columns[ ii ];
      Result := true;
      Break;
    end;
  end;
end;

function TIB_Row.BySQLNo( ASQLNo: smallint ): TIB_Column;
begin
  if not GetBySQLNo( ASQLNo, Result ) then
    raise EIB_Error.CreateWithSender( Statement,
                                      Format( E_FieldNo_NOT_FOUND, [ASQLNo] ));
end;

function TIB_Row.GetByName(     AFieldName: string;
                            var AIB_Field: TIB_Column ): boolean;
var
  ii: integer;
  RelPos: integer;
  ARelName: string;
  AColName: string;
  AnsiStrCmp: function ( const S1, S2: string ): integer;
  tmpRelName: string;
  tmpFldName: string;
  lookupColumn : TIB_Column;
begin
  AIB_Field := nil;
  Result := false;
  if ColumnCount = 0 then Exit;
  AFieldName := Trim( AFieldName );
  if AFieldName = '' then Exit;
  if ( Statement.SQLDialect < 3 ) then AnsiStrCmp := AnsiCompareText
                                  else AnsiStrCmp := AnsiCompareStr;
  RelPos := getLitSafePos( '.', AFieldName, 1 );
  if lastColumnIndex <> -1 then
    lookupColumn := GetColumns(lastColumnIndex+1)
  else
    lookupColumn := nil;
  if RelPos > 0 then
  begin
    tmpRelName := Copy( AFieldName, 1, RelPos - 1 );
    tmpFldName := Copy( AFieldName, RelPos + 1, MaxInt );
    ARelName := tmpRelName;
    AColName := tmpFldName;
    if isLitCriteria( ARelName, '"' ) then    {!!v4}
      ARelName := Statement.IB_Connection.mkVarIdent( stLitCriteria( ARelName ))
    else
      ARelName := AnsiUpperCase( ARelName );
    if isLitCriteria( AColName, '"' ) then
      AColName := Statement.IB_Connection.mkVarIdent( stLitCriteria( AColName ))
    else
      AColName := AnsiUpperCase( AColName );
    if ARelName = '""' then
      ARelName := '';
    if AColName = '""' then
      AColName := '';
    if ( lookupColumn <> nil ) and
       ( ( AnsiStrCmp( ARelName, lookupColumn.RelName      ) = 0 ) or
         ( AnsiStrCmp( ARelName, lookupColumn.RelAliasName ) = 0 ) ) and
       ( ( AnsiStrCmp( AColName, lookupColumn.FieldName    ) = 0 ) ) then
    begin
      AIB_Field := lookupColumn;
      Inc(lastColumnIndex);
      Result := true;
    end
    else
    begin
      for ii := 0 to FColumnList.Count - 1 do
      begin
        if ( ( AnsiStrCmp( ARelName, Columns[ ii ].RelName      ) = 0 ) or
             ( AnsiStrCmp( ARelName, Columns[ ii ].RelAliasName ) = 0 ) ) and
           ( ( AnsiStrCmp( AColName, Columns[ ii ].FieldName    ) = 0 ) ) then
        begin
          AIB_Field := Columns[ ii ];
          lastColumnIndex := ii;
          Result := true;
          Break;
        end;
      end;
    end;
    if not Result then
    begin
      if ( lookupColumn <> nil ) and
         ( ( AnsiStrCmp( ARelName, lookupColumn.RelName      ) = 0 ) or
           ( AnsiStrCmp( ARelName, lookupColumn.RelAliasName ) = 0 ) ) and
         ( ( AnsiStrCmp( AColName, lookupColumn.SQLName      ) = 0 ) ) then
      begin
        AIB_Field := lookupColumn;
        Inc(lastColumnIndex);
        Result := true;
      end
      else
      begin
        for ii := 0 to FColumnList.Count - 1 do
        begin
          if ( ( AnsiStrCmp( ARelName, Columns[ ii ].RelName      ) = 0 ) or
               ( AnsiStrCmp( ARelName, Columns[ ii ].RelAliasName ) = 0 ) ) and
             ( ( AnsiStrCmp( AColName, Columns[ ii ].SQLName      ) = 0 ) ) then
          begin
            AIB_Field := Columns[ ii ];
            lastColumnIndex := ii;
            Result := true;
            Break;
          end;
        end;
      end;
    end;
    // This is for the case where a master-detail may have a master
    // column referred to where there is an aggregate in the query
    // and so the RelName of the master column being referred to is left
    // blank.  But, if in the detail query the MasterLinks has an entry
    // that includes the master table name it was failing to have them
    // establish a reference.
    if not Result then
    begin
      if ( lookupColumn <> nil ) and
         ( lookupColumn.RelName = '' ) and
         ( AnsiStrCmp( AColName, lookupColumn.FieldName ) = 0 ) then
      begin
        AIB_Field := lookupColumn;
        Inc(lastColumnIndex);
        Result := true;
      end
      else
      begin
        for ii := 0 to FColumnList.Count - 1 do
        begin
          if ( Columns[ ii ].RelName = '' ) and
             ( AnsiStrCmp( AColName, Columns[ ii ].FieldName ) = 0 ) then
          begin
            AIB_Field := Columns[ ii ];
            lastColumnIndex := ii;
            Result := true;
            Break;
          end;
        end;
      end;
    end;

    // Because Borland didn't do a good job with handling field names
    // in regard to treating the double-quote character as having any
    // significance on the name of the identifier I now have to assume it is
    // possible to receive in an indentifier a quoted type identifier without
    // the quotes being on it.
    if ( not Result ) and ( (not isLitCriteria( tmpRelName, '"' )) or
                            (not isLitCriteria( tmpFldName, '"' ))) then
    begin
      if not isLitCriteria( tmpRelName, '"' ) then
        tmpRelName := mkLitCriteria( tmpRelName, '"' );
      if not isLitCriteria( tmpFldName, '"' ) then
        tmpFldName := mkLitCriteria( tmpFldName, '"' );
      Result := GetByName( tmpRelName + '.' + tmpFldName, AIB_Field );
    end;
  end
  else
  begin
    // Store aside the original value to prevent infinite recursion.
    tmpFldName := AFieldName;
    if not isLitCriteria( AFieldName, '"' ) then
      AFieldName := AnsiUpperCase( AFieldName );
    if AFieldName = '""' then
      AFieldName := '';
    if ( lookupColumn <> nil ) and
       ( AnsiStrCmp( AFieldName, lookupColumn.FieldName ) = 0 ) then
    begin
      AIB_Field := lookupColumn;
      Inc(lastColumnIndex);
      Result := true;
    end else
    begin
      for ii := 0 to ColumnCount - 1 do
      begin
        if AnsiStrCmp( AFieldName, Columns[ ii ].FieldName ) = 0 then
        begin
          AIB_Field := Columns[ ii ];
          lastColumnIndex := ii;
          Result := true;
          Break;
        end;
      end;
    end;
    if ( not Result ) and ( AFieldName <> '' ) then
    begin
      if ( lookupColumn <> nil ) and ( AnsiStrCmp( AFieldName, lookupColumn.SQLName ) = 0 ) then
      begin
        AIB_Field := lookupColumn;
        Inc(lastColumnIndex);
        Result := true;
      end else
      begin
        for ii := 0 to FColumnList.Count - 1 do
        begin
          if ( AnsiStrCmp( AFieldName, Columns[ ii ].SQLName ) = 0 ) then
          begin
            AIB_Field := Columns[ ii ];
            lastColumnIndex := ii;
            Result := true;
            Break;
          end;
        end;
      end;
    end;
    // Because Borland didn't do a good job with handling field names
    // in regard to treating the double-quote character as having any
    // significance on the name of the identifier I now have to assume it is
    // possible to receive in an indentifier a quoted type identifier without
    // the quotes being on it.
    if ( not Result ) and ( not isLitCriteria( tmpFldName, '"' )) then
      Result := GetByName( mkLitCriteria( tmpFldName, '"' ), AIB_Field );
  end;
end;

function TIB_Row.ByName( const AFieldName: string ): TIB_Column;
begin
  if not GetByName( AFieldName, Result ) then 
    raise EIB_StatementError.CreateWithSender( Statement,
                                               Format( E_FIELDNAME_NOT_FOUND,
                                                                [AFieldName] ));
end;

function TIB_Row.ParamByName( const AFieldName: string ): TIB_Column;
begin
  Result := ByName( AFieldName );
end;

function TIB_Row.GetColumns( Index: word ): TIB_Column;
begin
  if Index < FColumnList.Count then
    Result := FColumnList.Items[ Index ]
  else
    Result := nil;
end;

function TIB_Row.GetRelationCount: word;
begin
  if FRelationList <> nil then
    Result := FRelationList.Count
  else
    Result := 0;
end;

function TIB_Row.GetRelationName( Index: word ): string;
begin
  if ( FRelationList <> nil ) and
     ( Index < RelationCount ) then
    Result := FRelationList[ Index ]
  else
    Result := '';
end;

{------------------------------------------------------------------------------}

function TIB_Row.GetPBlobHead: PPIB_BlobNode;
begin
  if Assigned( RowNode ) then
    Result := @RowNode.BlobHead
  else
    Result := @FSysBlobHead;
end;

procedure TIB_Row.PostBlobBuffers;
var
  ii: integer;
  tmpBlobNode: PIB_BlobNode;
  tmpArrayDesc: PISC_ARRAY_DESC;
begin
  FTempBlobIDCount := 0;
  if ( BlobCount + ArrayCount > 0 ) and
     ( not IsKeyFields ) and ( not IsBufferFields ) then
    for ii := 0 to PSQLDA.SQLd - 1 do
    begin
      tmpBlobNode := GetBlobNode( ii );
      if Assigned( tmpBlobNode ) and tmpBlobNode.BlobChanged then
      begin
        if ( isc_quad_is_zero( pisc_quad(PSQLDA.SQLVAR[ ii ].SQLData)^ )) and
           ( PSQLDA.SQLVAR[ ii ].SQLInd^ <> IB_Null ) then
        begin
          tmpArrayDesc := nil;
          case PSQLDA.SQLVAR[ ii ].SQLType of
            SQL_ARRAY,
            SQL_ARRAY_:
              tmpArrayDesc := @(BySQLNo( ii ) as TIB_ColumnArray).ArrayDesc;
          end;
          Statement.PutBlobNodeData( @PSQLDA.SQLVAR[ ii ],
                                     tmpArrayDesc,
                                     tmpBlobNode );
          pisc_quad(PSQLDA.SQLVAR[ ii ].SQLData)^ := tmpBlobNode.BlobID;
        end;
        with pisc_quad(PSQLDA.SQLVAR[ ii ].SQLData)^ do
          if ( isc_quad_high = 0 ) and
             ( isc_quad_low <> 0 ) then
            Inc( FTempBlobIDCount )
      end;
    end;
end;

procedure TIB_Row.CancelBlobBuffers;
var
  ii: integer;
  tmpBlobNode: PIB_BlobNode;
  tmpNeedTrim: boolean;
begin
  if ( BlobCount + ArrayCount > 0 ) and
     ( not IsKeyFields ) and ( not IsBufferFields ) then
  begin
    for ii := 0 to PSQLDA.SQLd - 1 do
    begin
      tmpBlobNode := GetBlobNode( ii );
      if Assigned( tmpBlobNode ) and tmpBlobNode.BlobChanged then
        ClearBlobNodeData( tmpBlobNode );
    end;
    if ( RowType = rtField ) then
      if Assigned( FRowNode ) then
      begin
        tmpBlobNode := FRowNode.BlobHead;
        tmpNeedTrim := false;
        while Assigned( tmpBlobNode ) do
        begin
          if ( tmpBlobNode.BlobID.isc_quad_high = 0 ) and
             ( tmpBlobNode.BlobID.isc_quad_low = 0 ) and
             ( tmpBlobNode.BlobFieldNo <> 0 ) then
          begin
            ClearBlobNodeData( tmpBlobNode );
            tmpNeedTrim := true;
          end
          else
            tmpBlobNode := tmpBlobNode.Next;
        end;
        if tmpNeedTrim then
          TrimBlobsFromList;
      end;
  end;
end;

procedure TIB_Row.AfterPostBuffers( Posted: boolean );
var
  ii: integer;
  tmpBlobNode: PIB_BlobNode;
begin
  if ( BlobCount + ArrayCount > 0 ) and
     ( not IsKeyFields ) and ( not IsBufferFields ) then
    for ii := 0 to PSQLDA.SQLd - 1 do 
    begin
      tmpBlobNode := GetBlobNode( ii );
      if Assigned( tmpBlobNode ) and tmpBlobNode.BlobChanged then
      begin
        if Posted and ( RowType = rtField ) then
        begin
          tmpBlobNode.BlobChanged := false;
          if ( not isc_quad_is_zero(pisc_quad(PSQLDA.SQLVAR[ii].SQLData)^)) and
             ( PSQLDA.SQLVAR[ ii ].SQLInd^ <> IB_Null ) then
          begin
            tmpBlobNode.BlobID := pisc_quad( PSQLDA.SQLVAR[ ii ].SQLData )^;
            tmpBlobNode.BlobFieldNo := ii;
            PutBlobNodeIntoList( PBlobHead, tmpBlobNode, true );
          end;
        end
        else
        begin
          pisc_quad( PSQLDA.SQLVAR[ ii ].SQLData )^ := BlankQuad;
          tmpBlobNode.BlobID := BlankQuad;
        end;
      end;
    end;
  if Posted then
    TrimBlobNodeCache;
end;

procedure TIB_Row.TrimBlobNodeCache;
var
  ii: integer;
  tmpNode: PIB_BlobNode;
  tmpQuad: isc_quad;
  tmpBool: boolean;
  OldHead: PIB_BlobNode;
  tmpHead: PIB_BlobNode;
begin
  tmpHead := PBlobHead^;
  OldHead := tmpHead;
  while Assigned( tmpHead ) do
  begin
    tmpBool := false;
    for ii := 0 to PSQLDA.SQLn - 1 do
    begin
      case PSQLDA.SQLVAR[ii].SQLType of
        SQL_BLOB,
        SQL_BLOB_: begin
          tmpQuad := isc_quad( PSQLDA.SQLVAR[ii].SQLData^ );
          if isc_quad_equal( tmpHead.BlobID, tmpQuad ) then
          begin
            tmpBool := true;
            Break;
          end;
        end;
      end;
    end;
    tmpNode := tmpHead.Next;
    if tmpBool then
      OldHead := tmpHead
    else
    begin
      if tmpHead = PBlobHead^ then
        PBlobHead^ := PBlobHead^.Next
      else
      if Assigned( OldHead.Next ) then
        OldHead.Next := OldHead.Next.Next;
      FreeBlobNodeData( tmpHead );
      FreeMem( tmpHead );
    end;
    tmpHead := tmpNode;
  end;
end;

procedure TIB_Row.CancelBuffers( AClearBlobNodes: boolean );
begin
  if not IsKeyFields then
  begin
    Move( FOldBuffer^, FRowBuffer^, FBufferLength );
    if BlobCount + ArrayCount > 0 then
      ClearBlobNodes( AClearBlobNodes );
    FIsRowNumChanged := false;
    SetRowState( rsUnmodified );
    SysAfterModify( nil );
  end;
end;

procedure TIB_Row.SysClearBuffers;
var
  ii: integer;
begin
  if not IsKeyFields and ( BufferLength > 0 ) then
  begin
    FillChar( FRowBuffer^, BufferLength, #0 );
    if Assigned( PSQLDA ) then
      for ii := 0 to PSQLDA.sqln - 1 do
        PSQLDA.SQLVAR[ ii ].sqlind^ := IB_NULL;
    Move( FRowBuffer^, FOldBuffer^, BufferLength );
  end;
end;

procedure TIB_Row.ClearBuffers( NewRowState: TIB_RowState );
begin
  if RowType = rtParam then
    NewRowState := rsUnmodified;
  if not IsKeyFields then
  begin
    SysClearBuffers;
    if BlobCount + ArrayCount > 0 then
      ClearBlobNodes( false );
    FIsRowNumChanged := NewRowState <> RowState;
    SetRowState( NewRowState );
    SysAfterModify( nil );
  end;
end;

procedure TIB_Row.RefreshBuffers( NewRecord, ClearBlobs, NeedCalc: boolean );
begin
  if NewRecord then
    CleanBuffers( true );
  if ClearBlobs and ( BlobCount + ArrayCount > 0 ) then
    ClearBlobNodes( NewRecord );
  if not IsKeyFields then
  begin
    Move( FRowBuffer^, FOldBuffer^, FBufferLength );
    FIsRowNumChanged := NewRecord;
  end;
  if RowState <> rsUnmodified then
    SetRowState( rsUnmodified );
  if NeedCalc and ( CalcList.Count > 0 ) then
    CalculateFields;
  SysAfterModify( nil );
end;

procedure TIB_Row.ClearBlobNodes( ClearCache: boolean );
var
  ii: integer;
  tmpBlobNode: PIB_BlobNode;
begin
  for ii := 0 to BlobCount + ArrayCount - 1 do
  begin
    tmpBlobNode := PIB_BlobNode( longint(FBlobNodeList) +
                                 ii * SizeOf(TIB_BlobNode));
    if RowType = rtParam then
    begin
      tmpBlobNode.BlobID.isc_quad_high := 0;
      tmpBlobNode.BlobID.isc_quad_low := 0;
    end
    else
      ClearBlobNodeData( tmpBlobNode );
  end;
  if ClearCache then
    FreeBlobRefList( PBlobHead );
end;

procedure TIB_Row.CleanBuffers( ResetNullInd: boolean );
var
  ii: integer;
  VarLength: integer;
begin
  for ii := 0 to ColumnCount - 1 do
    with Columns[ ii ] do
    begin
      if ResetNullInd and ( not Odd( SQLType )) then
        PSQLVAR.sqlind^ := IB_NOTNULL;
      case SQLType of
        SQL_VARYING, SQL_VARYING_:
        begin
          if PSQLVAR.sqlind^ = IB_NULL then
            FillChar( PSQLVAR.sqldata^, sqllen + 2, FPadChar )
          else
          with PSQL_VARCHAR( PSQLVAR.sqldata )^ do
          begin
            VarLength := 256 * vary_len_high + vary_len_low;
            FillChar( vary_string[VarLength], sqllen - VarLength, FPadChar );
          end;
        end
        else
        if PSQLVAR.sqlind^ = IB_NULL then
          FillChar( PSQLVAR.sqldata^, sqllen, FPadChar );
      end;
    end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Row.SetRowState( AValue: TIB_RowState );
begin
  if RowState <> AValue then
  begin
    FRowState := AValue;
    SysRowStateChanged;
  end;
end;

procedure TIB_Row.SysRowStateChanged;
begin
  if Assigned( FOnRowStateChanged ) then
    FOnRowStateChanged( Self );
end;

procedure TIB_Row.SysBeforeColumnModify( IB_Field: TIB_Column );
begin
  SysBeforeModify( IB_Field );
end;

procedure TIB_Row.SysAfterColumnModify( IB_Field: TIB_Column );
begin
  FIsRowNumChanged := false;
  if not FCalculatingFields then
    if RowState <> rsModified then
      SetRowState( rsModified );
  SysAfterModify( IB_Field );
end;

procedure TIB_Row.SysBeforeModify( IB_Field: TIB_Column );
begin
  DoBeforeModify( IB_Field );
end;

procedure TIB_Row.SysAfterModify( IB_Field: TIB_Column );
begin
  if FUpdatingCounter = 0 then
  begin
    DoAfterModify( IB_Field );
    if RowType = rtParam then
      SetRowState( rsUnmodified );
  end
  else
  if IB_Field = nil then
  begin
    FUpdatedWholeRow := true;
    if IsRowNumChanged then
      FIsUpdatedRowNumChanged := true;
  end
  else
  if FUpdatedColumns.IndexOf( IB_Field ) = -1 then
    FUpdatedColumns.Add( IB_Field );
end;

function TIB_Row.GetUpdatedColumnCount: word;
begin
  Result := FUpdatedColumns.Count;
end;

function TIB_Row.GetUpdatedWholeRow: boolean;
begin
  Result := FUpdatedWholeRow;
end;

procedure TIB_Row.SysPrepareFailed;
begin
  SysUpdate( 0 );
end;

{------------------------------------------------------------------------------}

procedure TIB_Row.DoBeforeModify( IB_Field: TIB_Column );
begin
  if Assigned( FBeforeModify ) then
    FBeforeModify( Self, IB_Field );
end;

procedure TIB_Row.DoAfterModify( IB_Field: TIB_Column );
begin
  if Assigned( FAfterModify ) then
    FAfterModify( Self, IB_Field );
end;

procedure TIB_Row.BeginUpdate;
begin
  Inc( FUpdatingCounter );
end;

procedure TIB_Row.EndUpdate( SingleEventOnly: boolean );
begin
  if FUpdatingCounter > 0 then
  begin
    Dec( FUpdatingCounter );
    if FUpdatingCounter = 0 then
      SysApplyUpdates( SingleEventOnly );
  end;
end;

procedure TIB_Row.SysApplyUpdates( SingleEventOnly: boolean );
var
  ii: integer;
begin
  if FUpdatedWholeRow or
     ( SingleEventOnly and ( FUpdatedColumns.Count > 1 )) then
  begin
    if FUpdatedWholeRow and ( not FIsRowNumChanged ) then
    begin
      FIsRowNumChanged := FIsUpdatedRowNumChanged;
      FIsUpdatedRowNumChanged := false;
    end;
    DoAfterModify( nil );
    FIsRowNumChanged := false;
  end
  else
  for ii := 0 to FUpdatedColumns.Count - 1 do
    DoAfterModify( TIB_Column(FUpdatedColumns[ ii ]));
  FUpdatedColumns.Clear;
  FUpdatedWholeRow := false;
  if RowType = rtParam then
    SetRowState( rsUnmodified );
end;

function TIB_Row.GetRowData: string;
begin
  if IsKeyFields or ( RowState <> rsNone ) then
    Result := BinaryToHexText( RowBuffer, BufferLength )
  else
    Result := '';
end;

procedure TIB_Row.SetRowData( const AValue: string );
begin
  if AValue <> '' then
    try
      HexTextToBinary( AValue, RowBuffer, BufferLength );
    except
      raise EIB_StatementError.CreateWithSender(
        Statement, E_Invalid_Bookmark + ' in SetRowData()' );
    end;
end;

function TIB_Row.GetOldRowData: string;
begin
  if IsKeyFields or ( RowState <> rsNone ) then
    Result := BinaryToHexText( OldBuffer, BufferLength )
  else
    Result := '';
end;

procedure TIB_Row.SetOldRowData( const AValue: string );
begin
  if AValue <> '' then
    try
      HexTextToBinary( AValue, OldBuffer, BufferLength );
    except
      raise EIB_StatementError.CreateWithSender(
        Statement, E_Invalid_Bookmark + ' in SetOldRowData()' );
    end;
end;

procedure TIB_Row.CalculateFields;
var
  ii: integer;
begin
  if not FCalculatingFields then
  begin
    FCalculatingFields := true;
    try
      if Statement.CalculateAllFields then
        Statement.DoCalculateField( Self, nil )
      else
      begin
        with CalcList do
          for ii := 0 to Count - 1 do
            Statement.DoCalculateField( Self, TIB_Column( Items[ ii ] ));
      end;
    finally
      FCalculatingFields := false;
    end;
  end;
end;

function TIB_Row.GetBlobNode( SQLNo: integer ): PIB_BlobNode;
var
  tmpInteger: integer;
begin
  Result := nil;
  if Assigned( FBlobFieldMap ) and ( SQLNo < PSQLDA.SQLn ) then
  begin
    tmpInteger := FBlobFieldMap.BlobIndex[ SQLNo ];
    if tmpInteger >= 0 then
      Result := PIB_BlobNode( longint(FBlobNodeList) +
                              tmpInteger * SizeOf(TIB_BlobNode));
  end;
end;

//------------------------------------------------------------------------------

function TIB_Row.GetColumnValue( const ColumnName: string ): Variant;
var
  I: Integer;
  tmpColumns: TList;
begin
  if not Statement.Prepared then
    Statement.Prepare;
  if RowState = rsNone then
    Result := Unassigned
  else
  if ( getLitSafePos( ';', ColumnName, 1 ) <> 0 ) or ( ColumnName = '*' ) then
  begin
    tmpColumns := TList.Create;
    try
      GetColumnList( tmpColumns, ColumnName );
      Result := VarArrayCreate( [0, tmpColumns.Count - 1], varVariant );
      for I := 0 to tmpColumns.Count - 1 do
        if TIB_Column( tmpColumns[I] ).IsNull then
          Result[I] := Null
        else
          Result[I] := TIB_Column( tmpColumns[I] ).AsVariant;
    finally
      tmpColumns.Free;
    end;
  end
  else
  begin
    with ByName( ColumnName ) do
    begin
      if IsNull then
        Result := Null
      else
        Result := AsVariant;
    end;
  end;
end;

procedure TIB_Row.SetColumnValue( const ColumnName: string;
                                  const AValue: Variant );
var
  ii: Integer;
  tmpColumns: TList;
begin
  if not Statement.Prepared then
    Statement.Prepare;
  if ( getLitSafePos( ';', ColumnName, 1 ) <> 0 ) or ( ColumnName = '*' ) then
  begin
    BeginUpdate;
    try
      tmpColumns := TList.Create;
      try
        GetColumnList( tmpColumns, ColumnName );
        for ii := 0 to tmpColumns.Count - 1 do
        begin
          if VarIsArray( AValue ) then
            TIB_Column( tmpColumns[ ii ] ).AsVariant := AValue[ ii ]
         else
            TIB_Column( tmpColumns[ ii ] ).AsVariant := AValue;
        end;
      finally
        tmpColumns.Free;
      end;
    finally
      EndUpdate( true );
    end;
  end
  else
  begin
    with ByName( ColumnName ) do
    begin
      if ( not IsArray ) and VarIsArray( AValue ) then
      begin
        ii := VarArrayLowBound( AValue, 1 );
        AsVariant := AValue[ ii ];
      end
      else
        AsVariant := AValue;
    end;
  end;
end;

function TIB_Row.GetColumnOldValue( const ColumnName: string ): Variant;
var
  I: Integer;
  tmpColumns: TList;
begin
  if not Statement.Prepared then
    Statement.Prepare;
  if ( getLitSafePos( ';', ColumnName, 1 ) <> 0 ) or ( ColumnName = '*' ) then
  begin
    tmpColumns := TList.Create;
    try
      GetColumnList( tmpColumns, ColumnName );
      Result := VarArrayCreate( [0, tmpColumns.Count - 1], varVariant );
      for I := 0 to tmpColumns.Count - 1 do
        Result[I] := TIB_Column( tmpColumns[I] ).OldAsVariant;
    finally
      tmpColumns.Free;
    end;
  end
  else
    Result := ByName( ColumnName ).OldAsVariant;
end;

procedure TIB_Row.GetColumnList( AList: TList; const ColumnNames: string );
var
  Pos: integer;
begin
  AList.Clear;
  if ( ColumnNames = '' ) or ( ColumnNames = '*' ) then
    for Pos := 0 to ColumnCount - 1 do
      AList.Add( Columns[ Pos ] )
  else
  begin
    Pos := 1;
    while Pos <= Length( ColumnNames ) do
      AList.Add( ByName( ExtractFieldName( ColumnNames, Pos )));
  end;
end;

procedure TIB_Row.LoadFromNode( Notify, LoadBlobs: boolean );
var
  tmpPtr: pointer;
  tmpLen: longint;
begin
  if not Assigned( FRowNode.RecordData ) then
    raise EIB_StatementError.CreateWithSender( Statement,
                                               E_No_Record_Data_To_Load );
  if Assigned( FRowNode.OldRecData ) then
  begin
    tmpPtr := FRowNode.RecordData;
    tmpLen := FRowNode.RecordLen;
    try
      FRowNode.RecordData := FRowNode.OldRecData;
      FRowNode.RecordLen := FRowNode.OldRecLen;
      GetRecord( FRowNode, Self );
      Move( FRowBuffer^, FOldBuffer^, FBufferLength );
    finally
      FRowNode.RecordData := tmpPtr;
      FRowNode.RecordLen := tmpLen;
    end;
  end;
  GetRecord( FRowNode, Self );
// Removed from here because the node should cache the values for the
// calculated columns as well as the data columns. This is based on the
// assumption that people may do some fairly "expensive" operations in the
// OnCalc event that shouldn't be oft repeated if not necessary.
//  if PSQLDA.SQLn > PSQLDA.SQLd then CalculateFields;
  if not Assigned( FRowNode.OldRecData ) then
    Move( FRowBuffer^, FOldBuffer^, FBufferLength );
  if LoadBlobs then
    LoadBlobsFromNode;
  if Notify then
  begin
    // Not sure about FIsRowNumChanged - its definitely true on some calls
    // because it was missing after TIB_BindingCursor.QuickFetch was changed
    // to use this function in v3_5_B (from v3_5_Ai).
    FIsRowNumChanged := true;
    if RowState <> rsUnmodified then
     SetRowState( rsUnmodified );
    SysAfterModify( nil );
  end;
end;

procedure TIB_Row.LoadBlobsFromNode;
var
  tmpBlobNode: PIB_BlobNode;
  newBlobNode: PIB_BlobNode;
  tmpNeedTrim: boolean;
begin
  if ( RowType = rtField ) and ( not IsKeyFields ) and
                               ( not IsBufferFields ) then
  begin
    tmpNeedTrim := false;
    tmpBlobNode := FRowNode.BlobHead;
    while Assigned( tmpBlobNode ) do
    begin
      if ( tmpBlobNode.BlobID.isc_quad_high = 0 ) and
         ( tmpBlobNode.BlobID.isc_quad_low = 0 ) and
         ( tmpBlobNode.BlobFieldNo <> 0 ) then
      begin
        newBlobNode := GetBlobNode( tmpBlobNode.BlobFieldNo );
        TransferBlobNodeData( tmpBlobNode, newBlobNode, true );
        tmpNeedTrim := true;
      end
      else
        tmpBlobNode := tmpBlobNode.Next;
    end;
    if tmpNeedTrim then
      TrimBlobsFromList;
  end;
end;

procedure TIB_Row.TrimBlobsFromList;
var
  tmpBlobNode: PIB_BlobNode;
  newBlobNode: PIB_BlobNode;
begin
  while Assigned( FRowNode.BlobHead ) and
        ( FRowNode.BlobHead.BlobID.isc_quad_high = 0 ) and
        ( FRowNode.BlobHead.BlobID.isc_quad_low = 0 ) and
        ( FRowNode.BlobHead.BlobFieldNo = 0 ) do
  begin
    tmpBlobNode := FRowNode.BlobHead.Next;
    if Assigned( FRowNode.BlobHead.BlobBuffer ) then
      FreeMem( FRowNode.BlobHead.BlobBuffer );
    FreeMem( FRowNode.BlobHead );
    FRowNode.BlobHead := tmpBlobNode;
  end;
  tmpBlobNode := FRowNode.BlobHead;
  if Assigned( tmpBlobNode ) then
    while Assigned( tmpBlobNode.Next ) do
      if ( tmpBlobNode.Next.BlobID.isc_quad_high = 0 ) and
         ( tmpBlobNode.Next.BlobID.isc_quad_low = 0 ) and
         ( tmpBlobNode.Next.BlobFieldNo = 0 ) then
      begin
        newBlobNode := tmpBlobNode.Next.Next;
        if Assigned( tmpBlobNode.Next.BlobBuffer ) then
          FreeMem( tmpBlobNode.Next.BlobBuffer );
        FreeMem( tmpBlobNode.Next );
        tmpBlobNode.Next := newBlobNode;
      end
      else
        tmpBlobNode := tmpBlobNode.Next;
end;

procedure TIB_Row.StoreBlobsToNode;
var
  ii: integer;
  tmpBlobNode: PIB_BlobNode;
begin
  PutRecord( RowNode, Self );
  if ( RowType = rtField ) and ( not IsKeyFields ) and
                               ( not IsBufferFields ) then
    for ii := 0 to PSQLDA.SQLd - 1 do
    begin
      tmpBlobNode := GetBlobNode( ii );
      if Assigned( tmpBlobNode ) and tmpBlobNode.BlobChanged and
         (( tmpBlobNode.BlobSize > 0 ) or
           ( not Odd( PSQLDA.SQLVAR[ii].SQLType ))) then
      begin
        tmpBlobNode.BlobID.isc_quad_high := 0;
        tmpBlobNode.BlobID.isc_quad_low := 0;
        tmpBlobNode.BlobFieldNo := ii;
        PutBlobNodeIntoList( PBlobHead, tmpBlobNode, true );
      end;
    end;
end;

// IBA_Column.INT
// IBA_ColumnBlob.INT

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  25-Jan-02                                                                   }
{      Further adjustments to ColumnNumeric to support consistency changes     }
{      made to some IB_Utils methods.  Part of this is to be consistent with   }
{      the VCL in the use of statistical/bankers rounding of assignments.      }
{                                                                              }
{  Marco Menardi <mmenaz@lycosmail.com>                                        }
{  08-Gen-02                                                                   }
{      TIB_Column.GetDisplayWidth now considers GridDisplayName if set         }
{      (added private function CalcDefaultWidth, changed GetDefaultWidth too)  }
{                                                                              }
{  Geoff Worboys <geoff@telesiscomputing.com.au>                               }
{  01-Jan-02                                                                   }
{      Attempting to resolve rounding and overflow issues relating to all      }
{      number processing aspects of TIB_Column instances.  Also removed the    }
{      NoRoundErr processing - as this has been removed from IBO.              }
{      TIB_ColumnCurr and TIB_ColumnComp removed, no longer used.              }
{      **1**  I dont know why RoundDown was used, but I left it in.            }
{                                                                              }
{  Marco Menardi <mmenaz@lycosmail.com>                                        }
{  06-Nov-01                                                                   }
{      TIB_ColumnNumeric.SetAsString() can work even if CurrencyString is      }
{      longer than 1 char and has chars that can not be placed in Masks        }
{                                                                              }
{  Marco Menardi <mmenaz@lycosmail.com>                                        }
{  03-Nov-01 bug fix                                                           }
{      TIB_ColumnNumeric.SetAsString() now really try the conversion removing  }
{      eventual numeric formatting chars for any reason present and uses locale}
{      settings the best it can do                                             }
{                                                                              }
{  Marco Menardi <mmenaz@lycosmail.com>                                        }
{  30-Oct-01 bug fix                                                           }
{      Now grids and Autolabel using FieldName that are not in double quotes   }
{      have '_' changed with space even if in SQL Dialect3                     }
{      i.e. FIRST_NAME -> FIRST NAME (but "FIRST_NAME" is not changed)         }
{                                                                              }
{  Marco Menardi <mmenaz@lycosmail.com>                                        }
{  24-Oct-2001                                                                 }
{     Added support for IB6 CURRENT_DATE/_TIMESTAMP/_TIME as field defaults    }
{            Copyright (C) 2001 Marco Menardi                                  }
{                                                                              }
{******************************************************************************}

constructor TIB_Column.Create( ARow: TIB_Row;
                               PSQLVAR: PXSQLVAR;
                               AFieldNo: smallint );
var
  tmpPos: integer;
  tmpStr: string;
begin
  inherited Create;
  FOrderingLinkItemNo := -999;
  FRow := ARow;
  FFieldNo := AFieldNo;
  FPXSQLVAR := PSQLVAR;
  FDisplayWidth := -1;
  FCharCase := ccNormal;
  FVisible := true;
  FTrimming := ctNone;
  FMaskIntf := nil;
  FBlankIsNull := IsAttributeSet[ IBO_BLANKISNULL ];
  FIsBoolean := IsAttributeSet[ IBO_BOOLEAN ];
  FIsText := false;
  FPadChar := #0;
  FBooleanTrue := '';
  FBooleanFalse := '';
  FIsCurrencyDataType := false;
  FBound := false;
  if FIsBoolean then
  begin
    tmpStr := Trim( AttributeParams[ IBO_BOOLEAN ] );
    tmpPos := Pos( ',', tmpStr );
    if tmpPos > 1 then
    begin
      FBooleanTrue := Trim( Copy( tmpStr, 1, tmpPos - 1 ));
      FBooleanFalse := Trim( Copy( tmpStr, tmpPos + 1, MaxInt ));
    end;
  end
  else
  if ( SQLtype = SQL_BOOLEAN ) or
     ( SQLtype = SQL_BOOLEAN_ ) then
    FIsBoolean := true;     
  if FIsBoolean then
    FAlignment := taCenter
  else
    case SQLtype of
      SQL_FLOAT,
      SQL_FLOAT_,
      SQL_DOUBLE,
      SQL_DOUBLE_,
      SQL_SHORT,
      SQL_SHORT_,
      SQL_LONG,
      SQL_LONG_,
      SQL_QUAD,
      SQL_QUAD_,
      SQL_INT64,
      SQL_INT64_: FAlignment := taRightJustify;
      else        FAlignment := taLeftJustify;
    end;
  SysLayoutChanged;
end;

procedure TIB_Column.SysLayoutChanged;
var
  tmpBool: boolean;
begin
  // ~TC~ The following lined added by Telesis Computing
  // It allows calculated fields, parameters and stored procedures
  // to be given an explicit domain name - reducing the need to define
  // editmasks, displays formats etc for every such field.
  FDomainName := AttributeParams['DOMAIN'];
  if ( SQLType = SQL_TYPE_DATE ) or
     ( SQLType = SQL_TYPE_DATE_ ) then
    tmpBool := true
  else
    FDateOnlyFmt := GetAttributeParamsEx( IBO_NOTIME, tmpBool );
  if tmpBool and ( FDateOnlyFmt = '' ) then
  begin
    FDateOnlyFmt := DisplayFormat;
    if FDateOnlyFmt = '' then
      FDateOnlyFmt := ShortDateFormat;
  end;
  FTimeOnlyFmt := GetAttributeParamsEx( IBO_NODATE, tmpBool );
  if tmpBool and ( FTimeOnlyFmt = '' ) then
  begin
    FTimeOnlyFmt := DisplayFormat;
    if FTimeOnlyFmt = '' then
      FTimeOnlyFmt := ShortTimeFormat;
  end;
end;

destructor TIB_Column.Destroy;
begin
  if Assigned( FValidateBuffer ) then
  begin
    FreeMem( FValidateBuffer );
    FValidateBuffer := nil;
  end;
  if Assigned( FPreserveBuffer ) then
  begin
    FreeMem( FPreserveBuffer );
    FPreserveBuffer := nil;
  end;
  FreeMaskIntf;
  inherited Destroy;
end;

procedure TIB_Column.FreeMaskIntf;
begin
  // No need for an explicit free - the mask processor should
  // be derived from TInterfacedObject and will free itself when
  // the reference count reaches 0.
  if FMaskIntf <> nil then
    FMaskIntf := nil;
end;

function TIB_Column.GetBDEFieldName: string;
begin
  if not Row.FBDENamesValid then
    Row.NameVarListForBDE;
  Result := FBDEFieldName;
end;

function TIB_Column.GetDBFFieldName: string;
begin
  if not Row.FBDENamesValid then
    Row.NameVarListForBDE;
  Result := FDBFFieldName;
end;

function TIB_Column.GetIsDateOnly: boolean;
begin
  Result := FDateOnlyFmt <> '';
end;

function TIB_Column.GetIsTimeOnly: boolean;
begin
  Result := FTimeOnlyFmt <> '';
end;

function TIB_Column.GetBlobSize: longint;
begin
  Result := 0;
end;

function TIB_Column.GetDataSize: word;
begin
  Result := SQLLen;
  case SQLtype of
    SQL_VARYING,
    SQL_VARYING_: Inc( Result, SizeOf( smallint ));
  end;
end;

procedure TIB_Column.SysApplyTrimming( var AValue: string );
var
  tmpReadPos: integer;
  tmpWritePos: integer;
  procedure DoWriteByte;
  begin
    if tmpWritePos <> tmpReadPos then
      AValue[tmpWritePos] := AValue[tmpReadPos];
    Inc( tmpWritePos );
  end;
var
  curChar: char;
  tmpEndPos: integer;
  tmpPeriodPos: integer;
  tmpLastCharPos: integer;
  tmpLastWriteByte: integer;
begin
  tmpEndPos := Length( AValue );
  if tmpEndPos > 0 then
  begin
    case Trimming of
      ctBoth, ctBothCheckLen:
      begin
        if ( Ord( AValue[1] ) <= 32 ) or ( Ord( AValue[tmpEndPos] ) <= 32 ) then
          AValue := Trim( AValue );
      end;
      ctLeft, ctLeftCheckLen:
      begin
        if ( Ord( AValue[1] ) <= 32 ) then
          AValue := TrimLeft( AValue );
      end;
      ctRight, ctRightCheckLen:
      begin
        if ( Ord( AValue[tmpEndPos] ) <= 32 ) then
          AValue := TrimRight( AValue );
      end;
      ctAll, ctAllCheckLen, ctSentence, ctSentenceCheckLen:
      begin
        tmpPeriodPos := 0;
        tmpLastCharPos := 0;
        tmpLastWriteByte := 0;
        tmpReadPos := 1;
        tmpWritePos := 1;
        while tmpReadPos <= tmpEndPos do
        begin
          curChar := AValue[tmpReadPos];
          if curChar <> ' ' then
          begin
            if curChar = '.' then tmpPeriodPos := tmpReadPos;
            tmpLastCharPos := tmpReadPos;
            tmpLastWriteByte := tmpWritePos;
            DoWriteByte;
          end
          else
          if ( Trimming in [ ctSentence, ctSentenceCheckLen ] ) and
             ( tmpLastCharPos > 0 ) and
             (( tmpLastCharPos = tmpReadPos - 1 ) or
              (( tmpLastCharPos = tmpReadPos - 2 ) and
               ( tmpLastCharPos = tmpPeriodPos ))) then
            DoWriteByte;
          Inc( tmpReadPos );
        end;
        SetLength( AValue, tmpLastWriteByte );
      end;
    end;
  end;
  if ( Length( AValue ) > SQLLen ) and
     ( Trimming in [ ctNoneCheckLen, ctAllCheckLen,
                     ctBothCheckLen, ctleftCheckLen,
                     ctRightCheckLen, ctSentenceCheckLen ] ) then
    raise EIB_StatementError.CreateWithSender( Statement,
                                               Format( E_StringLenExceeded,
                                               [ BestFieldName,
                                               IntToStr( SQLLen ),
                                               IntToStr( Length( AValue )) ] ));
end;

procedure TIB_Column.CheckInfoValid;
begin
  if not FColInfoValid then
  begin
    with Statement.IB_Connection.SchemaCache.PrimaryKeys do
      FIsPrimary := LinkIndex[ SQLName + ',' + RelName ] >= 0;
    with Statement.IB_Connection.SchemaCache.ForeignKeys do
      FIsForeign := LinkIndex[ SQLName + ',' + RelName ] >= 0;
    with Statement.IB_Connection.SchemaCache.Defaults do
      FIsDefaulted := LinkIndex[ RelName + '.' + SQLName ] >= 0;
//    with Statement.IB_Connection.SchemaCache.FieldNoNames do
//      FIsAlternate := LinkIndex[ SQLName + ',' + RelName ] >= 0;
    FIsAlternate := false; // Work in progress!!!
    FIsIndexField := FIsPrimary or FIsForeign; // Work in progress!!!
    FColInfoValid := true;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Column.LoadFromFile( const AFileName: string );
begin
  raise Exception.Create( E_NotImplemented );
end;

procedure TIB_Column.SaveToFile( const AFileName: string );
begin
  raise Exception.Create( E_NotImplemented );
end;

procedure TIB_Column.AssignTo( Dest: TObject );
begin
  if Dest is TIB_Column then
    ( Dest as TIB_Column ).Assign( Self )
  else
  if Dest is TStrings then with Dest as TStrings do
    Text := AsString
  else
    raise EIB_ColumnError.CreateWithSender( Statement,
                                            Format( E_Assign_Column,
                                            [Dest.ClassName] ));
end;

procedure TIB_Column.Assign( Source: TObject );
begin
  if Source is TIB_Column then
    with Source as TIB_Column do
    begin
      Inc( Self.Row.FStrictModifyOnlyLevel );
      try
        if IsNull then
        begin
          if not Self.IsNull then
            Self.Clear;
        end
        else
        begin
          if Self.IsNull then
            Self.IsNull := false;
          if Self.IsBoolean and IsBoolean then
            Self.AsBoolean := AsBoolean
          else
          if Self.IsDateTime or Self.IsDateOnly or Self.IsTimeOnly then
            Self.AsDateTime := AsDateTime
          else
          if Self.IsArray or IsArray then
            Self.AsVariant := AsVariant
          else
          begin
            Self.FSetText := FOnSetText;
            Self.FSetTextCol := Source as TIB_Column;
            try
              Self.AsString := AsString;
            finally
              Self.FSetText := nil;
              Self.FSetTextCol := nil;
            end;
          end;
        end;
      finally
        Dec( Self.Row.FStrictModifyOnlyLevel );
      end;
    end
    else
      raise EIB_ColumnError.CreateWithSender( Statement,
                                              Format( E_AssignTo_Column,
                                                      [Source.ClassName] ));
end;

procedure TIB_Column.Revert;
var
  tmpVar: variant;
begin
  if IsModified then
  begin
    tmpVar := OldAsVariant;
    if VarIsEmpty( tmpVar ) or VarIsNull( tmpVar ) then
      Clear
    else
      AsVariant := tmpVar;
  end;
end;

procedure TIB_Column.SetBlobData( ABuffer: Pointer; ASize: integer );
var
  tmpStr: string;
begin
  SetLength( tmpStr, ASize );
  Move( ABuffer^, tmpStr[1], ASize );
  AsString := tmpStr;
end;

procedure TIB_Column.FocusControl;
var
  AField: TIB_Column;
begin
  AField := Self;
  Statement.ProcessLinkEvent(setFocusControl, longint(@AField));
end;

procedure TIB_Column.Clear;
begin
  IsNull := true;
end;

{------------------------------------------------------------------------------}

function TIB_Column.GetStatement: TIB_Statement;
begin
  Result := Row.Statement;
  if Assigned( Result.FBindingCursor ) then
    Result := Result.FBindingCursor;
end;

function TIB_Column.GetSQLTypeSource( const BaseTypeOnly: boolean ): string;
var
  tmpS: TIB_StringList;
begin
  Result := '';
  with FPXSQLVAR^ do
  begin
    case SQLtype of
      SQL_QUAD,
      SQL_QUAD_,
      SQL_INT64,
      SQL_INT64_:  if SQLScale <> 0 then
        Result := 'DECIMAL ( 18, ' + IntToStr( -SQLScale ) + ' )'
      else
      begin
        Result := 'DECIMAL ( 18, ' + IntToStr( -SQLScale ) + ' )';
//???        Result := 'BIGINT';
      end;
      SQL_DOUBLE,
      SQL_DOUBLE_: if SQLScale <> 0 then
        Result := 'NUMERIC ( 15, ' + IntToStr( -SQLScale ) + ' )'
      else
        Result := 'DOUBLE PRECISION';
      SQL_FLOAT,
      SQL_FLOAT_: if SQLScale <> 0 then
        Result := 'NUMERIC ( 9, ' + IntToStr( -SQLScale ) + ' )'
      else
        Result := 'FLOAT';
      SQL_LONG,
      SQL_LONG_: if SQLScale <> 0 then
        Result := 'DECIMAL ( 9, ' + IntToStr( -SQLScale ) + ' )'
      else
        Result := 'INTEGER';
      SQL_SHORT,
      SQL_SHORT_:  if SQLScale <> 0 then
        Result := 'DECIMAL ( 4, ' + IntToStr( -SQLScale ) + ' )'
      else
        Result := 'SMALLINT';
      SQL_BOOLEAN,
      SQL_BOOLEAN_: Result := 'BOOLEAN';
      SQL_TYPE_DATE,
      SQL_TYPE_DATE_: Result := 'DATE';
      SQL_TYPE_TIME,
      SQL_TYPE_TIME_: Result := 'TIME';
      SQL_TIMESTAMP,
      SQL_TIMESTAMP_:
        if Statement.IB_Connection.SQLDialect >= 3 then
          Result := 'TIMESTAMP'
        else
          Result := 'DATE';
      SQL_TEXT,
      SQL_TEXT_: Result := 'CHAR( ' + IntToStr( SQLLen ) + ' )';
      SQL_VARYING,
      SQL_VARYING_: Result := 'VARCHAR( ' + IntToStr( SQLLen ) + ' )';
      SQL_BLOB,
      SQL_BLOB_: Result := 'BLOB( ' + IntToStr( MaxWord{!!!} ) + ', ' +
                                      IntToStr( sqlsubtype ) + ' )';
{
  with Blob_Desc do begin
    if sqlsubtype = 1 then begin
      Result := 'BLOB( SUB_TYPE '      + IntToStr( blob_desc_subtype      ) +
                      'SEGMENT SIZE '  + IntToStr( blob_desc_segment_size ) +
                      'CHARACTER SET ' + IntToStr( blob_desc_charset      ) +
                      ' )';
    end;
  end;
}
    end;
  end;
  if BaseTypeOnly then
    Exit;
  if FieldNo < 0 then
    Result := Result + ' /* CLIENT CALCULATED */'
  else with Row, Statement do
  if ( RowType = rtParam ) and
     ( StatementType = stExecProcedure ) and
     ( IB_Connection.Characteristics.dbODS_Version >= 10 ) then
  begin
    if ( FDefaultSource = '' ) then
    begin
      tmpS := TIB_StringList.Create;
      try
        IB_Connection.SchemaCache.GetProcParamAttr( RelName, FieldName, tmpS );
        FDefaultSource := tmpS.LinkValues['DEFAULT_SOURCE'];
      finally
        tmpS.Free;
      end;
    end;
    if FDefaultSource <> '' then
      Result := Result + ' ' + FDefaultSource;
  end;
end;

function TIB_Column.GetFieldSource( DomainInfo: boolean ): string;
var
  ss: string;
begin
  ss := '';
  if DomainInfo then
    ss := DomainName;
  if ( ss = '' ) or ( Pos( IBO_RDB, ss ) = 1 ) then
    ss := SQLTypeSource[ false ];
  if FieldName = '' then
    Result := PadRight( '""', #32, 20, false ) + '   ' +
              PadRight( ss, #32, 20, false )
  else
    Result := PadRight( FieldName, #32, 20, false ) + '   ' +
              PadRight( ss, #32, 20, false );
  if not IsNullable then
    Result := Result + '   NOT NULL';
  Result := TrimRight( Result );
  if FieldNo < 0 then
    Result := Result + '   /* Calculated */';
  if IsDefaulted then
    Result := Result + '   /* Defaulted */';
  if Computed then
    Result := Result + '   /* Computed */';
end;

{------------------------------------------------------------------------------}

function TIB_Column.GetSQLName: string;
begin
  if FPXSQLVAR.sqlname_length <={!!} 0 then
    Result := ''
  else
  begin
    Result := Copy( FPXSQLVAR.sqlname, 1, FPXSQLVAR.sqlname_length );
    if AnsiCompareText( Result, IBO_DB_KEY ) = 0 then
      Result := IBO_RDB + IBO_DB_KEY
    else
      Result := Statement.IB_Connection.mkVARIdent( Result );
  end;
end;

function TIB_Column.GetRelName: string;
begin
  if FPXSQLVAR.relname_length <={!!} 0 then
    Result := ''
  else
  begin
    Result := Copy( FPXSQLVAR.relname, 1, FPXSQLVAR.relname_length );
    Result := Statement.IB_Connection.mkVARIdent( Result );
  end;
end;

function TIB_Column.GetRelAliasName: string;
begin
  Result := FRelAliasName;
  if Result = '' then
    Result := RelName;
end;

function TIB_Column.GetOwnerName: string;
begin
  if FPXSQLVAR.ownname_length <={!!} 0 then
    Result := ''
  else
  begin
    Result := Copy( FPXSQLVAR.ownname, 1, FPXSQLVAR.ownname_length );
    Result := Statement.IB_Connection.mkVARIdent( Result );
  end;
end;

function TIB_Column.GetFieldName: string;
begin
  if FPXSQLVAR.aliasname_length <={!!} 0 then
    Result := ''
  else
  begin
    Result := Copy( FPXSQLVAR.aliasname, 1, FPXSQLVAR.aliasname_length );
    Result := Statement.IB_Connection.mkVARIdent( Result );
  end;
  if ( Result = '' ) and ( Statement.SQLDialect < 3 ) then
    Result := 'COLUMN' + IntToStr( FieldNo );
end;

function TIB_Column.GetFullFieldName: string;
begin
  Result := FieldName;
  if ( RelAliasName <> '' ) then
    Result := RelAliasName + '.' + Result
  else
  if ( RelName <> '' ) then
    Result := RelName + '.' + Result
end;

function TIB_Column.GetFullSQLName: string;
begin
  Result := SQLName;
  if ( Result <> '' ) and ( RelName <> '' ) then
      Result := RelName + '.' + Result;
end;

function TIB_Column.GetBestFieldName: string;
begin
  if ( Row.RelationCount > 1 ) then
    Result := FullFieldName
  else
    Result := FieldName;
end;

function TIB_Column.GetDomainName: string;
begin
  if ( not IsCalculated ) and
     ( FDomainName = '' ) and
     ( Row.RowType = rtField ) and
     ( Pos( IBO_RDB, FieldName ) <> 1 ) then
    FDomainName := Statement.GetDomainName( RelName, SQLName );
  Result := FDomainName;
end;

function TIB_Column.GetSQLType: smallint;
begin
  Result := FPXSQLVAR.SQLtype;
end;

function TIB_Column.GetSQLScale: smallint;
begin
  Result := FPXSQLVAR.SQLscale;
end;

function TIB_Column.GetSQLSubType: smallint;
begin
  Result := FPXSQLVAR.SQLsubtype;
end;

function TIB_Column.GetSQLLen: smallint;
begin
  Result := FPXSQLVAR.SQLLen;
end;

function TIB_Column.GetOldIsNull: boolean;
begin
  Result := FOldColumnInd^ = IB_NULL;
end;
function TIB_Column.GetOldIsNotNull: boolean;
begin
  Result := FOldColumnInd^ <> IB_NULL;
end;
function TIB_Column.GetIsNull: boolean;
begin
  Result := FNewColumnInd^ = IB_NULL;
end;

function TIB_Column.GetIsNotNull: boolean;
begin
  Result := FNewColumnInd^ <> IB_NULL;
end;

function TIB_Column.GetOldAsString: string;
var
  tmpInd: smallint;
  tmpData: pointer;
begin
  if OldIsNull then
    Result := ''
  else
  begin
    tmpInd := FNewColumnInd^;
    tmpData := FNewColumnBuffer;
    try
      FNewColumnInd^ := FOldColumnInd^;
      FNewColumnBuffer := FOldColumnBuffer;
      Result := AsString;
    finally
      FNewColumnInd^ := tmpInd;
      FNewColumnBuffer := tmpData;
    end;
  end;
end;

function TIB_Column.GetOldAsVariant: variant;
var
  tmpInd: smallint;
  tmpData: pointer;
begin
  if OldIsNull then
    Result := Unassigned
  else
  begin
    tmpInd := FNewColumnInd^;
    tmpData := FNewColumnBuffer;
    try
      FNewColumnInd^ := FOldColumnInd^;
      FNewColumnBuffer := FOldColumnBuffer;
      Result := AsVariant;
    finally
      FNewColumnInd^ := tmpInd;
      FNewColumnBuffer := tmpData;
    end;
  end;
end;

procedure TIB_Column.SysSetIsNull( const NewValue: boolean );
begin
  if NewValue then
  begin
    if ( not IsNullable ) and ( Row.RowType = rtParam ) then
      Inc( PSQLVAR.SQLType );
    FNewColumnInd^ := IB_NULL;
    case SQLType of
      SQL_VARYING,
      SQL_VARYING_: with SQL_VARCHAR( FNewColumnBuffer^ ) do
      begin
        vary_len_low := 0;
        vary_len_high := 0;
        FillChar( vary_string, SQLLen, FPadChar );
      end
      else
        FillChar( FNewColumnBuffer^, DataSize, FPadChar );
    end;
  end
  else
  if FNewColumnInd^ <> 0 then
  begin
    FNewColumnInd^ := 0;
    if IsBoolean then
    begin
      if AsBoolean then
      begin
        if AsString <> BooleanTrue then
          AsString := BooleanTrue;
      end
      else
      begin
        if AsString <> BooleanFalse then
          AsString := BooleanFalse;
      end;
    end;
  end;
end;

procedure TIB_Column.SetIsNull( const NewValue: boolean );
begin
  if ( NewValue <> IsNull ) or
     (( NewValue and ( not IsNullable )) and ( Row.RowType = rtParam )) then
  begin
    SysBeforeModify;
    SysSetIsNull( NewValue );
    SysAfterModify;
  end;
end;

function TIB_Column.GetComputed: boolean;
var
  tmpInt: integer;
begin
  Result := ( Row.RowType = rtField ) and ( IsAttributeSet[ IBO_COMPUTED ] );
  if ( not Result ) and ( not IsCalculated ) then
    with Statement.IB_Connection.SchemaCache.Computed do
    begin
      tmpInt := LinkIndex[ SQLName + ',' + RelName ];
      Result := tmpInt >= 0;
    end;
end;

function TIB_Column.GetIsLoaded: boolean;
begin
  Result := true;
end;

function TIB_Column.GetIsModified: boolean;
begin
  Result := ( not BuffersEqual( FNewColumnBuffer,
                                FOldColumnBuffer,
                                DataSize )) or
            ( FNewColumnInd^ <> FOldColumnInd^ );
end;

function TIB_Column.GetIsCalculated: boolean;
begin
  Result := ( Row.RowType = rtField ) and
            (( RelName = '' ) or
             ( SQLName = '' ) or
             ( SQLName = IBO_DB_KEY ) or
             ( SQLName = IBO_RDB + IBO_DB_KEY ) or
             ( SQLName = 'RPL$SYNC_ID' ));
end;

function TIB_Column.GetReadOnly: boolean;
begin
  Result := FReadOnly or FNotInForUpdate;
  if not Result then
    Statement.GetColumnIsReadOnly( Self, Result );
end;

function TIB_Column.GetControlsReadOnly: boolean;
begin
  Result := FForceControlsReadOnly;
  if not Result then
    Statement.GetControlIsReadOnly( Self, Result );
end;

function TIB_Column.GetRequired: boolean;
begin
  Result := ( not IsNullable ) and ( not IsCalculated );
  if not Result then
    Result := IsAttributeSet[ IBO_REQUIRED ]
  else
  if IsAttributeSet[ IBO_NOTREQUIRED ] then
    Result := false;
end;

function TIB_Column.GetIsNullable: boolean;
begin
  Result := Odd( FPXSQLVAR.SQLType );
end;

function TIB_Column.GetIsPrimary: boolean;
begin
  if IsCalculated or Computed then
    Result := false
  else
  begin
    CheckInfoValid;
    Result := FIsPrimary;
  end;
end;

function TIB_Column.GetIsForeign: boolean;
begin
  if IsCalculated or Computed then
    Result := false
  else
  begin
    CheckInfoValid;
    Result := FIsForeign;
  end;
end;

function TIB_Column.GetIsDefaulted: boolean;
begin
  if IsCalculated or Computed then
    Result := false
  else
  begin
    CheckInfoValid;
    Result := FIsDefaulted;
  end;
end;

function TIB_Column.GetIsAlternate: boolean;
begin
  if IsCalculated or Computed then
    Result := false
  else
  begin
    CheckInfoValid;
    Result := FIsAlternate;
  end;
end;

function TIB_Column.GetIsIndexField: boolean;
begin
  if IsCalculated or Computed then
    Result := false
  else
  begin
    CheckInfoValid;
    Result := FIsIndexField;
  end;
end;

function TIB_Column.GetDefaultWidth: integer;
begin
  Result := CalcDefaultWidth( False );
end;

function TIB_Column.GetDisplayWidth: integer;
begin
  if FDisplayWidth >= 0 then
    Result := FDisplayWidth
  else
    Result := CalcDefaultWidth( True );
end;

function TIB_Column.CalcDefaultWidth( IsGrid: boolean ): integer;
var
  DataWidth: integer;
  HeaderWidth: integer;
begin
  if IsGrid then
    HeaderWidth := Length( Trim( GridDisplayName ) )
  else
    HeaderWidth := Length( Trim( DisplayName ) );
  if ( SQLType = SQL_TIMESTAMP ) or
     ( SQLType = SQL_TIMESTAMP_ ) or
     ( SQLType = SQL_QUAD ) or
     ( SQLType = SQL_QUAD_ ) or
     ( SQLType = SQL_INT64 ) or
     ( SQLType = SQL_INT64_ ) then
    DataWidth := 16
  else
    DataWidth := SQLLen;
  if HeaderWidth > DataWidth then
    Result := HeaderWidth * 8
  else
    Result := DataWidth * 8;
  if Result < 70 then
    Result := 70;
  if Result > 200 then
    Result := 200;
end;


{$IFDEF IBO_VCL30_OR_GREATER}
function TIB_Column.GetAsWideString: widestring;
begin
  raise Exception.Create( E_Not_implemented );
end;

procedure TIB_Column.SetAsWideString( const NewValue: WideString );
begin
  raise Exception.Create( E_Not_implemented );
end;
{$ENDIF}

function TIB_Column.GetAsRawString: string;
var
  tmpLen: word;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := ''
  else
    case SQLType of
      SQL_Text,
      SQL_Text_:
        Result := Copy( Pchar( FNewColumnBuffer ), 1, SQLLen );
      SQL_Varying,
      SQL_Varying_: with SQL_VARCHAR( FNewColumnBuffer^ ) do
      begin
        tmpLen := vary_len_low + vary_len_high * 256;
        SetLength( Result, tmpLen );
        Move( vary_string, pchar( Result )^, tmpLen );
      end
      else
        Result := AsString;
    end;
end;

procedure TIB_Column.SetAsRawString( const NewValue: string );
var
  tmpLen: word;
  tmpVal: string;
begin
  tmpVal := NewValue;
  tmpLen := Length( tmpVal );
  case SQLType of
    SQL_Text,
    SQL_Text_:
    begin
      if integer( tmpLen ) > SQLLen then
        raise Exception.Create( E_String_truncation + ' ' + BestFieldName );
      SysBeforeModify;
      FillChar( FNewColumnBuffer^, DataSize, FPadChar );
      if ( tmpLen = 0 ) and FBlankIsNull then
        SysSetIsNull( true )
      else
        SysSetIsNull( false );
      Move( tmpVal[1], FNewColumnBuffer^, tmpLen );
      SysAfterModify;
    end;
    SQL_Varying,
    SQL_Varying_: with SQL_VARCHAR( FNewColumnBuffer^ ) do
    begin
      if integer( tmpLen ) > SQLLen then
        raise Exception.Create( E_String_truncation + ' ' + BestFieldName );
      SysBeforeModify;
      FillChar( vary_string, SQLLen, FPadChar );
      if ( tmpLen = 0 ) and FBlankIsNull then
        SysSetIsNull( true )
      else
        SysSetIsNull( false );
      vary_len_low  := tmpLen mod 256;
      vary_len_high := tmpLen div 256;
      Move( tmpVal[1], vary_string, tmpLen );
      SysAfterModify;
    end
    else
      AsString := tmpVal;
  end;
end;

function TIB_Column.GetAsDate: TDateTime;
begin
  if AsString = '' then
    Result := 0
  else
    Result := Trunc( StrToDateTime( AsString ));
end;

function TIB_Column.GetAsDateTime: TDateTime;
begin
  if AsString = '' then
    Result := 0
  else
    Result := StrToDateTime( AsString );
end;

function TIB_Column.GetAsDateTimeEncodeString: string;
begin
  // Validate the string as begin a DateTimeEncodeString
  // (or should I skip this and assume it will be ok?)
  EncodeStringToDateTime( AsString );
  Result := AsString;
end;

procedure TIB_Column.SetAsDate( const NewValue: TDateTime );
begin
  AsString := DateTimeToStr( Trunc( NewValue ));
end;

procedure TIB_Column.SetAsDateTime( const NewValue: TDateTime );
begin
  AsString := DateTimeToStr( NewValue );
end;

procedure TIB_Column.SetAsDateTimeEncodeString( const NewValue: string );
begin
  // Validate the NewValue as begin a DateTimeEncodeString
  // (or should I skip this and assume it will be ok?)
  EncodeStringToDateTime( NewValue );
  AsString := NewValue;
end;

function TIB_Column.GetAsBoolean: boolean;
begin
  Result := not IsNull;
  if Result then
  begin
    case SQLType of
      SQL_BOOLEAN,
      SQL_BOOLEAN_:
        if PShort(FNewColumnBuffer)^ = ISC_TRUE then
          Result := true
        else
        if PShort(FNewColumnBuffer)^ = ISC_FALSE then
          Result := false
        else
          raise Exception.Create( E_Unexpected_Boolean_Value );
      else
        Result := AnsiCompareText( Trim( AsString ), BooleanTrue ) = 0;
    end;
  end;
end;

procedure TIB_Column.SetAsBoolean( const NewValue: boolean);
begin
  if NewValue then
    AsString := BooleanTrue
  else
    AsString := BooleanFalse;
end;

function TIB_Column.GetAsInteger: integer;
begin
  if Trim( AsString ) = '' then
    Result := 0
  else
    try
      Result := StrToInt( Trim( AsString ));
    except
      Result := Trunc( StrToFloat( Trim( AsString )));
    end;
end;

procedure TIB_Column.SetAsInteger( const NewValue: integer);
begin
  AsString := IntToStr( NewValue );
end;

function TIB_Column.GetAsInt64: ISC_INT64;
begin
{$IFDEF IBO_VCL35}
  raise Exception.Create( 'Unsupported' );
{$ELSE}
  if Trim( AsString ) = '' then
    Result := 0
  else
{$IFDEF IBO_VCL40_OR_GREATER}
    Result := StrToInt64( Trim( AsString ));
{$ELSE}
{$IFNDEF IBO_ASCOMP_SUPPORT}
    raise Exception.Create( Format( E_UNSUPPORTED_COLUMN, [ SQLType ] ));
{$ELSE}
    comp(Result) := AsComp;
{$ENDIF}
{$ENDIF}
{$ENDIF}
end;

procedure TIB_Column.SetAsInt64( const NewValue: ISC_INT64 );
begin
{$IFDEF IBO_VCL40_OR_GREATER}
  AsString := IntToStr( NewValue );
{$ELSE}
{$IFNDEF IBO_ASCOMP_SUPPORT}
    raise Exception.Create( Format( E_UNSUPPORTED_COLUMN, [ SQLType ] ));
{$ELSE}
  AsComp := comp(NewValue);
{$ENDIF}
{$ENDIF}
end;

function TIB_Column.GetAsFloat: double;
begin
  if AsString = '' then begin
    Result := 0;
  end else begin
    Result := StrToFloat( AsString );
  end;
end;

procedure TIB_Column.SetAsFloat( const NewValue: double );
begin
  AsString := FloatToStr( NewValue );
end;

function TIB_Column.GetAsSmallint: smallint;
begin
  if AsString = '' then begin
    Result := 0;
  end else begin
    Result := StrToInt( Trim( AsString ));
  end;
end;

procedure TIB_Column.SetAsSmallint( const NewValue: smallint);
begin
  AsString := IntToStr( NewValue );
end;

function TIB_Column.GetAsDouble: double;
begin
  if AsString = '' then begin
    Result := 0;
  end else begin
    Result := StrToFloat( AsString );
  end;
end;

procedure TIB_Column.SetAsDouble( const NewValue: double);
begin
  AsString := FloatToStr( NewValue );
end;

function TIB_Column.GetAsCurrency: currency;
begin
  if AsString = '' then
    Result := 0
  else
    Result := StrToCurr( AsString );
end;

procedure TIB_Column.SetAsCurrency( const NewValue: currency);
begin
  AsString := CurrToStr( NewValue );
end;

{$IFNDEF IBO_VCL40_OR_GREATER}
// This fixes a loss of precision problem with D3 and FloatToStr
function CompToStr(n: comp): String;
var
   o: comp;
   s: string;
begin
   if (n > -1E18) and (n < 1E18)
   then Result := FloatToStrF(n, ffFixed, 18, 0)
   else if n > 0
   then begin
      s := FloatToStr(n);
      o := n - 1E18 * StrToInt(s[1]);
      Result := s[1] + FloatToStrF(o, ffFixed, 18, 0)
   end
   else begin
      s := FloatToStr(n);
      o := n - 1E18 * -StrToInt(s[2]);
      Result := FloatToStrF(o, ffFixed, 18, 0);
      Insert(s[2], Result, 2)
   end
end;
{$ENDIF}

{$IFDEF IBO_ASCOMP_SUPPORT}
function TIB_Column.GetAsComp: comp;
begin
  if AsString = '' then
    Result := 0
  else
    Result := StrToFloat( AsString );
end;

procedure TIB_Column.SetAsComp( const NewValue: comp );
begin
{$IFDEF IBO_VCL40_OR_GREATER}
  AsString := FloatToStr( NewValue );
{$ELSE}
  AsString := CompToStr( NewValue );
{$ENDIF}
end;

{$ENDIF}

function TIB_Column.GetAsExtended: extended;
var
  tmpS: string;
begin
  tmpS := AsString;
  if tmpS = '' then
    Result := 0
  else
  case SQLType of
    SQL_FLOAT,
    SQL_FLOAT_: Result := AsFloat;
    SQL_DOUBLE,
    SQL_DOUBLE_: Result := AsDouble;
    else if SQLScale = 0 then
    begin
      case SQLType of
        SQL_INT64,
        SQL_INT64_: Result := AsInt64;
        else Result := StrToFloat( tmpS )
      end;
    end
    else
      Result := StrToFloat( tmpS );
  end;
end;

procedure TIB_Column.SetAsExtended( const NewValue: extended );
begin
  AsString := FloatToStr( NewValue );
end;

// Avoiding having to load in the ActiveX unit.
const
  VT_DECIMAL_X = 14;

type
  tagDEC_X = packed record
    wReserved: Word;
    case Integer of
      0: (scale, sign: Byte; Hi32: Longint;
      case Integer of
        0: (Lo32, Mid32: Longint);
        1: (Lo64: LONGLONG));
      1: (signscale: Word);
  end;

function TIB_Column.GetAsVariant: Variant;
begin
  if Row.RowState = rsNone then
    Result := Unassigned
  else
  if IsNull then
    Result := Null
  else
  case SQLType of
    SQL_VARYING,
    SQL_VARYING_,
    SQL_TEXT,
    SQL_TEXT_: Result := AsString;
    SQL_FLOAT,
    SQL_FLOAT_: Result := AsFloat;
    SQL_DOUBLE,
    SQL_DOUBLE_: Result := AsDouble;
    SQL_SHORT,
    SQL_SHORT_: if SQLScale = 0 then
      Result := AsSmallint
    else
    begin
      if FIsCurrencyDataType then
        Result := AsString
      else
        Result := AsExtended;
    end;
    SQL_LONG,
    SQL_LONG_: if SQLScale = 0 then
      Result := AsInteger
    else
    begin
      if IsCurrencyDataType then
        Result := AsString
      else
        Result := AsExtended;
    end;
    SQL_QUAD,
    SQL_QUAD_,
    SQL_INT64,
    SQL_INT64_: if SQLScale = 0 then
      Result := AsString // variant support for int64 is not reliable
    else
      begin
        if IsCurrencyDataType then
          Result := AsString // only reliable way of maintaining accuracy
        else
          Result := AsExtended;
      end;
    SQL_TYPE_DATE,
    SQL_TYPE_DATE_: Result := AsDate;
    SQL_TYPE_TIME,
    SQL_TYPE_TIME_,
    SQL_TIMESTAMP,
    SQL_TIMESTAMP_: Result := AsDateTime;
    SQL_BOOLEAN,
    SQL_BOOLEAN_: Result := AsBoolean;
    SQL_BLOB,
    SQL_BLOB_: Result := AsString;
//    SQL_ARRAY,
//    SQL_ARRAY_: {should be overridden} ;
    else
      raise EIB_Error.CreateWithSender( Statement,
                                        Format( E_UNSUPPORTED_COLUMN,
                                                [ SQLType ] ));
  end;
end;

procedure TIB_Column.SetAsVariant( const NewValue: Variant );
{$IFDEF IBO_VCL60_OR_GREATER}
{$ELSE}
var
  tmpExt: extended;
{$IFDEF IBO_VCL40_OR_GREATER}
  tmpInt64: int64;
{$ENDIF}
{$ENDIF}
var
  WasNull: boolean;
  WasChanged: boolean;
begin
  if VarIsNull( NewValue ) or VarIsEmpty( NewValue ) then
  begin
    if not IsNull then
      IsNull := true;
  end
  else
  begin
    WasChanged := false;
    WasNull := IsNull;
    SysSetIsNull( false );
    if VarType( NewValue ) = varString then
    begin
      if AsString <> NewValue then
      begin
        WasChanged := true;
        AsString := NewValue;
      end;
    end
    else
    case SQLType of
      SQL_VARYING,
      SQL_VARYING_,
      SQL_TEXT,
      SQL_TEXT_: if AsString <> VarAsType( NewValue, varString ) then
      begin
        WasChanged := true;
        AsString := VarAsType( NewValue, varString );
      end;
      SQL_FLOAT,
      SQL_FLOAT_: if AsFloat <> NewValue then
      begin
        WasChanged := true;
        AsFloat := NewValue;
      end;
      SQL_DOUBLE,
      SQL_DOUBLE_: if AsDouble <> NewValue then
      begin
        WasChanged := true;
        AsDouble := NewValue;
      end;
      SQL_SHORT,
      SQL_SHORT_: if SQLScale = 0 then
      begin
        if AsSmallint <> NewValue then
        begin
          WasChanged := true;
          AsSmallint := NewValue;
        end;
      end
      else
      begin
        if AsExtended <> NewValue then
        begin
          WasChanged := true;
          AsExtended := NewValue;
        end;
      end;
      SQL_LONG,
      SQL_LONG_: if SQLScale = 0 then
      begin
        if AsInteger <> NewValue then
        begin
          WasChanged := true;
          AsInteger := NewValue;
        end;
      end
      else
      begin
        if AsExtended <> NewValue then
        begin
          WasChanged := true;
          AsExtended := NewValue;
        end;
      end;
      SQL_QUAD,
      SQL_QUAD_,
      SQL_INT64,
      SQL_INT64_: begin
        // We can leave this here despite having changed GetAsVariant to
        // return AsString for these types.  There is a check above for
        // vartype of varString which will detect values generated by other
        // column instances, this code can be used to accept values from
        // other sources that are not given in string format.
  {$IFDEF IBO_VCL60_OR_GREATER}
        if SQLScale = 0 then
        begin
//          if AsInt64 <> NewValue then AsInt64 := NewValue;
          if AsExtended <> NewValue then
          begin
            WasChanged := true;
            AsExtended := NewValue;
          end;
        end
        else
        begin
          if AsExtended <> NewValue then
          begin
            WasChanged := true;
            AsExtended := NewValue;
          end;
        end;
  {$ELSE}
        tmpExt := NewValue;
  {$IFDEF IBO_VCL40_OR_GREATER}
        if TVarData(NewValue).VType = VT_DECIMAL_X then
        begin
          WasChanged := true;
          tmpInt64 := tagDEC_X(NewValue).lo64;
        end
        else
        begin
          WasChanged := true;
          tmpInt64 := Trunc( tmpExt );
        end;
        if SQLScale = 0 then
        begin
          if AsInt64 <> tmpInt64 then
          begin
            WasChanged := true;
            AsInt64 := tmpInt64;
          end;
        end
        else
        if AsExtended <> tmpExt then
        begin
          WasChanged := true;
          AsExtended := tmpExt;
        end;
  {$ELSE}
        if AsExtended <> tmpExt then
        begin
          WasChanged := true;
          AsExtended := tmpExt;
        end;
  {$ENDIF}
  {$ENDIF}
      end;
      SQL_TYPE_DATE,
      SQL_TYPE_DATE_: if AsDate <> Trunc( NewValue ) then
      begin
        WasChanged := true;
        AsDate := Trunc( NewValue );
      end;
      SQL_TYPE_TIME,
      SQL_TYPE_TIME_: if AsDateTime <> Frac( NewValue ) then
      begin
        WasChanged := true;
        AsDateTime := Frac( NewValue );
      end;
      SQL_TIMESTAMP,
      SQL_TIMESTAMP_: if AsDateTime <> NewValue then
      begin
        WasChanged := true;
        AsDateTime := NewValue;
      end;
      SQL_BOOLEAN,
      SQL_BOOLEAN_: if AsBoolean <> NewValue then
      begin
        WasChanged := true;
        AsBoolean := NewValue;
      end;
      SQL_BLOB,
      SQL_BLOB_: if AsString <> NewValue then
      begin
        WasChanged := true;
        AsString := NewValue;
      end
  //  SQL_ARRAY,
  //  SQL_ARRAY_: {should be overridden} ;
      else
        raise EIB_Error.CreateWithSender( Statement,
                                          Format( E_UNSUPPORTED_COLUMN,
                                                  [ SQLType ]));
    end;
    if ( not WasChanged ) and WasNull then
    begin
      SysSetIsNull( true );
      IsNull := false;
    end;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Column.SysBeforeModify;
begin
  if not Assigned( FValidateBuffer ) then
    GetMem( FValidateBuffer, DataSize + SizeOf( smallint ));
  if not FValidating then
    Move( FNewColumnInd^, FValidateBuffer^, DataSize + SizeOf( smallint ));
end;

procedure TIB_Column.SysAfterModify;
var
  WasActuallyChanged: boolean;
begin
  if not FValidating then
    try
      FValidating := true;
      try
        if Assigned( FOnValidate ) then
          FOnValidate( Self );
        with Statement do
          if Assigned( FOnValidateField ) then
            FOnValidateField( Statement, Row, Self );
      except
        Move( FValidateBuffer^, FNewColumnInd^, DataSize + SizeOf(smallint));
        raise;
      end;
    finally
      FValidating := false;
    end;
  WasActuallyChanged := SysInternalChanged;
  if WasActuallyChanged or ( Row.FStrictModifyOnlyLevel = 0 ) then
  begin
    if (( FieldNo >= 0 ) or ( not Row.Statement.CalculatingFields )) and
       ( Statement is TIB_Dataset ) and
       ( not ( TIB_Dataset( Statement ).NeedToPost )) then
    begin
      if not Assigned( FPreserveBuffer ) then
        GetMem( FPreserveBuffer, DataSize + SizeOf( smallint ));
      Move( FNewColumnInd^, FPreserveBuffer^, DataSize + SizeOf(smallint));
    end;
    try
      DoBeforeModify;
      if (( FieldNo >= 0 ) or ( not Row.Statement.CalculatingFields )) then
        FRow.SysBeforeColumnModify( Self );
      if Assigned( FPreserveBuffer ) then
      begin
        Move( FPreserveBuffer^, FNewColumnInd^, DataSize + SizeOf(smallint));
        FreeMem( FPreserveBuffer );
        FPreserveBuffer := nil;
      end;
    except
      Move( FValidateBuffer^, FNewColumnInd^, DataSize + SizeOf(smallint));
      raise;
    end;
    if not IsArray then  // This is necessary for now.
      FRow.SysAfterColumnModify( Self );
  end;
  FBound := not IsNull;
  if WasActuallyChanged then
    DoAfterModify;
end;

function TIB_Column.SysInternalChanged: boolean;
var
  tmpSize: integer;
begin
// Need to do this in a way that will ignore white space.
  tmpSize := SizeOf( smallint );
  if FNewColumnInd^ <> IB_NULL then
    case SQLType of
      SQL_VARYING,
      SQL_VARYING_: with SQL_VARCHAR( FNewColumnBuffer^ ) do
        Inc( tmpSize, vary_len_high * 256 + vary_len_low + SizeOf( smallint ));
      else
        Inc( tmpSize, DataSize );
    end;
  Result := not BuffersEqual( FValidateBuffer, FNewColumnInd, tmpSize );
end;

{------------------------------------------------------------------------------}

procedure TIB_Column.SetAlignment( AValue: TAlignment );
begin
  if FAlignment <> AValue then
    with Statement.FieldsAlignment do
      case AValue of
        taLeftJustify:  LinkValues[ BestFieldName ] := 'LEFT';
        taRightJustify: LinkValues[ BestFieldName ] := 'RIGHT';
        taCenter:       LinkValues[ BestFieldName ] := 'CENTER';
      end;
end;

procedure TIB_Column.SetCharCase( AValue: TIB_CharCase );
begin
  if FCharCase <> AValue then
    with Statement.FieldsCharCase do
      case AValue of
        ccUpper:  LinkValues[ BestFieldName ] := 'UPPER';
        ccLower:  LinkValues[ BestFieldName ] := 'LOWER';
        ccProper: LinkValues[ BestFieldName ] := 'PROPER';
        else      LinkValues[ BestFieldName ] := 'NORMAL';
      end;
end;

function TIB_Column.GetDisplayFormat: string;
begin
  Result := FDisplayFormat;
end;

procedure TIB_Column.SetDisplayFormat( const AValue: string );
begin
  if FDisplayFormat <> AValue then
    with Statement.FieldsDisplayFormat do
      LinkValues[ BestFieldName ] := AValue;
end;

procedure TIB_Column.SetDisplayLabel( const AValue: string );
begin
  if FDisplayLabel <> AValue then
    with Statement.FieldsDisplayLabel do
      if AValue = '' then
        LinkValues[ BestFieldName ] := '-'
      else
      if AValue = '-' then
        LinkValues[ BestFieldName ] := '/-'
      else
        LinkValues[ BestFieldName ] := AValue;
end;

procedure TIB_Column.SetGridDisplayLabel( const AValue: string );
begin
  if FGridDisplayLabel <> AValue then
    with Statement.FieldsGridLabel do
      if AValue = '' then
      begin
        LinkValues[ BestFieldName ] := '-';
      end else if AValue = '-' then
      begin
        LinkValues[ BestFieldName ] := '/-';
      end else
        LinkValues[ BestFieldName ] := AValue;
end;

procedure TIB_Column.SetGridTitleHint( const AValue: string );
begin
  if FGridTitleHint <> AValue then
    with Statement.FieldsGridTitleHint do
      LinkValues[ BestFieldName ] := AValue;
end;

procedure TIB_Column.SetDisplayWidth( AValue: integer );
begin
  if DisplayWidth <> AValue then
    with Statement.FieldsDisplayWidth do
      if AValue = GetDefaultWidth then
      begin
        FDisplayWidth := -1;
        LinkValues[ BestFieldName ] := '';
      end
      else
        LinkValues[ BestFieldName ] := IntToStr( AValue );
end;

function TIB_Column.GetEditMask: string;
var
  tmpInt: integer;
  tmpS: string;
begin
// FEditMask may have been assigned directly in IB_Statement so we always
// have to check whether we need to do enhanced processing.
// Edit masks for use with mask processor interfaces must have a six character
// prefix in the form "::XX::" where "XX" standards for the mask type.  Here I
// will only bother checking for the first "::" characters - which leaves the
// identification characters open for expansion later.
  if ( FMaskIntf = nil ) and ( Length( FEditMask ) >= 6 ) and
     ( Copy( FEditMask, 1, 2 ) = '::' ) then
    SetEditMask( FEditMask );
  Result := FEditMask;
  if Result = '' then
  begin
    if IsDateTime then
    begin
      if IsDateOnly then
      begin
        tmpS := UpperCase( FDateOnlyFmt );
        tmpInt := Pos( 'DD' + DateSeparator, tmpS );
        if tmpInt > 0 then
          System.Delete( tmpS, tmpInt, 3 );
        tmpInt := Pos( 'D' + DateSeparator, tmpS );
        if tmpInt > 0 then
          System.Delete( tmpS, tmpInt, 2 );
        tmpInt := Pos( 'MM' + DateSeparator, tmpS );
        if tmpInt > 0 then
          System.Delete( tmpS, tmpInt, 3 );
        tmpInt := Pos( 'M' + DateSeparator, tmpS );
        if tmpInt > 0 then
          System.Delete( tmpS, tmpInt, 2 );
        tmpInt := Pos( 'YY', tmpS );
        if tmpInt > 0 then
          System.Delete( tmpS, tmpInt, 2 );
        tmpInt := Pos( 'YY', tmpS );
        if tmpInt > 0 then
          System.Delete( tmpS, tmpInt, 2 );
        if tmpS = '' then
          Result := '!99' + DateSeparator + '99' + DateSeparator + '9999;1; ';
      end
      else
      if IsTimeOnly then
        Result := '!99' + TimeSeparator + '99' + TimeSeparator + '99;1; ';
    end;
  end
  else
  begin
    tmpInt := Pos( '%d', LowerCase( Result ));
    while tmpInt > 0 do
    begin
      Delete( Result, tmpInt, 2 );
      Insert( DateSeparator, Result, tmpInt );
      tmpInt := Pos( '%d', LowerCase( Result ));
    end;
    tmpInt := Pos( '%t', LowerCase( Result ));
    while tmpInt > 0 do
    begin
      Delete( Result, tmpInt, 2 );
      Insert( TimeSeparator, Result, tmpInt );
      tmpInt := Pos( '%t', LowerCase( Result ));
    end;
  end;
end;

procedure TIB_Column.SetEditMask( const AValue: string );
begin
  // Call to LinkValues will reset FEditMask directly, no need to assign
  // the value within this procedure.
  if FEditMask <> AValue then
    with Statement.FieldsEditMask do
      LinkValues[ BestFieldName ] := AValue;
  // Always free any previously used processor, the next mask may require
  // a different processor.
  FreeMaskIntf;
  // If necessary create mask processor to handle the mask definition
  if (Length(FEditMask) >= 6) and (Copy(FEditMask,1,2) = '::') and
    assigned(IB_CreateMaskProcessor) then
    FMaskIntf := IB_CreateMaskProcessor(FEditMask);
end;

function TIB_Column.IsMasked: boolean;
begin
  if FMaskIntf = nil then
    Result := ( EditMask <> '' )
  else
    Result := FMaskIntf.IsMasked;
end;

function TIB_Column.EmptyMaskText: string;
begin
  if FMaskIntf = nil then
    Result := FormatMaskText( EditMask, '' )
  else
    Result := FMaskIntf.EmptyText;
end;

function TIB_Column.FormatTextWithMask( const SourceStr: string ): string;
begin
  // Ensure IsMasked or GetEditMask is called before this function
  // to ensure that FMaskIntf has been created if necessary.
  if FMaskIntf = nil then
  begin
    // IBO standards with this use the DisplayFormat if provided
    // otherwise the EditFormat (rather than the other way around)
    if DisplayFormat <> '' then
      Result := FormatMaskText( DisplayFormat, SourceStr )
    else
    begin
      if EditMask <> '' then
        Result := FormatMaskText( EditMask, SourceStr )
      else
        Result := SourceStr;
    end;
  end
  else
  begin
    // Mask processor are not expected to need a separate DisplayFormat
    // I could still use the DisplayFormat to allow different display
    // to edit layout but this means resetting the mask which is more
    // overhead for very little gain.  Might change my mind later :-)
    if IsMasked then
      Result := FMaskIntf.FormatText( SourceStr )
    else
      Result := SourceStr;
  end;
end;

function TIB_Column.GetIndex: Integer;
begin
  if Assigned( Row ) then begin
    Result := Row.FColumnList.IndexOf( Self );
  end else begin
    Result := -1;
  end;
end;

procedure TIB_Column.SetIndex( AValue: integer );
var
  CurIndex, Count: Integer;
begin
  CurIndex := GetIndex;
  if CurIndex >= 0 then
  begin
    Count := Row.FColumnList.Count;
    if AValue < 0 then
      AValue := 0;
    if AValue >= Count then
      AValue := Count - 1;
    if AValue <> CurIndex then
    begin
      Row.FColumnList.Move( CurIndex, AValue );
      with Statement do
      begin
        if ( Row.RowType = rtField ) and
           ( FIgnoreLayoutChange = 0 ) then
        begin
          Inc( FIgnoreLayoutChange );
          FieldsIndex.BeginUpdate;
          try
            FieldsIndex.Clear;
            for CurIndex := 0 to FieldCount - 1 do
              with Fields[ CurIndex ] do
                FieldsIndex.Add( BestFieldName );
          finally
            FieldsIndex.EndUpdate;
            Dec( FIgnoreLayoutChange );
          end;
          SysLayoutChanged;
        end;
      end;
    end;
  end;
end;

procedure TIB_Column.SetPreventEditing( AValue: boolean );
begin
  if FPreventEditing <> AValue then
    with Statement.FieldsReadOnly do
      LinkParamIsSet[ BestFieldName, IBO_NOEDIT ] := AValue;
end;

procedure TIB_Column.SetPreventInserting( AValue: boolean );
begin
  if FPreventInserting <> AValue then
    with Statement.FieldsReadOnly do
      LinkParamIsSet[ BestFieldName, IBO_NOINSERT ] := AValue;
end;

procedure TIB_Column.SetPreventSearching( AValue: boolean );
begin
  if FPreventSearching <> AValue then
    with Statement.FieldsReadOnly do
      LinkParamIsSet[ BestFieldName, IBO_NOSEARCH ] := AValue;
end;

procedure TIB_Column.SetForceControlsReadOnly( AValue: boolean );
begin
  if ( AValue <> FForceControlsReadOnly ) then
  begin
    FForceControlsReadOnly := AValue;
    if ( Row.RowType <> rtParam ) then
    begin
      if Statement is TIB_Dataset then
        (Statement as TIB_Dataset).StateChanged
      else
// This was making things REALLY slow when doing a bunch of adjustments
// to only a few columns. I'll have to look into this better and see if it is
// even necessary at all. StateChange should do all that is needed for datasets.
// I moved it from being executed all the time to only if it is connected to a
// statement.
        with Statement do
          LayoutChange( FieldsReadOnly );
    end;
  end;
end;

procedure TIB_Column.SetTrimming( AValue: TIB_ColumnTrimming );
var
  t: TNotifyEvent;
begin
  if FTrimming <> AValue then
  begin
    t := Statement.FieldsTrimming.OnChange;
    Statement.FieldsTrimming.OnChange := nil;
    try
      with Statement.FieldsTrimming do
      begin
        case AValue of
          ctNone: LinkValues[ BestFieldName ] := 'NONE';
          ctAll: LinkValues[ BestFieldName ] := 'ALL';
          ctBoth: LinkValues[ BestFieldName ] := 'BOTH';
          ctLeft: LinkValues[ BestFieldName ] := 'LEFT';
          ctRight: LinkValues[ BestFieldName ] := 'RIGHT';
          ctSentence: LinkValues[ BestFieldName ] := 'SENTENCE';
          ctNoneCheckLen: LinkValues[ BestFieldName ] := 'CNONE';
          ctAllCheckLen: LinkValues[ BestFieldName ] := 'CALL';
          ctBothCheckLen: LinkValues[ BestFieldName ] := 'CBOTH';
          ctLeftCheckLen: LinkValues[ BestFieldName ] := 'CLEFT';
          ctRightCheckLen: LinkValues[ BestFieldName ] := 'CRIGHT';
          ctSentenceCheckLen: LinkValues[ BestFieldName ] := 'CSENTENCE';
        end;
      end;
    finally
      Statement.FieldsTrimming.OnChange := t;
    end;
  end;
end;

procedure TIB_Column.SetVisible( AValue: boolean );
begin
  if FVisible <> AValue then
    with Statement.FieldsVisible do
      if AValue then
        LinkValues[ BestFieldName ] := ''
      else
        LinkValues[ BestFieldName ] := 'FALSE';
end;

function TIB_Column.GetDisplayName: string;
begin
  if FDisplayLabel = '-' then
    Result := ''
  else
  if FDisplayLabel = '/-' then
    Result := '-'
  else
  if FDisplayLabel <> '' then
    Result := FDisplayLabel
  else
  begin
    Result := FieldName;
    if isLitCriteria( Result, '"' ) then
      Result := stLitCriteria( Result );
  end;
end;

function TIB_Column.GetDisplayLabel: string;
begin
  if FDisplayLabel = '-' then
    Result := ''
  else
  if FDisplayLabel = '/-' then
    Result := '-'
  else
  if FDisplayLabel <> '' then
    Result := FDisplayLabel
  else
  begin
    Result := FieldName;
    if not isLitCriteria( Result, '"' ) then
      replace_string( Result, '_', ' ' )
    else
      Result := stLitCriteria( Result );
  end;
end;

function TIB_Column.GetGridDisplayName: string;
begin
  if FGridDisplayLabel = '-' then
    Result := ''
  else
  if FGridDisplayLabel = '/-' then
    Result := '-'
  else
  if FGridDisplayLabel <> '' then
    Result := FGridDisplayLabel
  else
    Result := GetDisplayName;
end;

function TIB_Column.GetGridDisplayLabel: string;
begin
  if FGridDisplayLabel = '-' then
    Result := ''
  else
  if FGridDisplayLabel = '/-' then
    Result := '-'
  else
  if FGridDisplayLabel <> '' then
    Result := FGridDisplayLabel
  else
    Result := GetDisplayLabel;
end;

function TIB_Column.GetDisplayText: string;
begin
  if IsNull then
  begin
    if ( SQLType = SQL_BOOLEAN ) or
       ( SQLType = SQL_BOOLEAN_ ) then
      Result := 'UNKNOWN'
    else
      Result := ''
  end
  else
  if DisplayFormat <> '' then
  begin
    if IsNumeric then
    begin
      if FIsCurrencyDataType then
        Result := FormatCurr( DisplayFormat, AsCurrency )
      else
        Result := FormatFloat( DisplayFormat, AsExtended );
    end
    else begin
      if IsDateTime then
        Result := FormatDateTime( DisplayFormat, AsDateTime )
      else
        Result := FormatTextWithMask( AsString );
    end;
  end
  else
  begin
    if IsDateTime then
    begin
      if ( SQLType = SQL_TYPE_DATE ) or
         ( SQLType = SQL_TYPE_DATE_ ) or
         IsDateOnly then
        Result := FormatDateTime( ShortDateFormat, AsDateTime )
      else
      if ( SQLType = SQL_TYPE_TIME ) or
         ( SQLType = SQL_TYPE_TIME_ ) or
         IsTimeOnly then
        Result := FormatDateTime( ShortTimeFormat, AsDateTime )
      else
        Result := DateTimeToStr( AsDateTime );
    end
    else
    if IsMasked then
      Result := FormatTextWithMask( AsString )
    else
    if IsCurrency then
    begin
      if FIsCurrencyDataType then
        Result := CurrToStrF( AsCurrency,
                              ffCurrency,
                              CurrencyDecimals )
      else
        Result := FloatToStrF( AsExtended,
                               ffCurrency,
                               18{-SQLScale},
                               CurrencyDecimals )
    end
    else
      Result := AsString;
  end;
end;

function TIB_Column.GetAttributeParamsEx( const AParam: string;
                                            var IsSet: boolean ): string;
begin
  Result := GetColParamValueEx( Self, AParam, IsSet,
                                Statement.ColumnAttributes,
                                Statement.IB_Connection.ColumnAttributes );
end;

function TIB_Column.GetAttributeParams( const AParam: string ): string;
var
  IsSet: boolean;
begin
  Result := GetAttributeParamsEx( AParam, IsSet );
end;

procedure TIB_Column.SetAttributeParams( const AParam: string; AValue: string );
begin
  Statement.ColumnAttributes.LinkParamValue[ BestFieldName, AParam ] := AValue;
end;

// Updated to exclude Boolean fields from Default processing.
function TIB_Column.GetIsAttributeSet( const AParam: string ): boolean;
var
  BParam: string;
  BResult: boolean;
begin
  BParam := '';
  if AParam <> IBO_BOOLEAN then // we dont want to setup a loop
  begin
    if not IsBoolean then
    begin
      if ( Statement.IB_Connection.DefaultNoCase ) and
         ( AParam = IBO_NOCASE ) then
        BParam := IBO_YESCASE
      else
      if ( Statement.IB_Connection.DefaultNoTrailing ) and
                  ( AParam = IBO_NOTRAILING ) then
        BParam := IBO_YESTRAILING;
    end;
  end;
  if BParam <> '' then
  begin
    with Statement do
    begin
      BResult := false;
      Result := ColumnAttributes.LinkParamIsSet[ FullFieldName, AParam ];
      if ( not Result ) and ( FullFieldName <> FullSQLName ) then
        Result := ColumnAttributes.LinkParamIsSet[ FullSQLName, AParam ];
      if ( not Result ) then
      begin
        BResult := ColumnAttributes.LinkParamIsSet[ FullFieldName, BParam ];
        if ( not BResult ) and ( FullFieldName <> FullSQLName ) then
          BResult := ColumnAttributes.LinkParamIsSet[ FullSQLName, BParam ];
      end;
      if (not BResult) and (not Result) and Assigned( IB_Connection ) then
      begin
        with IB_Connection do if ( ColumnAttributes.Count > 0 ) then
        begin
          Result := ColumnAttributes.LinkParamIsSet[ FullFieldName, AParam ];
          if ( not Result ) and ( FullFieldName <> FullSQLName ) then
            Result := ColumnAttributes.LinkParamIsSet[ FullSQLName, AParam ];
          if ( not Result ) then
          begin
            BResult := ColumnAttributes.LinkParamIsSet[ FullFieldName, BParam ];
            if ( not Result ) and ( FullFieldName <> FullSQLName ) then
              BResult := ColumnAttributes.LinkParamIsSet[ FullSQLName, BParam ];
          end;
          if (not BResult) and (not Result) and
             ( fetDomainName in IB_Connection.FieldEntryTypes ) then
          begin
            Result := ColumnAttributes.LinkParamIsSet[ DomainName, AParam ];
            if (not Result) then
              BResult := ColumnAttributes.LinkParamIsSet[ DomainName, BParam ];
            if (not Result) and (not BResult) then
              Result := true;
          end;
        end;
      end;
    end;
    if not BResult then
      Result := not BResult;
  end
  else
    with Statement do
    begin
      Result := ColumnAttributes.LinkParamIsSet[ BestFieldName, AParam ];
      if ( not Result ) and Assigned( IB_Connection ) then
      begin
        with IB_Connection do if ( ColumnAttributes.Count > 0 ) then
        begin
        // Don't use BestFieldName here. COMPUTED don't work well.
          Result := ColumnAttributes.LinkParamIsSet[ FullFieldName, AParam ];
          if ( not Result ) and
             ( fetDomainName in IB_Connection.FieldEntryTypes ) and
             ( DomainName <> '' ) then
            Result := ColumnAttributes.LinkParamIsSet[ DomainName, AParam ];
        end;
      end;
    end;
end;

procedure TIB_Column.SetIsAttributeSet( const AParam: string; AValue: boolean );
begin
  Statement.ColumnAttributes.LinkParamIsSet[ FullFieldName, AParam ] := AValue;
end;

function TIB_Column.GetColData: string;
begin
  Result := '';
  with Row do
    if IsKeyFields or ( RowState <> rsNone ) then
      if FNewColumnInd^ <> IB_Null then
      begin
        SetLength( Result, DataSize );
        Move( FNewColumnBuffer^, Pchar(Result)^, DataSize );
      end;
end;

procedure TIB_Column.SetColData( const AValue: string );
begin
  if Length( AValue ) = 0 then
    FNewColumnInd^ := IB_Null
  else
  if Length( AValue ) = DataSize then
  begin
    FNewColumnInd^ := IB_NotNull;
    Move( Pchar(AValue)^, FNewColumnBuffer^, DataSize )
  end
  else
  if Length( AValue ) = DataSize + SizeOf( smallint ) then
    Move( Pchar(AValue)^, FNewColumnInd^, DataSize + SizeOf( smallint ))
  else
    raise EIB_ColumnError.CreateWithSender( Statement, E_Invalid_ColData );
end;

function TIB_Column.GetOldColData: string;
begin
  Result := '';
  with Row do
    if IsKeyFields or ( RowState <> rsNone ) then
      if FOldColumnInd^ <> IB_Null then
      begin
        SetLength( Result, DataSize );
        Move( FOldColumnBuffer^, Pchar(Result)^, DataSize );
      end;
end;

procedure TIB_Column.SetOldColData( const AValue: string );
begin
  if Length( AValue ) = 0 then
    FOldColumnInd^ := IB_Null
  else
  if Length( AValue ) = DataSize then
  begin
    FOldColumnInd^ := IB_NotNull;
    Move( Pchar(AValue)^, FOldColumnBuffer^, DataSize );
  end
  else
  if Length( AValue ) = DataSize + SizeOf( smallint ) then
    Move( Pchar(AValue)^, FOldColumnInd^, DataSize + SizeOf( smallint ))
  else
    raise EIB_ColumnError.CreateWithSender( Statement, E_Invalid_ColData );
end;

function TIB_Column.GetOrderingLinkItemNo: integer;
var
  tmpStr: string;
  tmpCode: integer;
begin
  Result := FOrderingLinkItemNo;
  if Result < -1 then
  begin
    Result := -1;
    if Statement is TIB_Dataset then
      with Statement as TIB_Dataset do
      begin
        tmpStr := Trim( OrderingLinks.LinkParamValue[ FieldName, 'ITEM' ] );
        if tmpStr = '' then
          tmpStr := Trim( OrderingLinks.LinkValues[ FieldName ] );
        if tmpStr <> '' then
        begin
          Val( tmpStr, Result, tmpCode );
          Result := Abs( Result );
        end;
      end;
    FOrderingLinkItemNo := Result;
  end;
end;

function TIB_Column.IsValidChar( AChar: Char ): boolean;
begin
  case SQLType of
    SQL_BOOLEAN,
    SQL_BOOLEAN_:
      Result := ( Pos( AChar, BooleanTrue ) > 0 ) or
                ( Pos( AChar, BooleanFalse ) > 0 ) or
                ( Pos( AChar, 'TtRrUuEeFfAaLlSsYyNnOoKkWw01' ) > 0 );
                //!!! Might be other characters they have defined.
    SQL_FLOAT,
    SQL_FLOAT_,
    SQL_DOUBLE,
    SQL_DOUBLE_: Result := AChar in [ DecimalSeparator,
                                      '+','-','0'..'9','E','e',
                                      'I','N','T','i','n','t', ' ' ];
    SQL_SHORT,
    SQL_SHORT_,
    SQL_LONG,
    SQL_LONG_,
    SQL_QUAD,
    SQL_QUAD_,
    SQL_INT64,
    SQL_INT64_:
      if SQLScale <> 0 then
        Result := AChar in [ DecimalSeparator, '+', '-', '0'..'9' ]
      else
        Result := AChar in [ '+', '-', '0'..'9' ];
    SQL_TYPE_TIME,
    SQL_TYPE_TIME_: Result := AChar in [ TimeSeparator,
                                         '0'..'9',
                                         'a'..'z',
                                         'A'..'Z', ' ' ];
    SQL_TYPE_DATE,
    SQL_TYPE_DATE_: Result := AChar in [ DateSeparator,
                                         '0'..'9',
                                         'a'..'z',
                                         'A'..'Z', ' ' ];
    SQL_TIMESTAMP,
    SQL_TIMESTAMP_: Result := AChar in [ DateSeparator,
                                         TimeSeparator,
                                         '0'..'9',
                                         'a'..'z',
                                         'A'..'Z', ' ' ];
  else Result := true;
  end;
end;

function TIB_Column.GetBooleanTrue: string;
begin
  Result := FBooleanTrue;
  if ( FBooleanFalse = '' ) and ( FBooleanTrue = '' ) then
  begin
    if ( SQLType = SQL_BOOLEAN ) or
       ( SQLType = SQL_BOOLEAN_ ) then
      Result := 'TRUE'
    else
    if IsNumeric then
      Result := BoolTrueNbr
    else
    if IsText then
      Result := BoolTrueChr;
  end;
end;

function TIB_Column.GetBooleanFalse: string;
begin
  Result := FBooleanFalse;
  if ( FBooleanFalse = '' ) and ( FBooleanTrue = '' ) then
  begin
    if ( SQLType = SQL_BOOLEAN ) or
       ( SQLType = SQL_BOOLEAN_ ) then
      Result := 'FALSE'
    else
    if IsNumeric then
      Result := BoolFalseNbr
    else
    if IsText then
      Result := BoolFalseChr;
  end;
end;

procedure TIB_Column.SetBooleanTrue( const AValue: string );
begin
  FBooleanTrue := AValue;
  Statement.LayoutChange( nil );
end;

procedure TIB_Column.SetBooleanFalse( const AValue: string );
begin
  FBooleanFalse := AValue;
  Statement.LayoutChange( nil );
end;

function TIB_Column.GetAsXml: string;
var
  XML : TStringList;
begin
  XML := TStringList.Create;
  try
    XML.Add( '<' + LowerCase( FieldName ) + '>' );
    XML.Add( AsString );
    XML.Add( '</'+ LowerCase( FieldName ) + '>' );
    RESULT := XML.Text;
  finally
    XML.Free;
  end;
end;

{------------------------------------------------------------------------------}

procedure TIB_Column.DoBeforeModify;
begin
  if Assigned( FOnBeforeModify ) then
    FOnBeforeModify( Self );
end;

procedure TIB_Column.DoAfterModify;
begin
  if Assigned( FOnAfterModify ) then
    FOnAfterModify( Self );
end;

{------------------------------------------------------------------------------}

constructor TIB_ColumnNumBase.Create( ARow: TIB_Row;
                                      PSQLVAR: PXSQLVAR;
                                      AFieldNo: smallint );
begin
  inherited Create( ARow, PSQLVAR, AFieldNo );
  FIsNumeric := true;
end;

procedure TIB_ColumnNumBase.SysLayoutChanged;
var
  tmpS: string;
begin
  inherited SysLayoutChanged;
  tmpS := GetAttributeParamsEx( IBO_MINVAL, FCheckMinVal );
  if FCheckMinVal then
    try
      FMinVal := StrToFloat( tmpS );
    except
      FCheckMinVal := false;
    end;

  tmpS := GetAttributeParamsEx( IBO_MAXVAL, FCheckMaxVal );
  if FCheckMaxVal then
    try
      FMaxVal := StrToFloat( tmpS );
    except
      FCheckMaxVal := false;
    end;
end;

{------------------------------------------------------------------------------}

constructor TIB_ColumnNumeric.Create( ARow: TIB_Row;
                                      PSQLVAR: PXSQLVAR;
                                      AFieldNo: smallint );
begin
  inherited Create( ARow, PSQLVAR, AFieldNo );
  FFmtStr := replicate_char( '#', 17 + FPXSQLVAR^.SQLScale ) +
             '0.' +
             replicate_char( '0', -FPXSQLVAR^.SQLScale );
  FIsCurrency := IsAttributeSet[ IBO_CURRENCY ];
  if (SQLScale < -4) or IsAttributeSet[ 'NOBCD' ] then
    FIsCurrencyDataType := false
  else
    FIsCurrencyDataType := true;
end;

function TIB_ColumnNumeric.GetAsString: string;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := ''
  else
  begin
{$IFDEF IBO_VCL40_OR_GREATER}
  with FPXSQLVAR^ do case SQLType of
    SQL_QUAD,
    SQL_QUAD_,
    SQL_INT64,
    SQL_INT64_:
      Result := Int64ScaledToStr( int64(FNewColumnBuffer^), -SQLScale );
    else
{$ENDIF}
      if IsCurrencyDataType then
      begin
        // FormatCurr rounds inappropriately at the limits of the range
        //Result := FormatCurr( FFmtStr, Value );
        // Similar problems happen with ffFixed, I dont really understand
        // why these problems occur (probably due to the semi-real nature of
        // the currency data type).  However ffGeneral seems to be working
        // as desired - and matches the default used by TDataset stuff.
        try
          Result := CurrToStrF( AsCurrency, ffGeneral, -SQLScale );
        except
          Result := FormatFloat( FFmtStr, AsExtended );
        end;
      end
      else
        Result := FormatFloat( FFmtStr, AsExtended );
{$IFDEF IBO_VCL40_OR_GREATER}
    end;
{$ENDIF}
  end;
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnNumeric.SetAsString( const NewValue: string);
var
  tmpVal: string;
  x: integer;
  tmpCurStr: string;
begin
  tmpVal := Trim( NewValue );
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
  if ( Length( tmpVal ) = 0 ) then
  begin
    if IsNullable or (( Row.RowType = rtField ) and FBlankIsNull ) then
      Clear
    else
    begin
      if IsCurrencyDataType then
        AsCurrency := 0
      else
        AsExtended := 0;
    end;
  end
  else
    try
      if IsCurrencyDataType then
        AsCurrency := StrToCurr( tmpVal )
      else
        AsExtended := StrToFloat( tmpVal );
    except
      on EConvertError do
      begin
        tmpVal := strip_char( tmpVal, ThousandSeparator );
        // remove locale currency prefix string that can be present in the
        // format string. CurrencyString is depured (in Italy is 'L.')
        tmpCurStr := strip_char( CurrencyString, ThousandSeparator );
        tmpCurStr := strip_char( tmpCurStr, DecimalSeparator );
        x := Pos( tmpCurStr, tmpVal );
        if x > 0 then
          System.Delete( tmpVal, x, length(tmpCurStr) );
{$IFDEF IBO_VCL40_OR_GREATER}
  with FPXSQLVAR^ do case SQLType of
    SQL_QUAD,
    SQL_QUAD_,
    SQL_INT64,
    SQL_INT64_:
      begin
        SysBeforeModify;
        SysSetIsNull( false );
        int64(FNewColumnBuffer^) := StrToInt64Scaled( tmpVal, -SQLScale );
        SysAfterModify;
      end;
    else
{$ENDIF}
        if IsCurrencyDataType then
          AsCurrency := StrToCurr( tmpVal )
        else
          AsExtended := StrToFloat( tmpVal );
{$IFDEF IBO_VCL40_OR_GREATER}
    end;
{$ENDIF}
      end
      else
        raise;
    end;
end;

function TIB_ColumnNumeric.GetAsExtended: extended;
var
  tmpINT64: ISC_INT64;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := 0
  else
  with FPXSQLVAR^ do case SQLType of
    SQL_DOUBLE,
    SQL_DOUBLE_:
      begin
        Result := double(FNewColumnBuffer^);
// Causing problems!
//        Result := RoundDown( Result, -SQLScale ); // **1**
      end;
    SQL_FLOAT,
    SQL_FLOAT_:
      begin
        Result := single(FNewColumnBuffer^);
// Causing problems!
//        Result := RoundDown( Result, -SQLScale ); // **1**
      end;
    SQL_SHORT,
    SQL_SHORT_:
      Result := ScaleExtended( smallint(FNewColumnBuffer^), SQLScale );
    SQL_LONG,
    SQL_LONG_:
      Result := ScaleExtended( integer(FNewColumnBuffer^), SQLScale );
    SQL_QUAD,
    SQL_QUAD_:
      begin
        FillChar( tmpINT64, 8, FPadChar );
        ISC_QUAD(tmpINT64).isc_quad_high :=
          ISC_QUAD(FNewColumnBuffer^).isc_quad_low;
        ISC_QUAD(tmpINT64).isc_quad_low :=
          ISC_QUAD(FNewColumnBuffer^).isc_quad_high;
{$IFDEF IBO_VCL40_OR_GREATER}
        Result := tmpINT64;
{$ELSE}
        Result := comp( tmpINT64 );
{$ENDIF}
        Result := ScaleExtended( Result, SQLScale );
      end;
    SQL_INT64,
    SQL_INT64_:
      begin
{$IFDEF IBO_VCL40_OR_GREATER}
        Result := int64(FNewColumnBuffer^);
{$ELSE}
        Result := comp(FNewColumnBuffer^);
{$ENDIF}
        Result := ScaleExtended( Result, SQLScale );
      end;
  else
      raise EIB_Error.CreateWithSender( Statement,
                                        Format( E_UNSUPPORTED_COLUMN,
                                                [ SQLType ] ));
  end;
end;

function TIB_ColumnNumeric.GetAsCurrency: currency;
var
  tmpINT64: ISC_INT64;
begin
    if FNewColumnInd^ = IB_NULL then begin
      Result := 0;
    end else with FPXSQLVAR^ do case SQLType of
      SQL_DOUBLE,
      SQL_DOUBLE_:
        begin
          Result := double(FNewColumnBuffer^);
// Causing problems!
//          Result := RoundDown( Result, -SQLScale ); // **1**
        end;
      SQL_FLOAT,
      SQL_FLOAT_:
        begin
          Result := single(FNewColumnBuffer^);
// Causing problems!
//          Result := RoundDown( Result, -SQLScale ); // **1**
        end;
      SQL_SHORT,
      SQL_SHORT_:
        Result := IntegerToCurrency( smallint(FNewColumnBuffer^), SQLScale );
      SQL_LONG,
      SQL_LONG_:
        Result := IntegerToCurrency( integer(FNewColumnBuffer^), SQLScale );
      SQL_QUAD,
      SQL_QUAD_:
        begin
          FillChar( tmpINT64, 8, FPadChar );
          ISC_QUAD(tmpINT64).isc_quad_high := ISC_QUAD(FNewColumnBuffer^).isc_quad_low;
          ISC_QUAD(tmpINT64).isc_quad_low  := ISC_QUAD(FNewColumnBuffer^).isc_quad_high;
          Result := Int64ToCurrency( tmpINT64, SQLScale );
        end;
      SQL_INT64,
      SQL_INT64_:
        begin
{$IFDEF IBO_VCL40_OR_GREATER}
          tmpINT64 := int64(FNewColumnBuffer^);
{$ELSE}
{$IFDEF IBO_VCL35}
          tmpINT64 := ISC_INT64(FNewColumnBuffer^);
{$ELSE}
          tmpINT64 := comp(FNewColumnBuffer^);
{$ENDIF}
{$ENDIF}
          Result := Int64ToCurrency( tmpINT64, SQLScale );
        end;
    else
      raise EIB_Error.CreateWithSender( Statement,
                                        Format( E_UNSUPPORTED_COLUMN,
                                                [ SQLType ] ));
    end;
end;

procedure TIB_ColumnNumeric.SetAsExtended( const NewValue: extended );
var
  tmpVal: extended;
  tmpINT64: ISC_INT64;
begin
  tmpVal := NewValue;
  SysBeforeModify;
  SysSetIsNull( false );
  with FPXSQLVAR^ do begin
    case SQLType of
      SQL_DOUBLE,
      SQL_DOUBLE_:
        begin
          tmpVal := RoundNear( tmpVal, -SQLScale );
          double(FNewColumnBuffer^) := tmpVal;
        end;
      SQL_FLOAT,
      SQL_FLOAT_:
        begin
          tmpVal := RoundNear( tmpVal, -SQLScale );
          single(FNewColumnBuffer^) := tmpVal;
        end;
      SQL_SHORT,
      SQL_SHORT_:
        smallint(FNewColumnBuffer^) := ExtendedToSmallint( tmpVal, -SQLScale );
      SQL_LONG,
      SQL_LONG_:
        integer(FNewColumnBuffer^) := ExtendedToInteger( tmpVal, -SQLScale );
      SQL_QUAD,
      SQL_QUAD_:
        begin
{$IFDEF IBO_VCL40_OR_GREATER}
          tmpINT64 := ExtendedToInt64( tmpVal, -SQLScale );
{$ELSE}
          raise Exception.Create( E_Unsupported );
{$ENDIF}
          ISC_QUAD(FNewColumnBuffer^).isc_quad_high :=
            ISC_QUAD(tmpINT64).isc_quad_low;
          ISC_QUAD(FNewColumnBuffer^).isc_quad_low :=
            ISC_QUAD(tmpINT64).isc_quad_high;
        end;
      SQL_INT64,
      SQL_INT64_:
        begin
          tmpINT64 := ExtendedToInt64( tmpVal, -SQLScale );
{$IFDEF IBO_VCL40_OR_GREATER}
          int64(FNewColumnBuffer^) := tmpINT64;
{$ELSE}
{$IFDEF IBO_VCL35}
          ISC_INT64(FNewColumnBuffer^) := tmpINT64;
{$ELSE}
          comp(FNewColumnBuffer^) := tmpINT64;
{$ENDIF}
{$ENDIF}
        end;
    else
      raise EIB_Error.CreateWithSender( Statement,
                                        Format( E_UNSUPPORTED_COLUMN,
                                                [ SQLType ] ));
    end;
  end;
  SysAfterModify;
end;

procedure TIB_ColumnNumeric.SetAsCurrency( const NewValue: currency );
var
  tmpVal: currency;
  tmpINT64: ISC_INT64;
begin
    tmpVal := NewValue;
    SysBeforeModify;
    SysSetIsNull( false );
    with FPXSQLVAR^ do
    begin
      case SQLType of
        SQL_DOUBLE,
        SQL_DOUBLE_:
          begin
            tmpVal := RoundNear( tmpVal, -SQLScale );
            double(FNewColumnBuffer^) := tmpVal;
          end;
        SQL_FLOAT,
        SQL_FLOAT_:
          begin
            tmpVal := RoundNear( tmpVal, -SQLScale );
            single(FNewColumnBuffer^) := tmpVal;
          end;
        SQL_SHORT,
        SQL_SHORT_:
          smallint(FNewColumnBuffer^) := CurrencyToSmallint( tmpVal,-SQLScale );
        SQL_LONG,
        SQL_LONG_:
          integer(FNewColumnBuffer^) := CurrencyToInteger( tmpVal, -SQLScale );
        SQL_QUAD,
        SQL_QUAD_:
          begin
  {$IFDEF IBO_VCL40_OR_GREATER}
            tmpINT64 := CurrencyToInt64( tmpVal, -SQLScale );
  {$ELSE}
            raise Exception.Create( E_Unsupported );
  {$ENDIF}
            ISC_QUAD(FNewColumnBuffer^).isc_quad_high :=
              ISC_QUAD(tmpINT64).isc_quad_low;
            ISC_QUAD(FNewColumnBuffer^).isc_quad_low :=
              ISC_QUAD(tmpINT64).isc_quad_high;
          end;
        SQL_INT64,
        SQL_INT64_:
          begin
            tmpINT64 := CurrencyToInt64( tmpVal, -SQLScale );
{$IFDEF IBO_VCL40_OR_GREATER}
            int64(FNewColumnBuffer^) := tmpINT64;
{$ELSE}
{$IFDEF IBO_VCL35}
          ISC_INT64(FNewColumnBuffer^) := tmpINT64;
{$ELSE}
          comp(FNewColumnBuffer^) := tmpINT64;
{$ENDIF}
{$ENDIF}
          end;
      else
        raise EIB_Error.CreateWithSender( Statement,
                                          Format( E_UNSUPPORTED_COLUMN,
                                                  [ SQLType ] ));
      end;
    end;
    SysAfterModify;
end;

function TIB_ColumnNumeric.GetValue: extended;
begin
  Result := AsExtended;
end;

procedure TIB_ColumnNumeric.SetValue( const NewValue: extended );
begin
  AsExtended := NewValue;
end;

function TIB_ColumnNumeric.GetAsInteger: integer;
begin
  if IsCurrencyDataType then
    Result := CurrencyToInteger(AsCurrency,0)
  else
    Result := Trunc( AsExtended );
end;

procedure TIB_ColumnNumeric.SetAsInteger( const NewValue: integer);
begin
  if IsCurrencyDataType then
    AsCurrency := NewValue
  else
    AsExtended := NewValue;
end;

{------------------------------------------------------------------------------}

function TIB_ColumnFloat.GetAsString: string;
begin
  if FNewColumnInd^ = IB_NULL then begin
    Result := '';
  end else begin
    Result := FloatToStr( Value );
  end;
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnFloat.SetAsString( const NewValue: string);
var
  tmpVal: string;
begin
  tmpVal := NewValue;
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
  if ( Length( tmpVal ) = 0 ) then
  begin
    if IsNullable or (( Row.RowType = rtField ) and FBlankIsNull ) then
      Clear
    else
      Value := 0;
  end
  else
    Value := StrToFloat( tmpVal );
end;

function TIB_ColumnFloat.GetAsFloat: double;
begin
  Result := Value;
end;

procedure TIB_ColumnFloat.SetAsFloat( const NewValue: double );
begin
  Value := NewValue;
end;

function TIB_ColumnFloat.GetValue: single;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := 0
  else
    Result := single(FNewColumnBuffer^);
end;

procedure TIB_ColumnFloat.SetValue( const NewValue: single );
begin
  SysBeforeModify;
  SysSetIsNull( false );
  single(FNewColumnBuffer^) := NewValue;
  SysAfterModify;
end;

{------------------------------------------------------------------------------}

function TIB_ColumnDouble.GetAsString: string;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := ''
  else
    Result := FloatToStr(Value);
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnDouble.SetAsString( const NewValue: string );
var
  tmpVal: string;
begin
  tmpVal := Trim( NewValue );
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
  if ( Length( tmpVal ) = 0 ) then
  begin
    if IsNullable or (( Row.RowType = rtField ) and FBlankIsNull ) then
      Clear
    else
      Value := 0;
  end
  else
  if Pos( 'INF', tmpVal ) > 0 then
  begin
    if Pos( '-', tmpVal ) > 0 then
      Value := mindouble
    else
      Value := maxdouble;
  end
  else
    Value := StrToFloat( tmpVal );
end;

function TIB_ColumnDouble.GetAsDouble: Double;
begin
  Result := Value;
end;

procedure TIB_ColumnDouble.SetAsDouble( const NewValue: Double);
begin
  Value := NewValue;
end;

function TIB_ColumnDouble.GetValue: double;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := 0
  else
    Result := double(FNewColumnBuffer^);
end;

procedure TIB_ColumnDouble.SetValue( const NewValue: double );
begin
  SysBeforeModify;
  SysSetIsNull( false );
  double(FNewColumnBuffer^) := NewValue;
  SysAfterModify;
end;

{------------------------------------------------------------------------------}

function TIB_ColumnBoolean.GetAsString: string;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := 'UNKNOWN'
  else
  if Value then
    Result := BooleanTrue
  else
    Result := BooleanFalse;
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnBoolean.SetAsString( const NewValue: string);
var
  tmpVal: string;
begin
  tmpVal := Trim( UpperCase( NewValue ));
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
  tmpVal := Trim( UpperCase( tmpVal ));
  if ( Length( tmpVal ) = 0 ) then
  begin
    if IsNullable or (( Row.RowType = rtField ) and FBlankIsNull ) then
      Clear
    else
      Value := False;
  end
  else
  if ( tmpVal = UpperCase( BooleanTrue )) or
     ( tmpVal = '1' ) or
     ( tmpVal = 'T' ) or
     ( tmpVal = 'TRUE' ) or
     ( tmpVal = 'Y' ) or
     ( tmpVal = 'YES' ) then
    Value := true
  else
  if ( tmpVal = UpperCase( BooleanFalse )) or
     ( tmpVal = '0' ) or
     ( tmpVal = 'F' ) or
     ( tmpVal = 'FALSE' ) or
     ( tmpVal = 'N' ) or
     ( tmpVal = 'NO' ) then
    Value := false
  else
  if ( tmpVal = 'UNKNOWN' ) or
     ( tmpVal = 'NULL' ) then
    Clear
  else
    raise Exception.Create( E_Invalid_Boolean_string_conversion +
                            ' ' + tmpVal );
end;

function TIB_ColumnBoolean.GetValue: boolean;
begin
  Result := AsBoolean;
end;

procedure TIB_ColumnBoolean.SetValue( const NewValue: boolean );
begin
  SysBeforeModify;
  SysSetIsNull( false );
  if NewValue then
    PShort(FNewColumnBuffer)^ := ISC_TRUE
  else
    PShort(FNewColumnBuffer)^ := ISC_FALSE;
  SysAfterModify;
end;

{------------------------------------------------------------------------------}

function TIB_ColumnSmallint.GetAsString: string;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := ''
  else
    Result := IntToStr(Value);
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnSmallint.SetAsString( const NewValue: string);
var
  tmp: longint;
  tmpVal: string;
begin
  tmpVal := NewValue;
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
  if ( Length( tmpVal ) = 0 ) then
  begin
    if IsNullable or (( Row.RowType = rtField ) and FBlankIsNull ) then
      Clear
    else
      Value := 0;
  end
  else
  begin
    tmp := StrToInt( Trim( tmpVal ));
    if (tmp < low(smallint)) or
       (tmp > high(smallint)) then
      raise EIB_ColumnError.CreateWithSender( Statement, E_Value_Out_Of_Range );
    Value := tmp;
  end;
end;

function TIB_ColumnSmallint.GetAsSmallint: smallint;
begin
  Result := Value;
end;

procedure TIB_ColumnSmallint.SetAsSmallint( const NewValue: smallint);
begin
  Value := NewValue;
end;

function TIB_ColumnSmallint.GetValue: smallint;
begin
  if FNewColumnInd^ = IB_NULL then begin
    Result := 0;
  end else begin
    Result := smallint(FNewColumnBuffer^);
  end;
end;

procedure TIB_ColumnSmallint.SetValue( const NewValue: smallint );
begin
  SysBeforeModify;
  SysSetIsNull( false );
  smallint(FNewColumnBuffer^) := NewValue;
  SysAfterModify;
end;

{------------------------------------------------------------------------------}

function TIB_ColumnInteger.GetAsString: string;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := ''
  else
    Result := IntToStr(Value);
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnInteger.SetAsString( const NewValue: string );
var
  tmpVal: string;
begin
  tmpVal := NewValue;
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
  tmpVal := Trim( tmpVal );
  if ( Length( tmpVal ) = 0 ) then
  begin
    if IsNullable or (( Row.RowType = rtField ) and FBlankIsNull ) then
      Clear
    else
      Value := 0;
  end
  else
    try
      Value := StrToInt( tmpVal );
    except
    // In Delphi 3 it doesn't seem to know how to reciprocate the value.
      if IntToStr( Low( integer )) = tmpVal then
        Value := Low( integer )
      else
        raise;
    end;
end;

function TIB_ColumnInteger.GetAsInteger: integer;
begin
  Result := Value;
end;

procedure TIB_ColumnInteger.SetAsInteger( const NewValue: integer);
begin
  Value := NewValue;
end;

function TIB_ColumnInteger.GetValue: integer;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := 0
  else
    Result := integer(FNewColumnBuffer^);
end;

procedure TIB_ColumnInteger.SetValue( const NewValue: integer);
begin
  SysBeforeModify;
  SysSetIsNull( false );
  integer(FNewColumnBuffer^) := NewValue;
  SysAfterModify;
end;

{------------------------------------------------------------------------------}

function TIB_ColumnInt64.GetAsString: string;
begin
  Result := '';
{$IFDEF IBO_VCL40_OR_GREATER}
  if FNewColumnInd^ <> IB_NULL then begin
    Result := IntToStr(Value);
  end;
{$ELSE}
{$IFDEF IBO_VCL35}
    if FNewColumnInd^ <> IB_NULL then begin
      raise exception.create( 'Unsupported' );
    end;
{$ELSE}
    if FNewColumnInd^ <> IB_NULL then begin
      Result := CompToStr(Value);
    end;
{$ENDIF}
{$ENDIF}
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnInt64.SetAsString( const NewValue: string );
var
  tmpVal: string;
begin
  tmpVal := NewValue;
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
  tmpVal := Trim( tmpVal );
  if ( Length( tmpVal ) = 0 ) then
  begin
    if IsNullable or (( Row.RowType = rtField ) and FBlankIsNull ) then
      Clear
    else
    begin
{$IFDEF IBO_VCL40_OR_GREATER}
      Value := 0;
{$ELSE}
{$IFDEF IBO_VCL35}
    raise exception.create( 'Unsupported' );
{$ELSE}
      Value := 0;
{$ENDIF}
{$ENDIF}
    end;
  end else begin
{$IFDEF IBO_VCL40_OR_GREATER}
    Value := StrToInt64( tmpVal );
{$ELSE}
{$IFDEF IBO_VCL35}
    raise exception.create( 'Unsupported' );
{$ELSE}
    Value := StrToFloat( tmpVal );
{$ENDIF}
{$ENDIF}
  end;
end;

function TIB_ColumnInt64.GetAsInteger: integer;
begin
{$IFDEF IBO_VCL40_OR_GREATER}
  Result := Value;
{$ELSE}
{$IFDEF IBO_VCL35}
  raise Exception.Create( 'Unsupported' );
{$ELSE}
  Result := Trunc(Value);
{$ENDIF}
{$ENDIF}
end;

procedure TIB_ColumnInt64.SetAsInteger( const NewValue: integer);
begin
{$IFDEF IBO_VCL40_OR_GREATER}
  Value := NewValue;
{$ELSE}
{$IFDEF IBO_VCL35}
  raise Exception.Create( 'Unsupported' );
{$ELSE}
  Value := NewValue;
{$ENDIF}
{$ENDIF}
end;

function TIB_ColumnInt64.GetValue: ISC_INT64;
begin
  if FNewColumnInd^ = IB_NULL then
    FillChar( Result, SizeOf( ISC_INT64 ), FPadChar )
  else
    Result := ISC_INT64(FNewColumnBuffer^);
end;

procedure TIB_ColumnInt64.SetValue( const NewValue: ISC_INT64 );
begin
  SysBeforeModify;
  SysSetIsNull( false );
  ISC_INT64(FNewColumnBuffer^) := NewValue;
  SysAfterModify;
end;

{------------------------------------------------------------------------------}

function TIB_ColumnQuad.GetAsString: string;
begin
  Result := '';
{$IFDEF IBO_VCL40_OR_GREATER}
  if FNewColumnInd^ <> IB_NULL then begin
    Result := IntToStr(Value);
  end;
{$ELSE}{!!!}
    Result := FloatToStr(AsExtended);
{$ENDIF}
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnQuad.SetAsString( const NewValue: string );
var
  tmpVal: string;
begin
  tmpVal := NewValue;
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
  tmpVal := Trim( tmpVal );
  if ( Length( tmpVal ) = 0 ) then
  begin
    if IsNullable or (( Row.RowType = rtField ) and FBlankIsNull ) then 
      Clear
    else
    begin
{$IFDEF IBO_VCL40_OR_GREATER}
      Value := 0;
{$ELSE}
      AsExtended := 0;
{$ENDIF}
    end;
  end else begin
{$IFDEF IBO_VCL40_OR_GREATER}
    Value := StrToInt64( tmpVal );
{$ELSE}
    AsExtended := StrToFloat( tmpVal );
{$ENDIF}
  end;
end;

function TIB_ColumnQuad.GetAsInteger: integer;
begin
{$IFDEF IBO_VCL40_OR_GREATER}
  Result := Value;
{$ELSE}
  Result := Trunc( AsExtended );
{$ENDIF}
end;

procedure TIB_ColumnQuad.SetAsInteger( const NewValue: integer);
begin
{$IFDEF IBO_VCL40_OR_GREATER}
  Value := NewValue;
{$ELSE}
  AsExtended := NewValue;
{$ENDIF}
end;

function TIB_ColumnQuad.GetValue: ISC_INT64;
begin
  FillChar( Result, SizeOf( ISC_INT64 ), FPadChar );
  if FNewColumnInd^ <> IB_NULL then
  begin
     ISC_QUAD(Result).isc_quad_high := ISC_QUAD(FNewColumnBuffer^).isc_quad_low;
     ISC_QUAD(Result).isc_quad_low := ISC_QUAD(FNewColumnBuffer^).isc_quad_high;
  end;
end;

procedure TIB_ColumnQuad.SetValue( const NewValue: ISC_INT64 );
begin
  SysBeforeModify;
  SysSetIsNull( false );
  ISC_QUAD(FNewColumnBuffer^).isc_quad_high := ISC_QUAD(NewValue).isc_quad_low;
  ISC_QUAD(FNewColumnBuffer^).isc_quad_low := ISC_QUAD(NewValue).isc_quad_high;
  SysAfterModify;
end;

{------------------------------------------------------------------------------}

constructor TIB_ColumnDateTime.Create( ARow: TIB_Row;
                                       PSQLVAR: PXSQLVAR;
                                       AFieldNo: smallint );
begin
  inherited Create( ARow, PSQLVAR, AFieldNo );
  FIsDateTime := true;
end;

function TIB_ColumnDateTime.GetAsString: string;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := ''
  else
  if ( SQLType = SQL_TYPE_DATE ) or
     ( SQLType = SQL_TYPE_DATE_ ) then
    Result := DateToStr( Value )
  else
  if IsDateOnly then
    DateTimeToString( Result, FDateOnlyFmt, Value )
  else
  if ( SQLType = SQL_TYPE_TIME ) or
     ( SQLType = SQL_TYPE_TIME_ ) then
    Result := TimeToStr( Value )
  else
  if IsTimeOnly then
    DateTimeToString( Result, FTimeOnlyFmt, Value )
  else
    Result := DateTimeToStr( Value );
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnDateTime.SetAsString( const NewValue: string );
var
  tmpVal: string;
begin
  tmpVal := NewValue;
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
  if ( Length( tmpVal ) = 0 ) or
     (( IsMasked ) and ( EmptyMaskText = tmpVal )) then
    Clear
  else
  if IsValidIdent( tmpVal ) then
  begin
    tmpVal := UpperCase( tmpVal );
    if Pos( 'NOW', tmpVal ) = 1 then
      Value := now
    else
    if Pos( 'TOD', tmpVal ) = 1 then
      Value := Trunc(now)
    else
    if Pos( 'TOM', tmpVal ) = 1 then
      Value := Trunc(now) + 1
    else
    if Pos( 'YES', tmpVal ) = 1 then
      Value := Trunc(now) - 1
    else
    if Pos( 'LASTW', tmpVal ) = 1 then
      Value := Trunc(now) - 7
    else
    if Pos( 'NEXTW', tmpVal ) = 1 then
      Value := Trunc(now) + 7
    else
    if Pos( 'CURRENT_DATE', tmpVal ) = 1 then
      Value := Date
    else
    if Pos( 'CURRENT_TIMESTAMP', tmpVal ) = 1 then
      Value := Now
    else
    if Pos( 'CURRENT_TIME', tmpVal ) = 1 then
      Value := Time
    else
    if Pos( 'NULL', tmpVal ) = 1 then
      Clear;
  end
  else  // I assume that a date/time constant (i.e. '10:45') is never a valid
  begin // identifier, since it must begin with a digit [0..9]
//  if Pos( 'BINARY', tmpVal ) = 1 then
//    Value :=
//  else
    if ( SQLType = SQL_TYPE_TIME ) or
       ( SQLType = SQL_TYPE_TIME_ ) then
      Value := StrToTime( tmpVal )
    else
    if IsTimeOnly then
      Value := Trunc( OldAsVariant ) + StrToTime( tmpVal )
    else
      Value := StrToDateTime( tmpVal );
  end;
end;

function TIB_ColumnDateTime.GetAsDate: TDateTime;
begin
  Result := Trunc( Value );
end;

procedure TIB_ColumnDateTime.SetAsDate( const NewValue: TDateTime );
begin
  Value := Trunc( NewValue );
end;

function TIB_ColumnDateTime.GetAsDateTime: TDateTime;
begin
  Result := Value;
end;

procedure TIB_ColumnDateTime.SetAsDateTime( const NewValue: TDateTime );
begin
  Value := NewValue;
end;

function TIB_ColumnDateTime.GetAsDateTimeEncodeString: string;
begin
  Result := DateTimeToEncodeString( Value );
end;

procedure TIB_ColumnDateTime.SetAsDateTimeEncodeString( const NewValue: string);
begin
  Value := EncodeStringToDateTime(NewValue);
end;

function TIB_ColumnDateTime.GetValue: TDateTime;
var
  pTime: PISC_TIME;
begin
  Result := 0;
  if FNewColumnInd^ <> IB_NULL then
  case SQLType of
    SQL_TYPE_DATE,
    SQL_TYPE_DATE_: begin
      Result := ISC_DATE( FNewColumnBuffer^ ) - 15018;
    end;
    SQL_TYPE_TIME,
    SQL_TYPE_TIME_: begin
      pTime := PISC_TIME( FNewColumnBuffer );
      Result := EncodeTime(( pTime^ div ( 10000 * 60 * 60 )),
                           ( pTime^ div ( 10000 * 60 )) mod 60,
                             pTime^ div ( 10000 ) mod 60,
                           ( pTime^ mod ( 10000 )) div 10 );
    end;
    SQL_TIMESTAMP,
    SQL_TIMESTAMP_: begin
      Result := isc_decode_TDateTime( pisc_quad( FNewColumnBuffer ));
    end;
  end;
end;

procedure TIB_ColumnDateTime.SetValue( const NewValue: TDateTime );
var
  Hour, Min, Sec, MSec: word;
begin
  SysBeforeModify;
  SysSetIsNull( false );
  case SQLType of
    SQL_TYPE_DATE,
    SQL_TYPE_DATE_: begin
      PISC_DATE( FNewColumnBuffer )^ := Trunc( NewValue ) + 15018;
    end;
    SQL_TYPE_TIME,
    SQL_TYPE_TIME_: begin
      DecodeTime( NewValue, Hour, Min, Sec, MSec );
      PISC_TIME( FNewColumnBuffer )^ := Hour * 10000 * 60 * 60 +
                                        Min  * 10000 * 60      +
                                        Sec  * 10000           +
                                        MSec *    10;
    end;
    SQL_TIMESTAMP,
    SQL_TIMESTAMP_: if IsDateOnly then begin
      isc_encode_TDateTime( Trunc( NewValue ), pisc_quad( FNewColumnBuffer ));
    end else begin
      isc_encode_TDateTime( NewValue, pisc_quad( FNewColumnBuffer ));
    end;
  end;
  SysAfterModify;
end;

{------------------------------------------------------------------------------}

constructor TIB_ColumnText.Create( ARow: TIB_Row;
                                   PSQLVAR: PXSQLVAR;
                                   AFieldNo: smallint );
begin
  inherited Create( ARow, PSQLVAR, AFieldNo );
  FIsText := true;
  FTrimming := ctNone;
//  if ARow.RowType <> rtParam then // Why was this here?
  begin
    if not Statement.IB_Connection.DefaultNoTrimming then
      FTrimming := ctRight;
    if not Statement.IB_Connection.DefaultNoLengthCheck then
      Inc( FTrimming );
  end;
  if not IsAttributeSet[ IBO_BINARY ] then
    FPadChar := #32;
  if IsAttributeSet[ IBO_NOCASE ] then
  begin
    FNoCaseFieldName := AttributeParams[ IBO_NOCASE ];
    if FNoCaseFieldName = '' then
      FNoCaseFieldName := 'UPPER( ' + FullFieldName + ' )';
  end;
end;

function TIB_ColumnText.GetAsString: string;
begin
{$IFDEF IBO_VCL60_OR_GREATER}
  if Statement.IB_Connection.CharSet = IBO_UTF8 then
    Result := UTF8Decode( Value )
  else
{$ENDIF}
    Result := Value;
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnText.SetAsString( const NewValue: string);
var
  tmpVal: string;
begin
  tmpVal := NewValue;
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
{$IFDEF IBO_VCL60_OR_GREATER}
  if Statement.IB_Connection.CharSet = IBO_UTF8 then
    Value := UTF8Encode( tmpVal )
  else
{$ENDIF}
    Value := tmpVal;
end;

{$IFDEF IBO_VCL30_OR_GREATER}
function TIB_ColumnText.GetAsWideString: WideString;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := ''
  else
    SetString( Result, PWideChar(FNewColumnBuffer) + 1, DataSize div 2 );
end;

procedure TIB_ColumnText.SetAsWideString( const NewValue: WideString );
begin
  SysBeforeModify;
  SysSetIsNull( false );
  FillChar( FNewColumnBuffer^, DataSize, FPadChar );
  if 2 * Length(NewValue) >= DataSize then
    Move(PWideChar(NewValue)^, FNewColumnBuffer^, DataSize)
  else
    Move(PWideChar(NewValue)^, FNewColumnBuffer^, 2 * Length(NewValue));
  SysAfterModify;
end;
{$ENDIF}

function TIB_ColumnText.GetValue: string;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := ''
  else
  begin
    Result := Copy(Pchar(FNewColumnBuffer), 1, SQLLen );
    case CharCase of
      ccUpper: Result := AnsiUpperCase( Result );
      ccLower: Result := AnsiLowerCase( Result );
    end;
    if not Row.IsKeyFields then
      SysApplyTrimming( Result );
  end;
end;

procedure TIB_ColumnText.SetValue( const NewValue: string );
var
  tmpVal: string;
  strLen: integer;
begin
  tmpVal := NewValue;
  SysBeforeModify;
  case CharCase of
    ccUpper: tmpVal := AnsiUpperCase( tmpVal );
    ccLower: tmpVal := AnsiLowerCase( tmpVal );
  end;
  if not Row.IsKeyFields then
    SysApplyTrimming( tmpVal );
  strLen := Length( tmpVal );
  FillChar( FNewColumnBuffer^, DataSize, FPadChar );
  if ( strLen = 0 ) and FBlankIsNull then
    SysSetIsNull( true )
  else
  begin
    SysSetIsNull( false );
    if strLen > SQLLen then
      strLen := SQLLen;
    Move( tmpVal[1], FNewColumnBuffer^, strLen );
  end;
  SysAfterModify;
end;

{------------------------------------------------------------------------------}

{$IFDEF IBO_VCL30_OR_GREATER}
function TIB_ColumnVarText.GetAsWideString: WideString;
var
  Len: Word;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := ''
  else
  with SQL_VARCHAR( FNewColumnBuffer^ ) do
  begin
    Len := vary_len_low + vary_len_high * 256;
    SetString( Result, PWideChar( @vary_string ), Len div 2 );
  end;
end;

procedure TIB_ColumnVarText.SetAsWideString( const NewValue: WideString );
var
  Len: Word;
begin
  SysBeforeModify;
  SysSetIsNull( false );
  Len := 2 * Length(NewValue);
  if Len > DataSize - 2 then
    Len := DataSize - 2;
  FillChar( FNewColumnBuffer^, DataSize, FPadChar );
  with SQL_VARCHAR( FNewColumnBuffer^ ) do
  begin
    vary_len_low := Len mod 256;
    vary_len_high := Len div 256;
    Move( PWideChar(NewValue)^, vary_string,Len );
  end;
  SysAfterModify;
end;
{$ENDIF}

function TIB_ColumnVarText.GetValue: string;
var
  StrLen: word;
  tmpPtr: Pointer;
begin
  if FNewColumnInd^ = IB_NULL then
    Result := ''
  else
  begin
    tmpPtr := pointer(longint(FNewColumnBuffer) + SizeOf( smallint ));
    with SQL_VARCHAR( FNewColumnBuffer^ ) do
      StrLen := vary_len_low + vary_len_high * 256;
    Result := Copy(pchar(tmpPtr), 1, StrLen);
    case CharCase of
      ccUpper: Result := AnsiUpperCase( Result );
      ccLower: Result := AnsiLowerCase( Result );
    end;
    if not Row.IsKeyFields then
      SysApplyTrimming( Result );
  end;
end;

procedure TIB_ColumnVarText.SetValue( const NewValue: string );
var
  StrLen: smallint;
  tmpVal: string;
begin
  tmpVal := NewValue;
  SysBeforeModify;
  if not Row.IsKeyFields then
    SysApplyTrimming( tmpVal );
  StrLen := Length( tmpVal );
  case CharCase of
    ccUpper: tmpVal := AnsiUpperCase( tmpVal );
    ccLower: tmpVal := AnsiLowerCase( tmpVal );
  end;
  if StrLen > SQLLen then
    StrLen := SQLLen;
  FillChar( FNewColumnBuffer^, DataSize, FPadChar );
  if ( StrLen = 0 ) and FBlankIsNull then
    SysSetIsNull( true )
  else
  with SQL_VARCHAR( FNewColumnBuffer^ ) do
  begin
    SysSetIsNull( false );
    vary_len_low  := StrLen mod 256;
    vary_len_high := StrLen div 256;
    Move( tmpVal[1], vary_string, StrLen );
  end;
  SysAfterModify;
end;

{------------------------------------------------------------------------------}

constructor TIB_ColumnDB_KEY.Create( ARow: TIB_Row;
                                     PSQLVAR: PXSQLVAR;
                                     AFieldNo: smallint );
begin
  inherited Create( ARow, PSQLVAR, AFieldNo );
  FPadChar := #0;
end;

function TIB_ColumnDB_KEY.GetAsString: string;
begin
  Result := Value;
  if Assigned( FOnGetText ) then
    FOnGetText( Self, Result );
end;

procedure TIB_ColumnDB_KEY.SetAsString( const NewValue: string);
var
  tmpVal: string;
begin
  tmpVal := NewValue;
  if Assigned( FSetText ) then 
    FSetText( FSetTextCol, tmpVal );
  if Assigned( FOnSetText ) then
    FOnSetText( Self, tmpVal );
  Value := tmpVal;
end;

function TIB_ColumnDB_KEY.GetDefaultWidth: integer;
var
  DataWidth: integer;
  HeaderWidth: integer;
begin
  HeaderWidth := Length( Trim( DisplayName ) );
  DataWidth := 2 * SQLLen;
  if HeaderWidth > DataWidth then
    Result := HeaderWidth * 8
  else
    Result := DataWidth * 8;
  if Result < 70 then
    Result := 70;
  if Result > 200 then
    Result := 200;
end;

function TIB_ColumnDB_KEY.GetValue: string;
begin
  if IsNull then
    Result := ''
  else
    Result := IB_Utils.BinaryToHexText( FNewColumnBuffer, SQLLen );
end;

procedure TIB_ColumnDB_KEY.SetValue( const NewValue: string );
begin
  SysBeforeModify;
  if Length( NewValue ) = 0 then
  begin
    SysSetIsNull( true );
    FillChar( FNewColumnBuffer^, SQLLen, FPadChar );
  end
  else
  begin
    SysSetIsNull( false );
    IB_Utils.HexTextToBinary( NewValue, FNewColumnBuffer, SQLLen );
  end;
  SysAfterModify;
end;

// IBA_ColumnBlob.INT

constructor TIB_ColumnBlob.Create( ARow: TIB_Row;
                                   PSQLVAR: PXSQLVAR;
                                   AFieldNo: smallint );
begin
  inherited Create( ARow, PSQLVAR, AFieldNo );
  FBlobNode := Row.GetBlobNode( AFieldNo );
  FIsBlob := true;
end;

constructor TIB_ColumnMemo.Create( ARow: TIB_Row;
                                   PSQLVAR: PXSQLVAR;
                                   AFieldNo: smallint );
begin
  inherited Create( ARow, PSQLVAR, AFieldNo );
  FIsText := true;
end;

constructor TIB_ColumnBLR.Create( ARow: TIB_Row;
                                  PSQLVAR: PXSQLVAR;
                                  AFieldNo: smallint );
begin
  inherited Create( ARow, PSQLVAR, AFieldNo );
  FIsText := true;
end;

{------------------------------------------------------------------------------}

{$IFDEF IBO_VCL30_OR_GREATER}
function TIB_ColumnMemo.GetAsWideString: WideString;
begin
  Result := AsString;
end;

procedure TIB_ColumnMemo.SetAsWideString( const NewValue: WideString );
begin
  AsString := NewValue;
end;
{$ENDIF}

{------------------------------------------------------------------------------}

function TIB_ColumnBlob.GetBlobSize: longint;
var
  tmpStream: TStream;
  Blob_Handle: ISC_BLOB_HANDLE;
  MaxSegmentSize: longint;
  NumSegments: longint;
begin
  if IsNull then
    Result := 0
  else
  if IsLoaded then
  begin
    tmpStream := Statement.CreateBlobStream( Self, bsmRead );
    try
      Result := tmpStream.Size;
    finally
      tmpStream.Free;
    end;
  end
  else
  with Statement.IB_Session do
  begin
    Statement.CheckTransaction( true );
    Blob_Handle := nil;
    errcode := isc_open_blob( @Status,
                              Statement.pdbHandle,
                              Statement.ptrHandle,
                              @Blob_Handle,
                              pisc_quad( FNewColumnBuffer ));
    if errcode = 0 then
      GetBlobSize( MaxSegmentSize, Result, NumSegments, @Blob_Handle, PSQLVAR );
    errcode := isc_close_blob( @status, @Blob_Handle );
  end;
end;

function TIB_ColumnBlob.GetIsLoaded: boolean;
var
  ParentBlobRef: PIB_BlobNode;
begin
  Result := IsNull or
            IsModified or
            ( BlobNode.BlobSize > 0 ) or
            Assigned( FindBlobNodeInList( Row.PBlobHead,
                                          BlobID,
                                          FieldNo,
                                          true,
                                          ParentBlobRef ));
end;

function TIB_ColumnBlob.GetLoadedBlobNode: PIB_BlobNode;
var
  ParentBlobRef: PIB_BlobNode;
begin
  if IsNull then
    Result := nil
  else
  if IsModified or ( BlobNode.BlobSize > 0 ) then
    Result := BlobNode
  else
    Result := FindBlobNodeInList( Row.PBlobHead,
                                  BlobID,
                                  FieldNo,
                                  true,
                                  ParentBlobRef );
end;

function TIB_ColumnBlob.GetIsModified: boolean;
begin
  Result := inherited GetIsModified or BlobNode.BlobChanged;
end;

function TIB_ColumnBlob.SysInternalChanged: boolean;
begin
  Result := true;
end;

function TIB_ColumnBlob.GetDisplayText: string;
begin
  if IsNull then
    Result := ''
  else
  if IsText and IsLoaded then
    Result := inherited GetDisplayText
  else
    Result := '( ' + DisplayName + ' )';
end; 

function TIB_ColumnBlob.GetBlobID: isc_quad;
begin
  Result := isc_quad( FNewColumnBuffer^ );
end;

function TIB_ColumnBlob.GetOldBlobID: isc_quad;
begin
  Result := isc_quad( FOldColumnBuffer^ );
end;

function TIB_ColumnBlob.GetOldAsString: string;
var
  PBlobNode: PIB_BlobNode;
  ParentBlobRef: PIB_BlobNode;
begin
  if IsModified then
  begin
    if OldIsNull then
      Result := ''
    else
    begin
      PBlobNode := FindBlobNodeInList( Row.PBlobHead,
                                       OldBlobID,
                                       FieldNo,
                                       true,
                                       ParentBlobRef );
      if not Assigned( PBlobNode ) then
      begin
        PBlobNode := AllocMem( SizeOf( TIB_BlobNode ));
        try
          ClearBlobNodeData( PBlobNode );
          PBlobNode.BlobID := OldBlobID;
          Statement.GetBlobNodeData( Self.PSQLVAR, nil, PBlobNode );
        except
          FreeBlobNodeData( PBlobNode );
          FreeMem( PBlobNode );
          raise;
        end;
        PBlobNode.Next := Row.PBlobHead^;
        Row.PBlobHead^ := PBlobNode;
      end;
      SetLength( Result, PBlobNode.BlobSize );
      Move( PBlobNode.BlobBuffer^, Result[1], PBlobNode.BlobSize );
    end;
  end
  else
    Result := AsString;
end;

function TIB_ColumnBlob.GetAsString: string;
var
  tmpStream: TStream;
begin
  if IsNull then
    Result := ''
  else
  begin
    tmpStream := Statement.CreateBlobStream( Self, bsmRead );
    try
      SetString( Result, nil, tmpStream.Size );
      tmpStream.ReadBuffer( Pointer( Result )^, Length( Result ));
    finally
      tmpStream.Free;
    end;
  end;
end;

procedure TIB_ColumnBlob.SetAsString( const NewValue: string );
var
  tmpStream: TStream;
begin
  tmpStream := Statement.CreateBlobStream( Self, bsmWrite );
  try
    if Length( NewValue ) > 0 then
      tmpStream.WriteBuffer( NewValue[1], Length( NewValue ))
    else
    if BlankIsNull then 
      SysSetIsNull( true )
    else
      SysSetIsNull( false );
  finally
    tmpStream.Free;
  end;
end;

procedure TIB_ColumnBlob.Clear;
begin
  Statement.CreateBlobStream( Self, bsmWrite ).Free;
end;

procedure TIB_ColumnBlob.AssignTo( Dest: TObject );
begin
  if Dest is TIB_ColumnBlob then
    SaveToBlob(TIB_ColumnBlob( Dest ))
  else
  if Dest is TStrings then
    SaveToStrings(TStrings(Dest))
  else
  if Dest is TStream then
    SaveToStream(TStream(Dest))
  else
  if Dest is TGraphic then
    SaveToGraphic(TGraphic(Dest))
  else
  if Dest is TPicture then
  begin
    if Assigned( TPicture(Dest).Graphic ) then
      SaveToGraphic(TPicture(Dest).Graphic)
    else
      SaveToGraphic(TPicture(Dest).Bitmap);
  end
  else
    inherited AssignTo( Dest );
end;

procedure TIB_ColumnBlob.Assign( Source: TObject );
begin
  if Source is TIB_ColumnBlob then begin
    LoadFromBlob( TIB_ColumnBlob( Source ));
  end else if Source is TStrings then begin
    LoadFromStrings( TStrings( Source ));
  end else if Source is TStream then begin
    LoadFromStream( TStream( Source ));
  end else if Source is TGraphic then begin
    LoadFromGraphic( TGraphic( Source ));
  end else if Source is TPicture then begin
    LoadFromGraphic( TPicture( Source ).Graphic );
  end else begin
    inherited Assign( Source );
  end;
end;

procedure TIB_ColumnBlob.SetBlobData( ABuffer: Pointer; ASize: integer );
begin
  with Statement.CreateBlobStream( Self, bsmWrite ) do try
    WriteBuffer( ABuffer^, ASize );
  finally
    Free;
  end;
end;

procedure TIB_ColumnBlob.LoadFromFile( const AFileName: string );
var
  tmpStream: TStream;
begin
  tmpStream := TFileStream.Create( AFileName, fmOpenRead );
  try
    LoadFromStream( tmpStream );
  finally
    tmpStream.Free;
  end;
end;

procedure TIB_ColumnBlob.SaveToFile( const AFileName: string );
var
  tmpStream: TStream;
begin
// Get the contents of the BLOB loaded prior to creating the file.
  Statement.CreateBlobStream( Self, bsmRead ).Free;
  tmpStream := TFileStream.Create( AFileName, fmCreate );
  try
    SaveToStream( tmpStream );
  finally
    tmpStream.Free;
  end;
end;

procedure TIB_ColumnBlob.LoadFromStream( const AStream: TStream );
var
  tmpStream: TStream;
begin
  tmpStream := Statement.CreateBlobStream( Self, bsmWrite );
  try
    tmpStream.CopyFrom( AStream, 0 );
  finally
    tmpStream.Free;
  end;
end;

procedure TIB_ColumnBlob.SaveToStream( const AStream: TStream );
var
  tmpStream: TStream;
begin
  tmpStream := Statement.CreateBlobStream( Self, bsmRead );
  try
    AStream.CopyFrom( tmpStream, 0 );
  finally
    tmpStream.Free;
  end;
end;

procedure TIB_ColumnBlob.LoadFromBlob( const ABlob: TIB_ColumnBlob );
var
  tmpStream: TStream;
begin
  if ( not Assigned( ABlob )) or ( ABlob.IsNull ) then
    Clear
  else
  begin
    tmpStream := Statement.CreateBlobStream( Self, bsmWrite );
    try
      if not BlankIsNull then
        SysSetIsNull( false );
      ABlob.SaveToStream( tmpStream );
    finally
      tmpStream.Free;
    end;
  end;
end;

procedure TIB_ColumnBlob.SaveToBlob( const ABlob: TIB_ColumnBlob );
var
  tmpStream: TStream;
begin
  tmpStream := Statement.CreateBlobStream( Self, bsmRead );
  try
    ABlob.LoadFromStream( tmpStream );
  finally
    tmpStream.Free;
  end;
end;

procedure TIB_ColumnBlob.LoadFromGraphic( const AGraphic: TGraphic );
var
  Header: TIB_GraphicHeader;
  tmpStream: TStream;
begin
  tmpStream := Statement.CreateBlobStream( Self, bsmWrite );
  try
    if AGraphic <> nil then
    begin
      if ( SQLSubType = isc_blob_graphic      ) or
         ( SQLSubType = isc_blob_typed_binary ) then
      begin
        Header.Count := 1;
        Header.HType := $0100;
        Header.Size := 0;
        tmpStream.Write( Header, SizeOf(Header) );
        AGraphic.SaveToStream( tmpStream );
        Header.Size := tmpStream.Position - SizeOf( Header );
        tmpStream.Position := 0;
        tmpStream.Write( Header, SizeOf( Header ));
      end
      else
        AGraphic.SaveToStream( tmpStream );
    end;
  finally
    tmpStream.Free;
  end;
end;

procedure TIB_ColumnBlob.SaveToGraphic( const AGraphic: TGraphic );
var
  Size: Longint;
  Header: TIB_GraphicHeader;
  tmpStream: TStream;
begin
  tmpStream := Statement.CreateBlobStream( Self, bsmRead );
  try
    Size := tmpStream.Size;
    if Size >= SizeOf(TIB_GraphicHeader) then
    begin
      tmpStream.Read( Header, SizeOf( Header ));
      if ( Header.Count <> 1 ) or ( Header.HType <> $0100 ) or
        ( Header.Size <> Size - SizeOf( Header )) then
        tmpStream.Position := 0;
    end;
    if Assigned( AGraphic ) then
      AGraphic.LoadFromStream( tmpStream )
    else
      raise EIB_ColumnError.CreateWithSender( Statement,
                                              E_Unable_Save_Graphic );
  finally
    tmpStream.Free;
  end;
end;

procedure TIB_ColumnBlob.LoadFromStrings( const AStrings: TStrings );
var
  tmpStream: TStream;
begin
  tmpStream := Statement.CreateBlobStream( Self, bsmWrite );
  try
    if Assigned( AStrings ) then
      AStrings.SaveToStream( tmpStream );
  finally
    tmpStream.Free;
  end;
end;

procedure TIB_ColumnBlob.SaveToStrings( const AStrings: TStrings );
var
  tmpStream: TStream;
begin
  tmpStream := Statement.CreateBlobStream( Self, bsmRead );
  try
    if Assigned( AStrings ) then
      AStrings.LoadFromStream( tmpStream );
  finally
    tmpStream.Free;
  end;
end;

procedure TIB_ColumnBlob.SysSetIsNull( const AValue: boolean );
begin
  if AValue then
    if BlobNode.BlobChanged then
      BlobNode.BlobChanged := false;
  inherited SysSetIsNull( AValue );
end;

function TIB_ColumnBlob.IsEqualTo( ABlob: TIB_ColumnBlob ): boolean;
begin
  if IsNull <> ABlob.IsNull then
    Result := false
  else
  if IsNull and ABlob.IsNull then
    Result := true
  else
    Result := Statement.IB_Session.BlobsEqual(
                                      Statement.IB_Connection.PdbHandle,
                                      ABlob.Statement.IB_Connection.PdbHandle,
                                      Statement.IB_Transaction.PtrHandle,
                                      ABlob.Statement.IB_Transaction.PtrHandle,
                                      PSQLVAR, ABlob.PSQLVAR,
                                      BlobID, ABlob.BlobID,
                                      LoadedBlobNode, ABlob.LoadedBlobNode,
                                      Statement.OnBlobCallback,
                                      ABlob.Statement.OnBlobCallback );
end;

// IBA_ColumnArray.INT

constructor TIB_ColumnArray.Create( ARow: TIB_Row;
                                    PSQLVAR: PXSQLVAR;
                                    AFieldNo: smallint );
begin
  inherited Create( ARow, PSQLVAR, AFieldNo );
  FIsArray := true;
  Statement.CheckTransaction( true );
  if not IsAttributeSet[ IBO_BINARY ] then
    FPadChar := #32;
  with Statement.IB_Session do begin
    errcode := isc_array_lookup_bounds( @status,
                                        Statement.pdbHandle,
                                        Statement.ptrHandle,
                                        PSQLVAR.RelName,
                                        PSQLVAR.SQLName,
                                        @FArrayDesc );
  end;
end;

function TIB_ColumnArray.GetSQLTypeSource( const BaseTypeOnly: boolean ): string;
var
  ii: integer;
  tmpS: string;
begin
  tmpS := '';
  with ArrayDesc do
  begin
    case byte( ArrayDesc.array_desc_dtype ) of
      blr_text,
      blr_text2,
      blr_cstring,
      blr_cstring2: Result := 'CHAR( ' + IntToStr(array_desc_length) + ' )';
      blr_varying,
      blr_varying2: Result := 'VARCHAR( ' + IntToStr(array_desc_length) + ' )';
      blr_short: Result := 'SMALLINT';
      blr_long: Result := 'INTEGER';
      blr_float: Result := 'FLOAT';
      blr_double,
      blr_d_float: Result := 'DOUBLE PRECISION';
      blr_date: Result := 'DATE';
      else
      begin
        Result := inherited GetSQLTypeSource( true );
        if not BaseTypeOnly then
          tmpS := Copy( inherited GetSQLTypeSource( BaseTypeOnly ),
                        Length( Result ),
                        MaxInt );
      end;
    end;
    Result := Result + '[ ';
    for ii := 0 to array_desc_dimensions - 1 do
    begin
      if ii > 0 then
        Result := Result + ', ';
      Result := Result + IntToStr( array_desc_bounds[ii].array_bound_upper );
    end;
    Result := Result + ' ]';
  end;
  if not BaseTypeOnly then
    Result := Result + ' ' + tmpS;
end;

function TIB_ColumnArray.GetOldAsString: string;
begin
  if OldIsNull then
    Result := '(array)'
  else
    Result := '(ARRAY)';
end;

function TIB_ColumnArray.GetAsString: string;
begin
  if IsNull then
    Result := '(array)'
  else
    Result := '(ARRAY)';
end;

procedure TIB_ColumnArray.SetAsString( const NewValue: string);
begin
  raise EIB_ColumnError.CreateWithSender( Statement,
                                         E_Unable_to_assign_to_array_AsString );
end;

function TIB_ColumnArray.GetArrayID: isc_quad;
begin
  Result := isc_quad( FNewColumnBuffer^ );
end;

function TIB_ColumnArray.GetOldArrayID: isc_quad;
begin
  Result := isc_quad( FOldColumnBuffer^ );
end;

procedure TIB_ColumnArray.Clear;
begin
  Statement.CreateBlobStream( Self, bsmWrite ).Free;
end;

function GetElementCnt( ArrayDesc: PISC_ARRAY_DESC ): integer;
var
  ii: integer;
begin
  Result := 1;
  for ii := 0 to ArrayDesc.array_desc_dimensions - 1 do
    with ArrayDesc.array_desc_bounds[ii] do
      Result := Result * ( array_bound_upper - array_bound_lower + 1 );
end;

function GetElementSize( ArrayDesc: PISC_ARRAY_DESC ): integer;
begin
  Result := ArrayDesc.array_desc_length;
  case byte(ArrayDesc.array_desc_dtype) of
    blr_varying,
    blr_varying2: Inc( Result, SizeOf( smallint ));
  end;
end;

type
  TArrayDims = array [0..15] of integer;

function GetVarArrayElementValue( const Dims: integer;
                                  const DimArray: variant;
                                  const CurEle,
                                        DimAdj: array of integer ): variant;
begin
  case Dims of
     1: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ) ];
     2: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ) ];
     3: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ) ];
     4: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ) ];
     5: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ) ];
     6: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ) ];
     7: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ),
                            ( CurEle[ 6] - DimAdj[ 6] ) ];
     8: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ),
                            ( CurEle[ 6] - DimAdj[ 6] ),
                            ( CurEle[ 7] - DimAdj[ 7] ) ];
     9: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ),
                            ( CurEle[ 6] - DimAdj[ 6] ),
                            ( CurEle[ 7] - DimAdj[ 7] ),
                            ( CurEle[ 8] - DimAdj[ 8] ) ];
    10: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ),
                            ( CurEle[ 6] - DimAdj[ 6] ),
                            ( CurEle[ 7] - DimAdj[ 7] ),
                            ( CurEle[ 8] - DimAdj[ 8] ),
                            ( CurEle[ 9] - DimAdj[ 9] ) ];
    11: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ),
                            ( CurEle[ 6] - DimAdj[ 6] ),
                            ( CurEle[ 7] - DimAdj[ 7] ),
                            ( CurEle[ 8] - DimAdj[ 8] ),
                            ( CurEle[ 9] - DimAdj[ 9] ),
                            ( CurEle[10] - DimAdj[10] ) ];
    12: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ),
                            ( CurEle[ 6] - DimAdj[ 6] ),
                            ( CurEle[ 7] - DimAdj[ 7] ),
                            ( CurEle[ 8] - DimAdj[ 8] ),
                            ( CurEle[ 9] - DimAdj[ 9] ),
                            ( CurEle[10] - DimAdj[10] ),
                            ( CurEle[11] - DimAdj[11] ) ];
    13: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ),
                            ( CurEle[ 6] - DimAdj[ 6] ),
                            ( CurEle[ 7] - DimAdj[ 7] ),
                            ( CurEle[ 8] - DimAdj[ 8] ),
                            ( CurEle[ 9] - DimAdj[ 9] ),
                            ( CurEle[10] - DimAdj[10] ),
                            ( CurEle[11] - DimAdj[11] ),
                            ( CurEle[12] - DimAdj[12] ) ];
    14: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ),
                            ( CurEle[ 6] - DimAdj[ 6] ),
                            ( CurEle[ 7] - DimAdj[ 7] ),
                            ( CurEle[ 8] - DimAdj[ 8] ),
                            ( CurEle[ 9] - DimAdj[ 9] ),
                            ( CurEle[10] - DimAdj[10] ),
                            ( CurEle[11] - DimAdj[11] ),
                            ( CurEle[12] - DimAdj[12] ),
                            ( CurEle[13] - DimAdj[13] ) ];
    15: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ),
                            ( CurEle[ 6] - DimAdj[ 6] ),
                            ( CurEle[ 7] - DimAdj[ 7] ),
                            ( CurEle[ 8] - DimAdj[ 8] ),
                            ( CurEle[ 9] - DimAdj[ 9] ),
                            ( CurEle[10] - DimAdj[10] ),
                            ( CurEle[11] - DimAdj[11] ),
                            ( CurEle[12] - DimAdj[12] ),
                            ( CurEle[13] - DimAdj[13] ),
                            ( CurEle[14] - DimAdj[14] ) ];
    16: Result := DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                            ( CurEle[ 1] - DimAdj[ 1] ),
                            ( CurEle[ 2] - DimAdj[ 2] ),
                            ( CurEle[ 3] - DimAdj[ 3] ),
                            ( CurEle[ 4] - DimAdj[ 4] ),
                            ( CurEle[ 5] - DimAdj[ 5] ),
                            ( CurEle[ 6] - DimAdj[ 6] ),
                            ( CurEle[ 7] - DimAdj[ 7] ),
                            ( CurEle[ 8] - DimAdj[ 8] ),
                            ( CurEle[ 9] - DimAdj[ 9] ),
                            ( CurEle[10] - DimAdj[10] ),
                            ( CurEle[11] - DimAdj[11] ),
                            ( CurEle[12] - DimAdj[12] ),
                            ( CurEle[13] - DimAdj[13] ),
                            ( CurEle[14] - DimAdj[14] ),
                            ( CurEle[15] - DimAdj[15] ) ];
    else Result := Unassigned;
  end;
end;

procedure SetVarArrayElementValue( const Dims: integer;
                                   var DimArray: variant;
                                   const NewValue: variant;
                                   var CurEle: TArrayDims;
                                   const DimAdj: TArrayDims );
begin
  case Dims of
     1: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ) ] := NewValue;
     2: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ) ] := NewValue;
     3: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ) ] := NewValue;
     4: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ) ] := NewValue;
     5: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ) ] := NewValue;
     6: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ) ] := NewValue;
     7: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ),
                  ( CurEle[ 6] - DimAdj[ 6] ) ] := NewValue;
     8: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ),
                  ( CurEle[ 6] - DimAdj[ 6] ),
                  ( CurEle[ 7] - DimAdj[ 7] ) ] := NewValue;
     9: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ),
                  ( CurEle[ 6] - DimAdj[ 6] ),
                  ( CurEle[ 7] - DimAdj[ 7] ),
                  ( CurEle[ 8] - DimAdj[ 8] ) ] := NewValue;
    10: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ),
                  ( CurEle[ 6] - DimAdj[ 6] ),
                  ( CurEle[ 7] - DimAdj[ 7] ),
                  ( CurEle[ 8] - DimAdj[ 8] ),
                  ( CurEle[ 9] - DimAdj[ 9] ) ] := NewValue;
    11: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ),
                  ( CurEle[ 6] - DimAdj[ 6] ),
                  ( CurEle[ 7] - DimAdj[ 7] ),
                  ( CurEle[ 8] - DimAdj[ 8] ),
                  ( CurEle[ 9] - DimAdj[ 9] ),
                  ( CurEle[10] - DimAdj[10] ) ] := NewValue;
    12: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ),
                  ( CurEle[ 6] - DimAdj[ 6] ),
                  ( CurEle[ 7] - DimAdj[ 7] ),
                  ( CurEle[ 8] - DimAdj[ 8] ),
                  ( CurEle[ 9] - DimAdj[ 9] ),
                  ( CurEle[10] - DimAdj[10] ),
                  ( CurEle[11] - DimAdj[11] ) ] := NewValue;
    13: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ),
                  ( CurEle[ 6] - DimAdj[ 6] ),
                  ( CurEle[ 7] - DimAdj[ 7] ),
                  ( CurEle[ 8] - DimAdj[ 8] ),
                  ( CurEle[ 9] - DimAdj[ 9] ),
                  ( CurEle[10] - DimAdj[10] ),
                  ( CurEle[11] - DimAdj[11] ),
                  ( CurEle[12] - DimAdj[12] ) ] := NewValue;
    14: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ),
                  ( CurEle[ 6] - DimAdj[ 6] ),
                  ( CurEle[ 7] - DimAdj[ 7] ),
                  ( CurEle[ 8] - DimAdj[ 8] ),
                  ( CurEle[ 9] - DimAdj[ 9] ),
                  ( CurEle[10] - DimAdj[10] ),
                  ( CurEle[11] - DimAdj[11] ),
                  ( CurEle[12] - DimAdj[12] ),
                  ( CurEle[13] - DimAdj[13] ) ] := NewValue;
    15: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ),
                  ( CurEle[ 6] - DimAdj[ 6] ),
                  ( CurEle[ 7] - DimAdj[ 7] ),
                  ( CurEle[ 8] - DimAdj[ 8] ),
                  ( CurEle[ 9] - DimAdj[ 9] ),
                  ( CurEle[10] - DimAdj[10] ),
                  ( CurEle[11] - DimAdj[11] ),
                  ( CurEle[12] - DimAdj[12] ),
                  ( CurEle[13] - DimAdj[13] ),
                  ( CurEle[14] - DimAdj[14] ) ] := NewValue;
    16: DimArray[ ( CurEle[ 0] - DimAdj[ 0] ),
                  ( CurEle[ 1] - DimAdj[ 1] ),
                  ( CurEle[ 2] - DimAdj[ 2] ),
                  ( CurEle[ 3] - DimAdj[ 3] ),
                  ( CurEle[ 4] - DimAdj[ 4] ),
                  ( CurEle[ 5] - DimAdj[ 5] ),
                  ( CurEle[ 6] - DimAdj[ 6] ),
                  ( CurEle[ 7] - DimAdj[ 7] ),
                  ( CurEle[ 8] - DimAdj[ 8] ),
                  ( CurEle[ 9] - DimAdj[ 9] ),
                  ( CurEle[10] - DimAdj[10] ),
                  ( CurEle[11] - DimAdj[11] ),
                  ( CurEle[12] - DimAdj[12] ),
                  ( CurEle[13] - DimAdj[13] ),
                  ( CurEle[14] - DimAdj[14] ),
                  ( CurEle[15] - DimAdj[15] ) ] := NewValue;
  end;
end;

function CreateVarArray( const DimCount: integer;
                         const Bounds: array of integer;
                         const VarType: integer ): variant;
var
  Dim01: array [0.. 1] of integer absolute Bounds;
  Dim02: array [0.. 3] of integer absolute Bounds;
  Dim03: array [0.. 5] of integer absolute Bounds;
  Dim04: array [0.. 7] of integer absolute Bounds;
  Dim05: array [0.. 9] of integer absolute Bounds;
  Dim06: array [0..11] of integer absolute Bounds;
  Dim07: array [0..13] of integer absolute Bounds;
  Dim08: array [0..15] of integer absolute Bounds;
  Dim09: array [0..17] of integer absolute Bounds;
  Dim10: array [0..19] of integer absolute Bounds;
  Dim11: array [0..21] of integer absolute Bounds;
  Dim12: array [0..23] of integer absolute Bounds;
  Dim13: array [0..25] of integer absolute Bounds;
  Dim14: array [0..27] of integer absolute Bounds;
  Dim15: array [0..29] of integer absolute Bounds;
  Dim16: array [0..31] of integer absolute Bounds;
begin
  case DimCount of
     1: Result := VarArrayCreate( Dim01, varType );
     2: Result := VarArrayCreate( Dim02, varType );
     3: Result := VarArrayCreate( Dim03, varType );
     4: Result := VarArrayCreate( Dim04, varType );
     5: Result := VarArrayCreate( Dim05, varType );
     6: Result := VarArrayCreate( Dim06, varType );
     7: Result := VarArrayCreate( Dim07, varType );
     8: Result := VarArrayCreate( Dim08, varType );
     9: Result := VarArrayCreate( Dim09, varType );
    10: Result := VarArrayCreate( Dim10, varType );
    11: Result := VarArrayCreate( Dim11, varType );
    12: Result := VarArrayCreate( Dim12, varType );
    13: Result := VarArrayCreate( Dim13, varType );
    14: Result := VarArrayCreate( Dim14, varType );
    15: Result := VarArrayCreate( Dim15, varType );
    16: Result := VarArrayCreate( Dim16, varType );
  end;
end;

function IncrementCurEle( const Dims, StartDim: integer;
                          const DimUpper,
                                DimLower: TArrayDims;
                          var CurEle: TArrayDims ): boolean;
begin
  if StartDim > Dims then
    Result := false
  else
  if CurEle[StartDim-1] = DimUpper[StartDim-1] then
  begin
    CurEle[StartDim-1] := DimLower[StartDim-1];
    Result := IncrementCurEle( Dims, StartDim + 1, DimUpper, DimLower, CurEle );
  end
  else
  begin
    Inc( CurEle[StartDim-1] );
    Result := true;
  end;
end;

type
  pcardinal = ^Cardinal;

function SysGetVarArray( const ArrayDesc: ISC_ARRAY_DESC;
                               ArrayReader: TIB_ArrayReader ): variant;
var
  EleNo: integer;
  EleSize: integer;
  ii: integer;
  ABuf: pointer;
  ABytes: integer;
  tmpStr: string;
  tmpQuad:ISC_QUAD;
  DimLower: TArrayDims;
  DimUpper: TArrayDims;
  DimBounds: array[0..31] of integer;
  DimEles: TArrayDims;
  DimAdj: TArrayDims;
  CurEle: TArrayDims;
  tmpVal: variant;
  vtype: integer;
  AVarChr: PSQL_VARCHAR;
begin
  EleNo := GetElementCnt( @ArrayDesc );
  EleSize := GetElementSize( @ArrayDesc );
  ABuf := AllocMem( EleSize );
  with ArrayDesc do
  try
    for ii := 0 to array_desc_dimensions - 1 do
    begin
      DimLower[ii] := array_desc_bounds[ii].array_bound_lower;
      DimUpper[ii] := array_desc_bounds[ii].array_bound_upper;
      DimBounds[ii*2] := array_desc_bounds[ii].array_bound_lower;
      DimBounds[ii*2+1] := array_desc_bounds[ii].array_bound_upper;
      DimEles[ii] := array_desc_bounds[ii].array_bound_upper -
                     array_desc_bounds[ii].array_bound_lower + 1;
      DimAdj[ii] := 0;
      CurEle[ii] := array_desc_bounds[ii].array_bound_lower;
    end;
    case byte( array_desc_dtype ) of
      blr_short: vtype := varSmallint;
      blr_long: vtype := varInteger;
      blr_float: vtype := varSingle;
      blr_double,
      blr_d_float: vtype := varDouble;
      blr_date,blr_sql_date,blr_sql_time: vtype :=varDate;
      else vtype := varVariant;
    end;
    Result := CreateVarArray( array_desc_dimensions, DimBounds, vtype );
    repeat
      ABytes := EleSize;
      ArrayReader( ABuf^, ABytes );
      case byte( array_desc_dtype ) of
        blr_text,
        blr_text2,
        blr_cstring,
        blr_cstring2:
        begin
          SetLength( tmpStr, EleSize );
          Move( ABuf^, tmpStr[1], EleSize );
          tmpVal := tmpStr;
        end;
        // Reading value.
        blr_varying,
        blr_varying2:
        begin
          AVarChr := PSQL_VARCHAR( ABuf );
          SetLength( tmpStr, AVarChr.vary_len_high * 256 +
                             AVarChr.vary_len_low );
          Move( AVarChr.vary_string[0],
                tmpStr[1],
                AVarChr.vary_len_high * 256 +
                AVarChr.vary_len_low );
          tmpVal := tmpStr;
        end;
        blr_short: tmpVal := smallint( ABuf^ );
        blr_long: tmpVal := integer( ABuf^ );
        blr_float: tmpVal := single( ABuf^ );
        blr_double,
        blr_d_float: tmpVal := double( ABuf^ );
        blr_date: tmpVal := isc_decode_TDateTime( pisc_quad( ABuf ));
        blr_sql_date:
        begin
          tmpQuad.isc_quad_low := 0;
          tmpQuad.isc_quad_high := pinteger( ABuf )^;
          tmpVal := isc_decode_TDateTime( @tmpQuad );
        end;
        blr_sql_time:
        begin
          tmpQuad.isc_quad_low := pcardinal( ABuf )^;
          tmpQuad.isc_quad_high := 15018; // results in year of value zero
          tmpVal := isc_decode_TDateTime( @tmpQuad );
        end
        else
          raise Exception.Create( E_Unsupported_array_variant_conversion );
      end;
      SetVarArrayElementValue( array_desc_dimensions,
                               Result,
                               tmpVal,
                               CurEle,
                               DimAdj );
    until ( not IncrementCurEle( array_desc_dimensions,
                                 1,
                                 DimUpper,
                                 DimLower,
                                 CurEle )) or ( EleNo < 0 );
  finally
    FreeMem( ABuf );
  end;
end;

procedure SysPutVarArray( const ArrayDesc: ISC_ARRAY_DESC;
                                ArrayWriter: TIB_ArrayWriter;
                                NewValue: variant;
                                ArrayPadChar: char );
var
  EleNo: integer;
  EleSize: integer;
  ii: integer;
  ABuf: pointer;
  ABytes: integer;
  tmpQuad:ISC_QUAD;
  tmpStr: string;
  StrLen: smallint;
  tmpVal: variant;
  DimLower: TArrayDims;
  DimUpper: TArrayDims;
  DimEles: TArrayDims;
  DimAdj: TArrayDims;
  CurEle: TArrayDims;
  AVarChr: PSQL_VARCHAR;
begin
  EleNo := GetElementCnt( @ArrayDesc );
  EleSize := GetElementSize( @ArrayDesc );
  ABuf := AllocMem( EleSize );
  SetLength( tmpStr, EleSize );
  with ArrayDesc do
  try
    if array_desc_dimensions <> VarArrayDimCount( NewValue ) then
      raise Exception.Create( E_Dimensions_are_different );
    for ii := 0 to array_desc_dimensions - 1 do
    begin
      DimLower[ii] := array_desc_bounds[ii].array_bound_lower;
      DimUpper[ii] := array_desc_bounds[ii].array_bound_upper;
      DimEles[ii] := DimUpper[ii] - DimLower[ii] + 1;
      if ( VarArrayHighBound( NewValue, ii + 1 ) -
           VarArrayLowBound( NewValue, ii + 1 ) + 1 ) <> DimEles[ii] then
        raise Exception.Create( E_Dimension_has_diff_no_of_elements );
      DimAdj[ii] := DimLower[ii] - VarArrayLowBound( NewValue, ii + 1 );
      CurEle[ii] := DimLower[ii];
    end;
    repeat
      tmpVal := GetVarArrayElementValue( array_desc_dimensions,
                                         NewValue,
                                         CurEle,
                                         DimAdj );
      case byte( array_desc_dtype ) of
        blr_cstring,
        blr_cstring2:
        begin
          tmpStr := PadRight( tmpVal, ArrayPadChar, EleSize, true );
          Move( tmpStr[1], ABuf^, EleSize );
        end;
        blr_text,
        blr_text2:
        begin
          tmpStr := PadRight( tmpVal, ArrayPadChar, EleSize, true );
          Move( tmpStr[1], ABuf^, EleSize );
        end;
        // Writing value.
        blr_varying,
        blr_varying2:
        begin
          tmpStr := tmpVal;
          StrLen := Length( tmpStr );
          if StrLen > integer(ArrayDesc.array_desc_length) then
            StrLen := integer(ArrayDesc.array_desc_length);
          AVarChr := PSQL_VARCHAR( ABuf );
          FillChar( AVarChr.vary_string[0],
                    ArrayDesc.array_desc_length,
                    ArrayPadChar );
          AVarChr.vary_len_low := StrLen mod 256;
          AVarChr.vary_len_high := StrLen div 256;
          Move( tmpStr[1], AVarChr.vary_string[0], StrLen );
        end;
        blr_short: psmallint( ABuf )^ := tmpVal;
        blr_long: try
          if Length( Trim( tmpVal )) = 0 then tmpVal := 0;
          pinteger( ABuf )^ := StrToInt( Trim( tmpVal ));
        except
          pinteger( ABuf )^ := 0;
        end;
        blr_float: try
          if Length( Trim( tmpVal )) = 0 then tmpVal := 0;
          psingle( ABuf )^ := StrToFloat( Trim( tmpVal ));
        except
          psingle( ABuf )^ := 0;
        end;
        blr_double,
        blr_d_float: try
          if Length( Trim( tmpVal )) = 0 then tmpVal := 0;
          pdouble( ABuf )^ := StrToFloat( Trim( tmpVal ));
        except
          pdouble( ABuf )^ := 0;
        end;
        blr_sql_date:
        try
          if (varType(tmpVal)=varOleStr) then
            if (tmpVal<>'') then
            begin
              isc_encode_TDateTime( StrToDateTime(tmpVal), @tmpQuad);
              pinteger(ABuf)^:=tmpQuad.isc_quad_high;
            end
            else
              FillChar( ABuf^, EleSize, 0 )
          else
            isc_encode_TDateTime( tmpVal, pisc_quad( ABuf ));
        except
          FillChar( ABuf^, EleSize, 0 );
        end;
        blr_sql_time:
        try
          if (varType(tmpVal)=varOleStr) then
            if (tmpVal<>'') then
            begin
              isc_encode_TDateTime( StrToDateTime(tmpVal), @tmpQuad);
              pcardinal(ABuf)^:=tmpQuad.isc_quad_low;
            end
            else
              FillChar( ABuf^, EleSize, 0 )
          else
            isc_encode_TDateTime( tmpVal, pisc_quad( ABuf ));
        except
          FillChar( ABuf^, EleSize, 0 );
        end;
        blr_date: try
          if (varType(tmpVal)=varOleStr) then
             if (tmpVal<>'') then
                isc_encode_TDateTime( StrToDateTime(tmpVal), pisc_quad( ABuf ))
             else
                FillChar( ABuf^, EleSize, 0 )
          else
             isc_encode_TDateTime( tmpVal, pisc_quad( ABuf ));
        except
          FillChar( ABuf^, EleSize, 0 );
        end
        else
          raise Exception.Create( E_Unsupported_array_variant_conversion );
      end;
      ABytes := EleSize;
      ArrayWriter( ABuf^, ABytes );
      Dec( EleNo );
    until ( not IncrementCurEle( array_desc_dimensions,
                                 1,
                                 DimUpper,
                                 DimLower,
                                 CurEle )) or ( EleNo < 0 );
  finally
    FreeMem( ABuf );
  end;
end;

function TIB_ColumnArray.GetAsVariant: variant;
var
  AStream: TStream;
begin
  if IsNull then
    Result := Unassigned
  else
  begin
    AStream := Statement.CreateBlobStream( Self, bsmRead );
    try
      Result := SysGetVarArray( FArrayDesc, AStream.ReadBuffer );
    finally
      AStream.Free;
    end;
  end;
end;

procedure TIB_ColumnArray.SetAsVariant( const NewValue: variant );
var
  AStream: TStream;
begin
  if VarIsEmpty( NewValue ) or VarIsNull( NewValue ) then
    Clear
  else
  if VarIsArray( NewValue ) then
  begin
    SysBeforeModify;
    AStream := Statement.CreateBlobStream( Self, bsmWrite );
    try
      SysPutVarArray( FArrayDesc, AStream.WriteBuffer, NewValue, FPadChar );
    finally
      AStream.Free;
    end;
    SysSetIsNull( false );
    SysAfterModify;
  end
  else
    raise Exception.Create( E_Variant_must_be_of_an_array_type );
end;

{------------------------------------------------------------------------------}

procedure TIB_ColumnArray.GetArray( PArrayBuffer: pointer;
                                    PArraySize: pointer );
begin GetSlice( @FArrayDesc, PArrayBuffer, PArraySize ); end;
procedure TIB_ColumnArray.PutArray( PArrayBuffer: pointer;
                                    PArraySize: pointer );
begin PutSlice( @FArrayDesc, PArrayBuffer, PArraySize ); end;

procedure TIB_ColumnArray.GetSlice( PArrayDesc: PISC_ARRAY_DESC;
                                    PArrayBuffer: pointer;
                                    PArraySize: pointer );
begin
  if IsNull then
    raise EIB_ColumnError.CreateWithSender( Statement, E_Array_Is_Null );
  Statement.CheckTransaction( true );
  with Statement.IB_Session do
  begin
    errcode := isc_array_get_slice ( @status,
                                     Statement.pDBHandle,
                                     Statement.pTRHandle,
                                     PSQLVAR.SQLData,
                                     PArrayDesc,
                                     PArrayBuffer,
                                     PArraySize );
    if errcode <> 0 then HandleException( Statement );
  end;
end;

procedure TIB_ColumnArray.PutSlice( PArrayDesc: PISC_ARRAY_DESC;
                                    PArrayBuffer: pointer;
                                    PArraySize: pointer );
begin
  SysBeforeModify;
  SysSetIsNull( false );
  Statement.CheckTransaction( true );
  with Statement.IB_Session do
  begin
    errcode := isc_array_put_slice ( @status,
                                     Statement.pDBHandle,
                                     Statement.pTRHandle,
                                     PSQLVAR.SQLData,
                                     PArrayDesc,
                                     PArrayBuffer,
                                     PArraySize );
    if errcode <> 0 then HandleException( Statement );
  end;
  SysAfterModify;
end;

function TIB_ColumnArray.GetVarSlice( Dimensions: array of integer ): variant;
var
  des: ISC_ARRAY_DESC;
  ii, jj: integer;
  bufsize: integer;
  AStream: TMemoryStream;
begin
  if IsNull then
  begin
    Result := Unassigned;
    Exit;
  end;
  des := ArrayDesc;
  if high( Dimensions ) <> 0 then
  begin
    if ( not Odd( high( Dimensions ))) then
      raise Exception.Create( E_Invalid_dimensions );
    des.array_desc_dimensions := ( high( Dimensions ) + 1 ) div 2;
    jj := 0;
    for ii := 0 to ((( high( dimensions ) + 1 ) div 2 ) - 1 ) do
    begin
      with des.array_desc_bounds[ii] do
      begin
        if array_bound_lower > dimensions[jj] then
          raise Exception.Create( E_Lower_dimension_out_of_bounds )
        else
          array_bound_lower := dimensions[jj];
        if array_bound_upper < dimensions[jj+1] then
          raise Exception.Create( E_Upper_dimension_out_of_bounds )
        else
          array_bound_upper := dimensions[jj+1];
      end;
      Inc( jj, 2 );
    end;
  end;
  AStream := TMemoryStream.Create;
  try
    bufsize := GetArrayBufferLength( @des );
    AStream.SetSize( bufsize );
    Statement.CheckTransaction( true );
    with Statement, IB_Session do
    begin
      errcode := isc_array_get_slice( @status,
                                      PdbHandle,
                                      PTRHandle,
                                      PSQLVAR.SQLData,
                                      @des,
                                      AStream.Memory,
                                      @bufsize );
      if errcode <> 0 then
        HandleException( Statement );
    end;
    Result := SysGetVarArray( des, AStream.ReadBuffer );
  finally
    AStream.Free;
  end;
end;

procedure TIB_ColumnArray.PutVarSlice( const Values: variant );
var
  ii, bufsize: integer;
  Dims: integer;
  LBnd, HBnd: integer;
  AStream: TMemoryStream;
  des: ISC_ARRAY_DESC;
begin
  SysBeforeModify;
  des := ArrayDesc;
  dims := VarArrayDimCount( Values );
  for ii := 1 to Dims do
  begin
    LBnd := VarArrayLowBound( Values, ii );
    HBnd := VarArrayHighBound( Values, ii );
    with des.array_desc_bounds[ii-1] do
    begin
      if LBnd < array_bound_lower then
      begin
        raise Exception.Create( E_Lower_dimension_out_of_bounds );
      end
      else
      begin
        array_bound_lower := LBnd;
      end;
      if HBnd > array_bound_upper then
      begin
        raise Exception.Create( E_Upper_dimension_out_of_bounds );
      end
      else
      begin
        array_bound_upper := HBnd;
      end;
    end;
  end;
  AStream := TMemoryStream.Create;
  try
    bufsize := GetArrayBufferLength( @des );
    AStream.SetSize( bufsize );
    SysPutVarArray( des, AStream.WriteBuffer, Values, FPadChar );
    Statement.CheckTransaction( true );
    with Statement, IB_Session do
    begin
      errcode := isc_array_put_slice( @status,
                                      PdbHandle,
                                      PTRHandle,
                                      PSQLVAR.SQLData,
                                      @des,
                                      AStream.Memory,
                                      @bufsize );
      if errcode <> 0 then HandleException( Statement );
    end;
  finally
    AStream.Free;
  end;
  SysSetIsNull( false );
// Need to finish the auto re-fetch of the new ARRAY ID after this call is made.
// Otherwise, an Invalid BLOB ID error is generated if a call to get_slice
// is performed using the temporary Array ID that put slice creates.
  SysAfterModify;
end;

function TIB_ColumnArray.IsEqualTo( AArray: TIB_ColumnArray ): boolean;
var
  st1, st2: TIB_BlobStream;
begin
  Result := false;
  if IsNull <> AArray.IsNull then
  else
  if IsNull and AArray.IsNull then
    Result := true
  else
  begin
    st1 := Statement.CreateBlobStream( Self, bsmRead );
    st2 := AArray.Statement.CreateBlobStream( AArray, bsmRead );
    try
      if st1.Size = st2.Size then
        Result := BuffersEqual( st1.BlobNode.BlobBuffer,
                                st2.BlobNode.BlobBuffer,
                                st1.BlobNode.BlobSize );
    finally
      st1.Free;
      st2.Free;
    end;
  end;
end;

initialization
  ReserveSessionHookRef;

{$IFDEF NAG}
  if Pos( 'DELPHI32.EXE', UpperCase( Application.EXEName )) > 0 then
  else
  if Pos( 'IB_SQL.EXE', UpperCase( Application.EXEName )) > 0 then
  else
  if Pos( 'IB_FTS.EXE', UpperCase( Application.EXEName )) > 0 then
  else
  if Pos( 'IB_RPL.EXE', UpperCase( Application.EXEName )) > 0 then
  else
  if FindWindow( 'TAppBuilder', nil ) > 0 then
  else
    ShowMessage( IB_REG_MESSAGE );
{$ENDIF}

finalization
  try
    ClearConnectionPool;
  except
  // Eat exceptions for lunch, we are going home.
  end;
  ReleaseSessionHookRef;

end.

