Files
lazarus-ccr/components/flashfiler/sourcelaz/ffsreng.pas
2016-12-07 13:31:59 +00:00

15748 lines
569 KiB
ObjectPascal

{Notes:
1. The perform dynamic link call has been commented out in the
server engine create.
2. Server-side objects are freed when a client requests the object be closed
(e.g., SessionRemove) & all of its dependent objects report they be
closed. For example, a TffSrDatabase can be closed only if no other
thread is using the TffSrDatabase and its associated cursors report they
are inactive.
If a server-side object cannot be freed when the close request is received
from the client then the server's garbage collection thread will
eventually free the object.
3. When adding new TffServerEngine methods, please follow these guidelines:
a. All steps should be wrapped with a Try..Except block. At a minimum,
the Try..Except block must do the following:
try
...
except
on E : Exception do begin
Result := ConvertServerException(E, btEngine.EventLog);
end;
end;
This ensures the client is returned an error code that it
understands.
b. If you call any of the CheckxxxIDAndGet methods, the remaining
code should be wrapped with a try..finally block. The Finally
section should call "xxxx.Deactivate". Why? Because the
CheckxxxxIDAndGet methods mark the relevant object as Active to make
sure it is not freed by another thread. Once the operation has
completed, the object must be marked as Inactive so that it may
be closed and freed at a later time.
}
{*********************************************************}
{* FlashFiler: Server Engine class *}
{*********************************************************}
(* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is TurboPower FlashFiler
*
* The Initial Developer of the Original Code is
* TurboPower Software
*
* Portions created by the Initial Developer are Copyright (C) 1996-2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** *)
{$I FFDEFINE.INC}
{ Enable the following define to debug RAM pages. }
{.$DEFINE RAMPageCheck}
{ Enable the following to debug the deleted record count. }
{.$DEFINE DebugDelCount}
{ Diasable the following to retrieve files using DatabaseTableList that
are not FlashFiler 2 Tables. }
{$DEFINE OnlyRetrieveTables} {!!.01}
unit ffsreng;
interface
uses
Windows,
SysUtils,
Classes,
Forms,
FFStDate,
FFConst,
FFLLBase,
FFLLEng,
FFLLDict,
FFLLThrd,
FFSrMgr,
FFLLExcp,
FFLLLog,
FFLLProt,
FFLLTemp,
FFLLUNC,
FFHash,
FFNetMsg,
FFSrBase,
FFFile,
FFSqlBas,
FFSrIntf,
FFSrBDE,
FFSrCfg,
FFSrFMap,
FFSrIntm,
FFSrStat,
FFSrCvEx,
FFSrFold,
FFSrIxhl,
FFSrLock,
FFSrTran,
FFSrFltr,
FFConvFF,
FFTbBase,
FFTbData,
FFTbBLOB,
FFTbDict,
FFTbIndx;
{===Read/Write alias data from table=================================}
const
ffc_SavPrefix = 'SAV';
ffc_StdPrefix = 'FFS';
ffc_TmpPrefix = 'XXS';
ffc_AliasSuffix = 'ALIAS';
ffc_IndexSuffix = 'INDEX';
ffc_InfoSuffix = 'INFO';
ffc_UserSuffix = 'USER';
ffc_AliasTableName = 'FFSALIAS';
ffc_SavedAliasTableName = 'SAVALIAS';
ffc_TempAliasTableName = 'XXSALIAS';
ffc_IndexTableName = 'FFSINDEX';
ffc_SavedIndexTableName = 'SAVINDEX';
ffc_TempIndexTableName = 'XXSINDEX';
ffc_GenInfoTableName = 'FFSINFO';
ffc_SavedGenInfoTableName = 'SAVINFO';
ffc_TempGenInfoTableName = 'XXSINFO';
ffc_UserTableName = 'FFSUSER';
ffc_SavedUserTableName = 'SAVUSER';
ffc_TempUserTableName = 'XXSUSER';
ffc_AliasScript = 'FFAlias.sc$';
ffc_ClientShutdownTime : TffWord32 = 10000; {!!.05}
ffc_StartTranWithDelay : DWORD = 10; {!!.10}
{ Used with TransactionStartWith. If a lock cannot be immediately obtained
then the operation will be retried every ffc_StartTranWithDelay
milliseconds. }
type
TffCursorPosition = ( {Positions of a cursor in an index}
cpUnknown, {..unknown: must be resolved asap}
cpBOF, {..prior to first record}
cpEOF, {..after last record}
cpOnCrack, {..in between two records}
cpOnRecord); {..on a record somewhere}
TffRecOp = ( {Record update operations}
roInsert, {..insertion}
roDelete, {..deletion}
roModify); {..modification}
type
PffSrBookmark = ^TffSrBookmark;
TffSrBookmark = packed record
sbHash : Longint; {validity check}
sbIndexID : Longint;
sbPos : TffCursorPosition;
sbKeyValid : boolean;
sbFill1 : array [0..1] of byte; {to DWORD align}
sbRefNr : TffInt64;
sbKeyLen : Longint;
sbKey : array [0..1] of byte;
end;
type
TffServerEngine = class; {forward declaration}
TffSrTableClass = class of TffSrBaseTable; {forward declaration}
TffSrBaseTable = class; {forward declaration}
TffSrDatabase = class; {forward declaration}
TffSrSession = class; {forward declaration}
TffSrClient = class; {forward declaration}
TffSrStmtList = class; {forward declaration} {!!.10}
{ This type identifies the state of a TffServerObject. Given the
multi-threaded nature of the server engine, it is possible for thread A
to be using an object while thread B processes a command that would result
in the closing and freeing of the object. For example, in thread A a
cursor is waiting to obtain an exclusive page lock. While the cursor
is waiting, the client times out and issues a CloseCursor command to
the server. Thread B processes the CloseCursor command. Thread B
must see that the cursor is active and thread B must not free the cursor.
Doing so would cause an access violation as soon as thread A tries to
use the cursor once more. }
TffServerObjectState = (ffosInactive, ffosActive, ffosClosePending,
ffosClosing);
{ ffosInactive - The object is not being used by a thread.
ffosActive - The object is being used by a thread.
ffosClosePending - Thread A is using the object but thread B wants
to free the object. Thread A is responsible for freeing the object
once it has finished its operation.
ffosClosing - The object is being freed by a thread. }
{ Contains the essential properties and methods for a server object (e.g.,
client, session, database, cursor). Before a thread can use a server object
it must call the Activate method. If the object can be used then the
Activate method returns True.
When a thread has finished using a server object, it must call the
Deactivate method.
When a thread wants to close and free an object, it must call the
Close method. If the Close method returns True then the thread must
call TffServerObject.Free. }
TffServerObject = class(TffSelfListItem)
protected
soClient : TffSrClient; {!!.10}
{ This is a reference to the server object's parent TffSrClient.
It is instantiated for TffSrDatabase, TffSrBaseTable,
and TffSrBaseCursor. }
soLock : TffPadlock;
{ Padlock used to prevent re-entrancy on a per-client basis.
This lock is instantiated only for TffServerObjects of type
TffSrClient. }
soState : TffServerObjectState;
soTimeout : Longint;
public
constructor Create(const aTimeout : Longint);
destructor Destroy; override;
function Activate : boolean;
{ This method must be called before a thread can use a server object.
If State is ffosInactive then sets State to ffosActive and returns
True. Otherwise returns False. }
function CanClose(const Mark : boolean) : boolean; virtual;
{ When a server object is to be freed, call this method. If the
object can be freed this method returns True otherwise it returns
False. If the Mark parameter is True then the object's state is
set to ffosClosing. }
procedure Deactivate;
{ When a thread has finished using a server object, it must call this
method.
If State is ffosShutdownPending then the object frees itself.
If State is ffosActive then switches to ffosInactive.
If State is ffosShuttingDown then does nothing with the assumption
that another thread will finish the object's shutdown. }
procedure ForceClose; virtual;
{ Sets the client's state to ffosClosing so that it will free itself
when the server next requests the client to be removed. }
procedure RequestClose; virtual; {!!.03}
{ If an object cannot be closed (i.e., CanClose returns False) then
call this method to submit a request to close the object. }
function ShouldClose : boolean; virtual;
{ When a server object is ready to be freed (i.e., State = ffosClosing),
this method returns True. }
{ Properties }
property Client : TffSrClient read soClient; {!!.10}
{ The object's parent client object. }
property State : TffServerObjectState read soState write soState;
{ The current state of the object. }
property Timeout : Longint read soTimeout write soTimeout;
{ The object's timeout value. }
end;
{ This is the base class for lists of TffServerObjects. }
TffServerObjectList = class(TffObject)
protected {private}
solList : TffThreadList;
protected
public
constructor Create; virtual; {!!.01}
destructor Destroy; override;
procedure BeginRead;
{ A thread must call this method to gain read access to the list. }
procedure BeginWrite;
{ A thread must call this method to gain write access to the list. }
function CanClose(const Mark : boolean) : boolean; virtual;
{ Used to determine if all the server objects in the list can be
closed. Returns True if all can be closed otherwise returns False. }
procedure EndRead;
{ A thread must call this method when it no longer needs read access
to the list. If it does not call this method, all writers will
be perpetually blocked. }
procedure EndWrite;
{ A thread must call this method when it no longer needs write access
to the list. If it does not call this method, all readers and writers
will be perpetualy blocked. }
procedure ForceClose; virtual;
{ Use this method to force all objects within the list to set themselves
to a ffosClosing state. }
{Begin !!.06}
function HasClosableState(const Mark : Boolean) : boolean;
{ Use this method to determine if objects have a closable state. Ignores
all other facets of the object. If the Mark parameter is True and all
objects in the list can be closed then sets all objects with state
ffosInactive to ffosClosing. }
{End !!.06}
procedure RemoveUnused; virtual;
{ Use this method to free objects that could not be freed at the time
they were closed. }
{Begin !!.03}
procedure RequestClose; virtual;
{ Use this method to request a close on all objects contained in the
list. }
{End !!.03}
function ShouldClose : boolean; virtual;
{ Use this method to determine if all the objects in the list should
be closed. }
end;
TffSrCursorInfo = packed record
Deleted : boolean;
{ If true then the record referenced by this information has been
deleted. }
KeyPath : TffKeyPath;
{This is a trail into the current index that leads us to a
specific record, crack between two records, EOF, or BOF}
KeyValid : boolean;
{This variable is set to True when we position to the
next or previous record, reposition to an existing record,
retrieve a record for a key, or position to a bookmark that is on a
valid record.
When this variable is True, we can rely upon the key stored in
variable bcCurKey.
This variable is set to False when we insert a record, modify a
record, or otherwise need to force a recalculation of the key
path to a record (e.g., TffSrCursor.SetToBegin,
TffSrCursor.SwitchToIndex). }
Pos : TffCursorPosition;
{ This tells us whether the cursor is on a specific record, at BOF,
at EOF, or on a crack between two records. }
RefNr : TffInt64;
{ Reference number of the current record. This is its physical position
within the file. For example, if RefNr = 128,556 then the record
starts at position 128,556 within the data file. }
end;
TffContentLockMode = (ffclmCommit, ffclmRead, ffclmWrite);
{ Used by cursor to indicate what type of content lock is needed. }
TffSrBaseCursor = class; {forward declaration}
TffSrCursorClass = class of TffSrBaseCursor; {forward declaration} {!!.06}
TffSrCopyRecordsProc = procedure(aSrcCursor : TffSrBaseCursor;
aSrcRecord : PffByteArray;
aCookie1, aCookie2 : Longint;
var include : boolean) of object;
{ Defines the event handler for the CopyRecords method.
SrcCursor is the cursor from which the record is being copied.
aSrcRecord is the record to be copied.
Set include to True if the record is to be copied, otherwise set it to
False. }
{ Use the following type to describe how columns within a simple table should
be sorted. }
TffOrderByDirection = (ffobAscending, ffobDescending);
PffOrderByArray = ^TffOrderByArray;
TffOrderByArray = array[0..ffcl_MaxIndexFlds] of TffOrderByDirection;
{ Defines the standard interface for a cursor. Note that once you create a
cursor, you must call its Open method to open a cursor for an existing
table. If the table does not yet exist, use the Build method to create
the table and open the cursor on the new table. }
TffSrBaseCursor = class(TffServerObject)
protected {private}
bcTableClass : TffSrTableClass;
{ The type of table to be created by the cursor.
Be sure to initialize it to the appropriate value in the
inherited constructors before calling one of the TffSrBaseCursor
constructors. }
bcBLOBCursors : TffList; { List of cursors for which we have
dereferenced BLOB links. }
bcCloseTable : Boolean; { Set to True if the cursor is to close
its table when the cursor is freed.
Standard cursors leave the table open
because other clients may need to access
the same table. SQL cursors close the
table right away because the result set
is typically for only one client. }
bcCloseWTrans : Boolean; {!!.05}
bcDatabase : TffSrDatabase;
bcEngine : TffServerEngine; {the engine with which this cursor is
associated }
bcExclOwner : Boolean; {If True then cursor has exclusively
opened the table. }
bcExtenders : TffList;
{-List of engine extenders associated with this cursor. }
bcIndexID : Longint;
{Begin !!.03}
bcLockedRefNum : TffInt64; { Last record locked via GetRecord
method. The cursor tracks this to
ensure that a record lock obtained
via TffTable.Edit, while an implicit
transaction is in effect, will be
unlocked if the client abruptly
terminates. }
{End !!.03}
bcNumReadLocks : Integer; { Number of open read locks.} {!!.05}
bcTable : TffSrBaseTable;
bcTempStore : TffBaseTempStorage;
bcKID : TffKeyIndexData; {work field for index access}
bcCompareData: TffCompareData; {ditto}
bcCurKey : PffByteArray; {current key}
bcFilter : TffSrFilter; {filter object}
bcFilterSav : TffSrFilter; {overridden filter}
bcHasRange : Boolean; {whether range is active}
bcInfo : TffSrCursorInfo; {the cursor's current position, key path,
reference number, etc. }
{Begin !!.06}
bcInfoLock : TffPadlock; {Used to prevent transaction from
clearing a cursor's key path while the
cursor is navigating to next or prev
record. }
{End !!.06}
bcOpenMode : TffOpenMode;
bcRecordData : PffByteArray; {work record data area}
bcRecordLen : Integer; {record length}
bcRng1Valid : Boolean; {is low range point valid?}
bcRng2Valid : Boolean; {is high range point valid?}
bcRng1Key : PffByteArray; {range start key}
bcRng2Key : PffByteArray; {range end key}
bcRng1FldCnt : Integer; {range start field count}
bcRng2FldCnt : Integer; {range end field count}
bcRng1PtlLen : Integer; {range start partial length}
bcRng2PtlLen : Integer; {range end partial length}
bcRng1Incl : Boolean; {range includes start key}
bcRng2Incl : Boolean; {range includes end key}
bcSavedInfo : TffSrCursorInfo; {temporary work area for bcSaveCurInfo &
bcRestoreCurInfo }
bcNewRecBuff : PffByteArray; { exclusively used by extenders }
bcOldRecBuff : PffByteArray; { exclusively used by extenders }
bcNeedNestedTransaction : Boolean; {If set to true all operations on the
cursor use a nested transaction if needed}
procedure bcAddExtender(anExtender : TffBaseEngineExtender);
{-Use this method to add an extender to the list of extenders
interested in a cursor. }
function bcBLOBCopy(aSrcCursor : TffSrBaseCursor;
const aBLOBNr : TffInt64;
var aDestBLOBNr : TffInt64) : TffResult;
{ Used to copy a BLOB from one cursor to another. }
function bcBLOBLinkGetLength(const aTableName : TffTableName;
const aBLOBNr : TffInt64;
var aLength : Longint) : TffResult; virtual;
{-Used to obtain the length of a BLOB referenced by a BLOB link within
a record of this cursor's result set. }
function bcBLOBLinkRead(const aTableName : TffTableName;
const aBLOBNr : TffInt64;
const aOffset : TffWord32; {!!.06}
const aLen : TffWord32; {!!.06}
var aBLOB;
var aBytesRead : TffWord32) {!!.06}
: TffResult;
{-Used to read a BLOB referenced by a BLOB link within a record of this
cursor's result set. }
function bcCheckExclusiveReadWrite : TffResult; virtual;
{-Verifies the cursor has exclusive read-write access to the table. }
function bcFindBLOBCursor(const aTableName : TffTableName) : TffSrBaseCursor; virtual;
{-Finds a BLOB cursor based upon a table name. }
function bcGetAttribs : TffFileAttributes; virtual;
function bcGetCursorID : TffCursorID; virtual;
function bcGetPosition : TffCursorPosition;
function bcGetRefNr : TffInt64;
procedure bcInit(const aOpenMode : TffOpenMode;
const aShareMode : TffShareMode;
const aExclContLock : Boolean); virtual; {!!.10}
{-Called from a cursor constructor. Performs misc. initializations. }
procedure bcInvalidateCurKey;
function bcIsCurKeyPathValid : boolean;
function bcIsCurKeyValid: boolean;
procedure bcRebuildKeyPath; {!!.05 - Moved from TffSrCursor.scRebuildKeyPath}
{ If the cursor has a valid key, this method rebuilds the cursor's key
path. }
procedure bcTableOpenPreconditions(aTable : TffSrBaseTable;
const aIndexName : string;
var aIndexID : Longint;
const aOpenMode : TffOpenMode); virtual; abstract;
{ Used by Create method to verify a thread may open a table. }
procedure bcTableOpenPrim(aDatabase : TffSrDatabase;
const aTableName : TffTableName;
const aOpenMode : TffOpenMode;
const aShareMode : TffShareMode;
const aForServer : boolean;
const aAttribs : TffFileAttributes); virtual;
{ Primitive engine method for opening a table. }
procedure bcRecordUpdated(aOp : TffRecOp;
aRefNr : TffInt64;
aIndexID : integer); virtual;
{ Called when another cursor has updated a record in the same
table. Gives this cursor a chance to update its internal
information (e.g., whether or not the current record has been
deleted, key path status). }
procedure bcRestoreCurInfo; virtual;
{ Restore the cursor's position, reference number, key, etc.
as saved via scSaveCurValues. }
procedure bcSaveCurInfo; virtual;
{ Save the cursor's current position, reference number, key, etc. }
function bcGetDictionary: TffDataDictionary; virtual;
public
constructor Create(anEngine : TffServerEngine;
aDatabase : TffSrDatabase;
const aTimeout : Longint); virtual;
destructor Destroy; override;
{Begin !!.10}
procedure AcqContentLock(const aMode : TffContentLockMode); virtual;
{ Acquire unconditional content lock. }
function AcqExclContentLock : TffResult; virtual;
{ Acquire conditional content lock (i.e., the lock is obtained only if
it can be immediately granted). }
{End !!.10}
{ Used by threads to obtain a content lock. }
function AddIndexToTable(const aIndexDesc : TffIndexDescriptor) : TffResult; virtual; abstract;
procedure AppendNewRecord(aData : PffByteArray); virtual;
{ BLOB methods }
function BLOBAdd(var aBLOBNr : TffInt64) : TffResult; virtual;
function BLOBLinkAdd(const aTableName : TffTableName;
const aTableBLOBNr : TffInt64;
var aBLOBNr : TffInt64) : TffResult; virtual;
{ Adds a link to a BLOB in another table to the cursor's table. }
procedure Build(const aTableName : TffTableName;
aDict : TffDataDictionary;
const aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
aForServer : boolean;
aOverWrite : boolean;
aAttribs : TffFileAttributes;
aStoreSize : TffWord32); virtual;
{ Use this method to open a cursor for a table that does not
yet exist. This method uses aDict to create the table. This method
then opens the cursor and positions to the Sequential Access Index
(i.e., index 0). }
function CanClose(const Mark : boolean) : boolean; override; {New !!.01}
{ A cursor can close if it is not active & is not involved in a
transaction. }
function FileBLOBAdd(const aFileName : TffFullFileName;
var aBLOBNr : TffInt64) : TffResult; virtual;
function BLOBDelete(const aBLOBNr : TffInt64) : TffResult; virtual;
function BLOBFree(aBLOBNr : TffInt64) : TffResult; virtual;
function BLOBGetLength(aBLOBNr : TffInt64;
var aFBError: TffResult) : Longint; virtual;
function BLOBIsLink(aBLOBNr : TffInt64; {!!.11 - New}
var aSrcTableName : TffTableName;
var aSrcTableBLOBNr : TffInt64)
: Boolean;
{Begin !!.03}
function BLOBListSegments(aBLOBNr : TffInt64;
aStream : TStream) : TffResult; virtual;
{End !!.03}
function BLOBRead(aBLOBNr : TffInt64;
aOffset : TffWord32; {!!.06}
aLen : TffWord32; {!!.06}
var aBLOB;
var aBytesRead : TffWord32) {!!.06}
: TffResult; virtual;
function BLOBTruncate(aBLOBNr : TffInt64;
aLen : TffWord32) : TffResult; virtual;
function BLOBWrite(const aBLOBNr : TffInt64;
aOffset : TffWord32;
aLen : TffWord32;
var aBLOB) : TffResult; virtual;
function CheckBookmark(aBookmark : PffByteArray) : TffResult; virtual; abstract;
procedure ClearIndex; virtual; abstract;
function CloneCursor(aOpenMode : TffOpenMode) : TffSrBaseCursor; virtual; abstract;
function CompareBookmarks(aBookmark1, aBookmark2 : PffByteArray;
var CmpResult : Longint) : TffResult; virtual; abstract;
function CopyRecords(aSrcCursor : TffSrBaseCursor; aBLOBCopyMode : TffBLOBCopyMode;
aCallback : TffSrCopyRecordsProc;
aCookie1, aCookie2 : Longint) : TffResult; virtual;
{ Use this method to copy all records from a source cursor to this
cursor. Copies only those records matching the range and/or filter
applied to the source cursor.
Requirement: The source and destination cursors must have compatible
dictionaries. The dictionaries must have the same field order, field
type, length, units, and decimal places.
If a record contains BLOBs, they are handled based upon the
aBLOBCopyMode parameter. If mode is ffbcmNoCopy then the BLOB fields
are set to NULL in the destination record. If mode is ffbcmCopyFull
then the BLOBs are copied wholesale to the destination cursor.
If mode is ffbcmCreateLink then the destination cursor is given a
link to the BLOB in the source cursor.
Use aCallback to have a validation routine called for each r4ecord
that is copied. The validation routine has the opportunity to
inspect the record and tell this routine whether or not to copy the
record. }
function CopyRecordParts(aSrcCursor : TffSrBaseCursor;
aFields : PffLongintArray;
aNumFields : integer;
aBLOBCopyMode : TffBLOBCopyMode;
aCallback : TffSrCopyRecordsProc;
aCookie1, aCookie2 : Longint) : TffResult; virtual;
{ Similar to the CopyRecords method except this method allows you to
copy specific fields from the source cursor. aFields identifies the
fields to be copied. Each element of aFields is a field number in
the source cursor's dictionary (base zero). The fields are copied in
the order specified.
The destination cursor's dictionary must have fields that match the
specified fields in the source dictionary except that they must be
in the order specified by aFields.
}
function DeleteRecord(aData : PffByteArray) : TffResult; virtual;
{Begin !!.06}
function DeleteRecords : TffResult; virtual;
{ Delete all records in the cursor's result set, taking into account
the active filter and/or range. }
{End !!.06}
function DropIndexFromTable(const aIndexName : TffDictItemName;
aIndexID : Longint) : TffResult; virtual; abstract;
function Empty : TffResult; virtual;
function EnsureWritable(aCheckCurRec, aConditionalLock : boolean) : TffResult; virtual;
{ Ensures the cursor is writable. If aCheckCurRec is true, this method
attempts to obtain an Exclusive, Commit duration lock on the
record. If aConditionalLock is also True then the method succeeds only
if it is able to immediately obtain the Exclusive lock. }
function ExtractKey(aData : PffByteArray; aKey : PffByteArray) : TffResult; virtual; abstract;
function GetBookmark(aBookmark : PffByteArray) : TffResult; virtual; abstract;
function GetBookmarkSize : integer; virtual; abstract;
function GetRecord(aData : PffByteArray; aLockType : TffSrLockType) : TffResult; virtual;
function GetRecordCount(var aRecCount : Longint) : TffResult; virtual; abstract;
function GetNextRecord(aData : PffByteArray; aLockType : TffSrLockType) : TffResult; virtual; abstract;
function GetPriorRecord(aData : PffByteArray; aLockType : TffSrLockType) : TffResult; virtual; abstract;
function GetRecordField(aField : integer;
aRecordBuffer : PffByteArray;
var isNull: boolean;
aFieldBuffer : pointer) : TffResult; virtual;
{ Obtain the value of a field. }
function GetRecordForKey(aDirectKey : boolean;
aFieldCount : integer;
aPartialLen : integer;
aKeyData : PffByteArray;
aData : PffByteArray;
aFirstCall : Boolean) : TffResult; virtual; abstract;
function InsertRecord(aData : PffByteArray; aLockType : TffSrLockType) : TffResult; virtual; abstract;
function InsertRecordNoDefault(aData : PffByteArray; aLockType : TffSrLockType) : TffResult; virtual; abstract;{!!.10}
function IsInRange(aKey : PffByteArray) : integer; virtual; abstract;
function IsRecordLocked(aLockType : TffSrLockType) : Boolean; virtual;
{Begin !!.03}
procedure ListBLOBFreeSpace(aTI : PffTransInfo;
const aInMemory : Boolean;
aStream : TStream);
{End !!.03}
function OverrideFilter(aExpression : pCANExpr;
aTimeout : TffWord32) : TffResult; virtual;
function ModifyRecord(aData : PffByteArray; aRelLock : Boolean)
: TffResult; virtual; abstract;
function NotifyExtenders(const anAction : TffEngineAction;
const aFailAction : TffEngineAction)
: TffResult;
{-Notifies all extenders associated with the cursor about the
specified action. If ignoreErrCode is True then error codes
returned by extenders are ignored. If failures occur it will
be taken care of before going back to the calling method.}
procedure Open(const aTableName : TffTableName;
const aIndexName : TffName;
const aIndexID : Longint;
const aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
aForServer : Boolean;
const aExclContLock : Boolean; {!!.10}
aAttribs : TffFileAttributes); virtual;
{ Use this method to open a cursor for a table that exists. }
procedure ReadAutoIncValue(var aValue: TffWord32); virtual;
procedure RelContentLock(aMode : TffContentLockMode); virtual;
procedure RelRecordLock(aAllLocks : Boolean); virtual;
procedure RelTableLock(aAllLocks : Boolean); virtual;
procedure RemoveIfUnused; virtual; {!!.05}
procedure ResetRange; virtual; abstract;
function RestoreFilter : TffResult; virtual;
procedure SetAutoIncValue(aValue: TffWord32); virtual;
function SetFilter(aExpression : pCANExpr;
aTimeout : TffWord32) : TffResult; virtual;
function SetRange(aDirectKey : Boolean;
aFieldCount1 : Integer;
aPartialLen1 : Integer;
aKeyData1 : PffByteArray;
aKeyIncl1 : Boolean;
aFieldCount2 : Integer;
aPartialLen2 : Integer;
aKeyData2 : PffByteArray;
aKeyIncl2 : Boolean) : TffResult; virtual; abstract;
procedure SetToBegin; virtual; abstract;
function SetToBookmark(aBookmark : PffByteArray) : TffResult; virtual; abstract;
function SetToCursor(aCursor : TffSrBaseCursor) : TffResult; virtual; abstract;
procedure SetToEnd; virtual; abstract;
function SetToKey(aSearchAction : TffSearchKeyAction;
aDirectKey : boolean;
aFieldCount : integer;
aPartialLen : integer;
aKeyData : PffByteArray) : TffResult; virtual; abstract;
function ShouldClose : boolean; override; {New !!.01}
{ A cursor can close if it is not involved in a transaction. }
function SortRecords(aFieldsArray : TffFieldList;
const aOrderByArray : TffOrderByArray;
const aNumFields : integer) : TffResult; virtual;
{ Use this method to physically sort the records within a table.
Parameters:
aFieldsArray - Array of field numbers on which the table is being
sorted. Field numbers correspond to the fields in
the table's dictionary. Each element in this array
must have a corresponding element in aOrderByArray.
aOrderByArray - Array of order by indicators, one for each field on
which the table is being sorted. Each element in
this array has a corresponding element in
aFieldsArray.
aNumFields - The number of fields on which the table is being
sorted.
}
function SwitchToIndex(aIndexID : integer;
aPosnOnRec : boolean) : TffResult; virtual; abstract;
{ Properties }
property Attribs : TffFileAttributes read bcGetAttribs;
{ Returns the file attributes attached to the table's data file. }
property CloseTable : boolean read bcCloseTable write bcCloseTable;
{ Set this property to True if the cursor is to close its table when
the cursor is freed. This is useful for SQL cursors which generate
temporary tables applicable to only one client. }
property CursorID : TffCursorID read bcGetCursorID;
property CursorInfo : TffSrCursorInfo read bcInfo write bcInfo;
property Database : TffSrDatabase read bcDatabase;
property Dictionary : TffDataDictionary read bcGetDictionary;
property Engine : TffServerEngine read bcEngine;
property ExclOwner : boolean read bcExclOwner write bcExclOwner;
property Extenders : TffList read bcExtenders; {!!.02}
property Filter: TffSrFilter read bcFilter;
property IndexID : Longint read bcIndexID;
property Position : TffCursorPosition read bcGetPosition;
property RefNr : TffInt64 read bcGetRefNr;
{ Returns the reference number of the current record. }
// property ServerEngine : TFFServerEngine read bcEngine; {Deleted !!.03}
property Table : TffSrBaseTable read bcTable;
{ Used exclusively by extenders, these might not reflect actual values }
property NewRecordBuffer : PffByteArray read bcNewRecBuff;
property OldRecordBuffer : PffByteArray read bcOldRecBuff;
property NeedNestedTransaction : Boolean {!!.03}
read bcNeedNestedTransaction {!!.03}
write bcNeedNestedTransaction; {!!.03}
end;
TffSrCursor = class(TffSrBaseCursor)
protected {private}
scKeyLen : integer; {key length for cursor's index}
protected
procedure bcInit(const aOpenMode : TffOpenMode;
const aShareMode : TffShareMode;
const aExclContLock : Boolean); override; {!!.10}
procedure bcTableOpenPreconditions(aTable : TffSrBaseTable;
const aIndexName : string;
var aIndexID : Longint;
const aOpenMode : TffOpenMode); override;
{ Used by Create method to verify a thread may open a table. }
procedure scRebuildCurKey(aRecData : PffByteArray;
aLockObtained : boolean);
{ Rebuilds the cursor's key from the specified record buffer. If
aRecData is nil then this method reads the record from the data file
& rebuilds the key from the retrieved record.
If you have already obtained a lock on the current record, set
aLockObtained := True. Doing so skips an unnecessary lock request. }
// procedure scRebuildKeyPath; {!!.05 - moved to TffSrBaseCursor.bcRebuildKeyPath}
// { If the cursor has a valid key, this method rebuilds the cursor's key
// path. }
public
constructor Create(anEngine : TffServerEngine;
aDatabase : TffSrDatabase;
const aTimeout : Longint); override;
destructor Destroy; override;
function AddIndexToTable(const aIndexDesc : TffIndexDescriptor) : TffResult; override;
function CheckBookmark(aBookmark : PffByteArray) : TffResult; override;
procedure ClearIndex; override;
function CloneCursor(aOpenMode : TffOpenMode) : TffSrBaseCursor; override;
function CompareBookmarks(aBookmark1, aBookmark2 : PffByteArray;
var CmpResult : Longint) : TffResult; override;
function DropIndexFromTable(const aIndexName : TffDictItemName;
aIndexID : Longint) : TffResult; override;
function ExtractKey(aData : PffByteArray; aKey : PffByteArray) : TffResult; override;
function GetBookmark(aBookmark : PffByteArray) : TffResult; override;
function GetBookmarkSize : integer; override;
function GetNextRecord(aData : PffByteArray; aLockType : TffSrLockType) : TffResult; override;
function GetPriorRecord(aData : PffByteArray; aLockType : TffSrLockType) : TffResult; override;
function GetRecordCount(var aRecCount : Longint) : TffResult; override;
function GetRecordForKey(aDirectKey : boolean;
aFieldCount : integer;
aPartialLen : integer;
aKeyData : PffByteArray;
aData : PffByteArray;
aFirstCall : Boolean) : TffResult; override;
function InsertRecord(aData : PffByteArray; aLockType : TffSrLockType) : TffResult; override;
function InsertRecordNoDefault(aData : PffByteArray; aLockType : TffSrLockType) : TffResult; override;{!!.10}
function IsInRange(aKey : PffByteArray) : integer; override;
function ModifyRecord(aData : PffByteArray; aRelLock : boolean) : TffResult; override;
procedure ResetRange; override;
function SetRange(aDirectKey : boolean;
aFieldCount1 : integer;
aPartialLen1 : integer;
aKeyData1 : PffByteArray;
aKeyIncl1 : boolean;
aFieldCount2 : integer;
aPartialLen2 : integer;
aKeyData2 : PffByteArray;
aKeyIncl2 : boolean) : TffResult; override;
procedure SetToBegin; override;
function SetToBookmark(aBookmark : PffByteArray) : TffResult; override;
function SetToCursor(aCursor : TffSrBaseCursor) : TffResult; override;
procedure SetToEnd; override;
function SetToKey(aSearchAction : TffSearchKeyAction;
aDirectKey : boolean;
aFieldCount : integer;
aPartialLen : integer;
aKeyData : PffByteArray) : TffResult; override;
function SwitchToIndex(aIndexID : integer;
aPosnOnRec : boolean) : TffResult; override;
end;
TffSrCursorList = class(TffServerObjectList)
protected {private}
protected
function GetCursorItem(Find : TffListFindType; Value : Longint) : TffSrBaseCursor;
public
procedure AddCursor(aCursor : TffSrBaseCursor);
function CursorCount : integer;
{ Returns the number of cursors in the list. }
procedure DeleteCursor(aCursorID : TffCursorID);
{ Removes a cursor from the list and frees the cursor. }
procedure RemoveCursor(aCursorID : TffCursorID);
{ Removes a cursor from the list but does not free the cursor. }
property Cursor [Find : TffListFindType; Value : Longint] : TffSrBaseCursor
read GetCursorItem; default;
end;
{ Describes the interface for the representation of a physical table. }
TffSrBaseTable = class(TffSelfListItem)
protected
btBaseName : PffShStr;
btBLOBEngine : TffBaseBLOBEngine; {!!.11}
btBufMgr : TffBufferManager;
btCursorList : TffSrCursorList;
btDictionary : TffServerDataDict;
btEngine : TffServerEngine;
btFiles : TffVCLList;
btFolder : TffSrFolder;
btForServer : Boolean;
btContentLocks : TffLockContainer;
btClientLocks : TffLockContainer;
btOpenIntents : Longint;
btPortal : TffReadWritePortal;
// btUseInternalRollback : boolean; {!!.03}{Deleted !!.11}
{Begin !!.03}
procedure btCommitBLOBMgr;
{ Commits the changes made by the BLOB resource manager to its
in-memory list. }
{End !!.03}
procedure btCreateFile(aFileInx : integer;
aTI : PffTransInfo;
const aExtension : TffExtension;
aForServer : boolean;
aAttribs : TffFileAttributes;
aStore : TffBaseTempStorage); virtual;
procedure btDeleteBLOBsForRecord(aTI : PffTransInfo;
aData : PffByteArray); virtual;
function btGetBaseName : TffTableName; virtual;
function btGetCursorList : TffSrCursorList; virtual;
function btGetDictionary : TffServerDataDict; virtual;
function btGetFile(Inx : integer) : PffFileInfo; virtual;
function btGetFileCount : integer; virtual;
function btGetFolder : TffSrFolder; virtual;
procedure btInformCursors(aSrcCursorID : TffCursorID;
aOp : TffRecOp;
aRefNr : TffInt64;
aIndexID : integer); virtual;
function btGetOpenIntents : Longint; virtual;
{Begin !!.03}
procedure btRollbackBLOBMgr;
{ Rolls back the changes made by the BLOB resource manager to its
in-memory list. }
{End !!.03}
procedure btSetFile(Inx : integer; FI : PffFileInfo); virtual;
procedure btSetFileCount(FC : integer); virtual;
procedure btTableUpdated(aDatabaseID : TffDatabaseID); virtual;
procedure btUpdateAutoInc(aTI : PffTransInfo; aData : PffByteArray); virtual;
public
constructor Create(anEngine : TffServerEngine;
const aBaseName : TffTableName;
aFolder : TffSrFolder;
aBufMgr : TffBufferManager;
const aOpenMode : TffOpenMode); virtual;
destructor Destroy; override;
procedure AcqClientLock(aCursorID : Longint;
const aLockType : TffSrLockType;
const aConditional : Boolean); virtual;
procedure AcqContentLock(aTrans : TffSrTransaction;
const aLockType : TffSrLockType;
const aConditional : boolean); virtual;
{Begin !!.10}
function AcqExclContentLock(aTrans : TffSrTransaction) : TffResult; virtual;
{End !!.10}
procedure AcqLock(const aCursorID : TffCursorID;
const aLockType : TffSrLockType); virtual;
{Begin !!.03}
procedure AddAttribute(const anAttrib : TffFileAttribute);
{ Add an attribute to the table's FF-specific file attributes. }
{End !!.03}
procedure AddIndex(const aIndexDesc : TffIndexDescriptor;
aTI : PffTransInfo); virtual; abstract;
procedure BeginCommit; virtual;
{ Before a transaction commits, a thread must call this method.
This ensures that all readers have finished with the table before
the table is updated. When done committing, the thread must call
TffSrTable.EndCommit. }
procedure BeginRead; virtual;
{ Threads that are not in a transaction & needing to read data from
the table must call this method prior to reading. When done
reading the thread must call TffSrTable.EndRead. }
procedure BuildFiles(aTI : PffTransInfo;
aForServer : boolean;
aDictionary : TffDataDictionary;
aAttribs : TffFileAttributes;
aStore : TffBaseTempStorage); virtual; abstract;
function BuildKeyForRecord(aIndexID : integer;
aData : PffByteArray;
aKey : PffByteArray;
aFieldCount : integer;
aPartialLen : integer) : TffResult; virtual; abstract;
procedure CloseFiles(commitChanges : boolean; aTI : PffTransInfo); virtual;
procedure CommitChanges(aTI : PffTransInfo); virtual;
function CompareKeysForCursor(var aKID : TffKeyIndexData;
aKey1 : PffByteArray;
aKey2 : PffByteArray)
: Integer; virtual; abstract;
function DeleteRecord(aTI : PffTransInfo;
const aCursorID : TffCursorID;
const aRefNr : TffInt64;
const aLockObtained : Boolean;
var aBTreeChanged : Boolean) {!!.05}
: TffResult; virtual; abstract;
procedure DeregisterOpenIntent; virtual;
{ Use this function to deregister intent to open. Should only be
called if RegisterIntentOpen was previously called. }
procedure DropIndex(aTI : PffTransInfo; aIndexID : Longint); virtual; abstract;
function EmptyFiles(aTI : PffTransInfo) : TffResult; virtual;
procedure EndCommit(aDatabaseID : TffDatabaseID); virtual;
{ Call this method after calling BeginCommit and finishing the commit
operation. }
procedure EndRead; virtual;
{ Call this method after calling BeginRead and finishing the read
operation. }
function FindKey(var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aTI : PffTransInfo;
aKey : PffByteArray;
var aKeyPath : TffKeyPath;
aAction : TffSearchKeyAction) : boolean; virtual; abstract;
function GetNextKey(var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aTI : PffTransInfo;
aKey : PffByteArray;
var aKeyPath : TffKeyPath) : TffResult; virtual; abstract;
function GetNextRecord(aTI : PffTransInfo;
const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID; {!!.10}
var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aKey : PffByteArray;
var aKeyPath : TffKeyPath;
aData : PffByteArray;
const aLockType : TffSrLockType) : TffResult; virtual; abstract;
procedure GetNextRecordSeq(aTI : PffTransInfo;
var aRefNr : TffInt64;
aData : PffByteArray); virtual;
procedure GetPrevRecordSeq(aTI : PffTransInfo;
var aRefNr : TffInt64;
aData : PffByteArray); virtual;
function GetPriorRecord(aTI : PffTransInfo;
const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID; {!!.10}
var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aKey : PffByteArray;
var aKeyPath : TffKeyPath;
aData : PffByteArray;
const aLockType : TffSrLockType) : TffResult; virtual; abstract; {!!.10}
function GetRecord(aTI : PffTransInfo;
const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID; {!!.10}
aRefNr : TffInt64;
aData : PffByteArray;
const aLockType : TffSrLockType; {!!.10}
const aLockObtained : boolean; {!!.10}
const aConditional : boolean) : TffResult; virtual; {!!.10}
{ Use this method to retrieve a record from the data file.
If a lock has already been obtained via TffSrTable.GetRecordLock
then set aLockObtained := True. Doing so skips an unnecessary
lock request. }
procedure GetRecordLock(aTI : PffTransInfo;
const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID; {!!.10}
const aRefNr : TffInt64; {!!.10}
const aLockType : TffSrLockType); virtual; {!!.10}
{Begin !!.10}
procedure GetRecordNoLock(aTI : PffTransInfo;
aRefNr : TffInt64;
aData : PffByteArray);
{ Retrieve a record without obtaining any type of lock. }
{End !!.10}
function HasClientLock(const aCursorID : TffCursorID) : boolean; virtual;
{ Returns True if the specified cursor has a client lock (i.e.,
TffTable.LockTable). }
function HasLock(const aCursorID : TffCursorID;
const aLockType : TffSrLockType) : boolean; virtual;
{ Returns True if the specified cursor has an open lock of the specified
type on the table. }
{Begin !!.06}
function HasRecordLocks : Boolean;
{ Returns True if there are any record locks on the table. }
{End !!.06}
function InsertRecord(aTI : PffTransInfo;
aCursorID : TffCursorID;
aData : PffByteArray;
aLockType : TffSrLockType;
var aNewRefNr : TffInt64) : TffResult; virtual; abstract;
function InsertRecordNoDefault(aTI : PffTransInfo; {!!.10}
aCursorID : TffCursorID;
aData : PffByteArray;
aLockType : TffSrLockType;
var aNewRefNr : TffInt64) : TffResult; virtual; abstract;
function IsContentLockedBy(aTrans : TffSrTransaction) : boolean; virtual;
{ Returns True if the table's contents are locked by the specified
transaction. This returns True whether the lock is a read lock or
a write lock. }
function IsRecordLocked(aTI : PffTransInfo;
aCursorID : TffCursorID;
aRefNr : TffInt64;
aLockType : TffSrLockType) : Boolean; virtual;
function IsServerTable : boolean; virtual;
{ Returns True if this table is a server table. }
procedure MakeKIDForCursor(aIndexID : integer; var aKID : TffKeyIndexData); virtual; abstract;
procedure OpenFiles(aTI : PffTransInfo; aForServer : boolean;
aAttribs : TffFileAttributes); virtual;
function PutRecord(aTI : PffTransInfo;
aCursorID : TffCursorID;
aRefNr : TffInt64;
aData : PffByteArray;
aRelLock : boolean; {!!.05}
var aKeyChanged : Boolean) : TffResult; virtual; abstract; {!!.05}
procedure RegisterOpenIntent; virtual;
{ Use this method to register intent to open a table. }
{Begin !!.10}
procedure RelaxRecordLock(aTI : PffTransInfo;
aCursorID : TffCursorID;
aRefNr : TffInt64); virtual;
{End !!.10}
procedure RelClientLock(aCursorID : Longint; aRemoveAll : Boolean); virtual;
procedure RelContentLock(aTrans : TffSrTransaction); virtual;
procedure RelLock(const aCursorID : TffCursorID;
const aAllLocks : boolean); virtual;
procedure RelRecordLock(aTI : PffTransInfo;
aDatabaseID : TffDatabaseID; {!!.10}
aCursorID : TffCursorID;
aRefNr : TffInt64); virtual;
procedure RemoveLocksForCursor(const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID;
const aRefNr : TffInt64;
aTI : PffTransInfo); virtual;
{Begin !!.03}
procedure ListBLOBFreeSpace(aTI : PffTransInfo;
const aInMemory : Boolean;
aStream : TStream);
{End !!.03}
procedure SetAttributes(const fileAttribs : TffFileAttributes); virtual;
{ Sets the file attributes on all files of a table instance. This
should only be called when the table is first opened. }
procedure SetExclOwner(const aCursorID : TffCursorID); virtual;
{ Marks each file managed by a table as exclusively owned by the
specified cursor. Only call this method when the table has been
exclusively opened by the cursor. }
property BaseName : TffTableName read btGetBaseName;
property ClientLocks : TffLockContainer read btClientLocks; {!!.11}
property CursorList : TffSrCursorList read btGetCursorList;
property Dictionary : TffServerDataDict read btGetDictionary;
property FileCount : Integer read btGetFileCount write btSetFileCount;
property Files [Inx : integer] : PffFileInfo read btGetFile write btSetFile;
property Folder : TffSrFolder read btGetFolder;
property OpenIntents : Longint read btGetOpenIntents;
{ The number of threads that have registered their intent to open this
table. }
property TableID : Longint read KeyAsInt;
{Begin !!.03}
// property UseInternalRollback : boolean {Deleted !!.11}
// read btUseInternalRollback {Deleted !!.11}
// write btUseInternalRollback; {Deleted !!.11}
{ This property is set to True when the server is attempting to
insert or modify a record. When set to True and the operation fails,
the server undoes any modifications made up to the point of failure.
For example, a record is inserted into a table having four indexes.
The record is stored in the data file and keys are added to two of
the indexes. However, a key violation occurs when adding a key to
the third index. The server removes the keys from the first two
indexes and removes the record from the data file. }
{End !!.03}
end;
{ Represents a table opened by one or more cursors. Only one
instance of this class is created and the instance is freed when all
cursors have closed the table.
Table locks are acquired using the parent folder's lock manager. This
means that each client opening a table obtains some kind of lock on the
table. The following types of locks are used:
ffsltExclusive - Used to obtain exclusive read-write access to a table.
ffsltShare - Used to obtain read-only access to a table.
ffsltIntentS - Used to obtain read-write access to a table.
Since client A may open a table in read-only mode while clients B, C, & D
may open a table in non-exclusive read-write mode, we use the ffsltShare
& ffsltIntentS locks to represent non-exclusive read-write and read-only
modes. ffsltShare and ffsltIntentS are compatible locks so any number
of clients may concurrently access the table.
If a client wants to open the table exclusively, their request for a
ffsltExclusive lock will wait until all non-exclusive read-write and
read-only clients have released their locks.
Conversely, a client wanting to open the table in read-only or
non-exclusive read-write mode must wait until a client granted Exclusive
access to the table has released its lock.
Notes on LockTable and UnlockTable:
Just as in the BDE, a client may lock a table for reading or writing.
Pertinent rules:
1. Table locking is as described in the previous paragraphs.
2. If a table is read-locked then no client may edit a record.
3. If a table is write-locked then only the client obtaining the lock
may edit records.
}
TffSrTable = class(TffSrBaseTable)
protected
// stUseInternalRollback : boolean; {!!.03}
stUserBuildKey : TffVCLList;
stUserCompareKey : TffVCLList;
function stGetBuiltCompositeKey(aIndexID : integer;
aData : PffByteArray;
aKeyLen : Longint;
var aKey : PffByteArray) : TffResult;
function stBuildCompositeKey(aIndexID : integer;
aData : PffByteArray;
aKey : PffByteArray;
aFieldCount : integer;
aLastFldLen : integer) : TffResult;
function stDeleteKeyPrim(aInxFile : Integer;
aTI : PffTransInfo;
aRefNr : TffInt64;
aKey : PffByteArray;
aCompare : TffKeyCompareFunc;
aCmpData : PffCompareData;
var aBTreeChanged : Boolean) : Boolean; {!!.05}
function stDeleteKeysForRecord(aTI : PffTransInfo;
aRefNr : TffInt64;
aData : PffByteArray;
var aBTreeChanged : Boolean) {!!.05}
: TffResult;
function stGetUserBuildKey(aIndexID : Integer) : TffKeyBuildFunc;
function stGetUserCompareKey(aIndexID : Integer) : TffKeyCompareFunc;
function stInsertKeyPrim(aInxFile: integer;
aTI : PffTransInfo;
aRefNr : TffInt64;
aKey : PffByteArray;
aCompare: TffKeyCompareFunc;
aCmpData: PffCompareData) : boolean;
function stInsertKeysForRecord(aTI : PffTransInfo;
aRefNr : TffInt64;
aData : PffByteArray) : TffResult;
function stUpdateKeysForRecord(aCursorID : TffCursorID;
aTI : PffTransInfo;
aRefNr : TffInt64;
aData,
aOldData : PffByteArray; {!!.05}
var aKeyChanged : Boolean) : TffResult; {!!.05}
public
constructor Create(anEngine : TffServerEngine;
const aBaseName : TffTableName;
aFolder : TffSrFolder;
aBufMgr : TffBufferManager;
const aOpenMode : TffOpenMode); override;
destructor Destroy; override;
procedure AddIndex(const aIndexDesc : TffIndexDescriptor;
aTI : PffTransInfo); override;
procedure BuildFiles(aTI : PffTransInfo;
aForServer : boolean;
aDictionary : TffDataDictionary;
aAttribs : TffFileAttributes;
aStore : TffBaseTempStorage); override;
function BuildKeyForRecord(aIndexID : integer;
aData : PffByteArray;
aKey : PffByteArray;
aFieldCount : integer;
aPartialLen : integer) : TffResult; override;
function CompareKeysForCursor(var aKID : TffKeyIndexData;
aKey1 : PffByteArray;
aKey2 : PffByteArray) : integer; override;
function DeleteRecord(aTI : PffTransInfo;
const aCursorID : TffCursorID;
const aRefNr : TffInt64;
const aLockObtained : Boolean;
var aBTreeChanged : Boolean) {!!.05}
: TffResult; override;
procedure DropIndex(aTI : PffTransInfo; aIndexID : Longint); override;
function FindKey(var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aTI : PffTransInfo;
aKey : PffByteArray;
var aKeyPath : TffKeyPath;
aAction : TffSearchKeyAction) : boolean; override;
function GetNextKey(var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aTI : PffTransInfo;
aKey : PffByteArray;
var aKeyPath : TffKeyPath) : TffResult; override;
function GetNextRecord(aTI : PffTransInfo;
const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID; {!!.10}
var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aKey : PffByteArray;
var aKeyPath : TffKeyPath;
aData : PffByteArray;
const aLockType : TffSrLockType) : TffResult; override; {!!.10}
function GetPriorRecord(aTI : PffTransInfo;
const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID; {!!.10}
var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aKey : PffByteArray;
var aKeyPath : TffKeyPath;
aData : PffByteArray;
const aLockType : TffSrLockType) : TffResult; override; {!!.10}
function InsertRecord(aTI : PffTransInfo;
aCursorID : TffCursorID;
aData : PffByteArray;
aLockType : TffSrLockType;
var aNewRefNr : TffInt64) : TffResult; override;
function InsertRecordNoDefault(aTI : PffTransInfo; {!!.10}
aCursorID : TffCursorID;
aData : PffByteArray;
aLockType : TffSrLockType;
var aNewRefNr : TffInt64) : TffResult; override;
procedure MakeKIDForCursor(aIndexID : integer; var aKID : TffKeyIndexData); override;
function PutRecord(aTI : PffTransInfo;
aCursorID : TffCursorID;
aRefNr : TffInt64;
aData : PffByteArray;
aRelLock : boolean; {!!.05}
var aKeyChanged : Boolean) : TffResult; override; {!!.05}
procedure RemoveDynamicLinks;
procedure ResolveDynamicLinks;
property BaseName : TffTableName read btGetBaseName;
property CursorList : TffSrCursorList read btGetCursorList;
property Dictionary : TffServerDataDict read btGetDictionary;
property FileCount : integer read btGetFileCount write btSetFileCount;
property Files [Inx : integer] : PffFileInfo read btGetFile write btSetFile;
property Folder : TffSrFolder read btGetFolder;
property OpenIntents : Longint read btOpenIntents;
{ The number of threads that have registered their intent to open this
table. }
property TableID : Longint read KeyAsInt;
// property UseInternalRollback : boolean read stUseInternalRollback write stUseInternalRollback; {!!.03}
end;
{ The following class may be used to access system tables (e.g., FFSALIAS,
FFSUSER, etc.). }
TffSrSystemTable = class(TffSrTable)
public
function IsServerTable : boolean; override;
end;
TffSrTableList = class(TffObject)
protected {private}
tlList : TffThreadList;
FOwner : TffServerEngine; {!!.06}
protected
function GetTableItem(Find : TffListFindType; Value : Longint) : TffSrBaseTable;
public
constructor Create;
destructor Destroy; override;
procedure AddTable(aTable : TffSrBaseTable);
function BeginRead : TffSrTableList;
{-A thread must call this method to gain read access to the list.
Returns the instance of this object as a convenience. }
function BeginWrite : TffSrTableList;
{-A thread must call this method to gain write access to the list.
Returns the instance of this object as a convenience.}
procedure DeleteTable(aTableID : Longint);
procedure EndRead;
{-A thread must call this method when it no longer needs read access
to the list. If it does not call this method, all writers will
be perpetually blocked. }
procedure EndWrite;
{-A thread must call this method when it no longer needs write access
to the list. If it does not call this method, all readers and writers
will be perpetualy blocked. }
function GetTableFromName(const aTableName : TffTableName) : TffSrBaseTable;
procedure RemoveIfUnused(aTable : TffSrBaseTable);
procedure RemoveUnusedTables;
function TableCount : integer;
property Owner : TffServerEngine {!!.06}
read FOwner write FOwner; {!!.06}
property Table[Find : TffListFindType; Value : Longint] : TffSrBaseTable
read GetTableItem; default;
end;
{ An instance of this class mirrors an instance of TffDatabase in the client
application. If multiple clients open the same database, there will be
one instance of TffSrDatabase per client.
A TffSrDatabase may have one active transaction however there may be
multiple concurrent transactions on a physical database. }
TffSrDatabase = class(TffServerObject)
protected {private}
dbAlias : PffShStr;
dbCheckSpace : Boolean; {!!.11}
dbCursorList : TffSrCursorList;
dbEngine : TffServerEngine;
dbExtenders : TffThreadList;
dbFolder : TffSrFolder;
dbOpenMode : TffOpenMode;
dbSession : TffSrSession;
dbShareMode : TffShareMode;
dbStmtList : TffSrStmtList; {!!.10}
dbTI : PffTransInfo;
{-Transaction-specific information used for locking. }
dbTrans : TffSrTransaction;
{-The active transaction for this database. }
protected
procedure dbAddExtender(anExtender : TffBaseEngineExtender);
function dbGetAlias : TffName;
function dbGetDatabaseID : TffDatabaseID;
function dbGetTransID : TffTransID;
{-Returns the ID of the transaction associated with the cursor. }
function dbGetTransLSN : TffWord32;
{-Returns the LSN of the cursor's transaction. }
{Begin !!.11}
procedure dbSetExistingTableVersion(const Version : Longint);
{ *** WARNING: This procedure is provided for testing & utility
purposes only. Do not use it unless you really know what you're
doing. That means you! ***}
procedure dbSetNewTableVersion(const Version : Longint);
{ *** WARNING: This procedure is provided for testing & utility
purposes only. Do not use it unless you really know what you're
doing. That means you! ***}
procedure dbSetPackSrcTableVersion(const Version : Longint);
{ *** WARNING: This procedure is provided for testing & utility
purposes only. Do not use it unless you really know what you're
doing. That means you! ***}
{End !!.11}
procedure dbSetTrans(aTransaction : TffSrTransaction); virtual;
public
constructor Create(anEngine : TffServerEngine;
aSession : TffSrSession;
aFolder : TffSrFolder;
anAlias : TffName;
aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
aTimeout : Longint;
aCheckSpace : Boolean); {!!.11}
destructor Destroy; override;
function CanClose(const Mark : boolean) : boolean; override;
procedure ForceClose; override;
function NotifyExtenders(const anAction : TffEngineAction;
const aFailAction : TffEngineAction) : TffResult;
{-Notifies all extenders associated with the cursor about the
specified action. If ignoreErrCode is True then error codes
returned by extenders are ignored. If failures occur it will
be taken care of before going back to the calling method.}
procedure RequestClose; override; {!!.03}
function ShouldClose : boolean; override;
property Alias : TffName read dbGetAlias;
{-The alias for which this database was opened. }
property CheckSpace : Boolean {!!.11}
read dbCheckSpace; {!!.11}
property CursorList : TffSrCursorList read dbCursorList;
property DatabaseID : TffDatabaseID read dbGetDatabaseID;
property Engine : TffServerEngine read dbEngine;
property Folder : TffSrFolder read dbFolder;
property OpenMode : TffOpenMode read dbOpenMode;
property Session : TffSrSession read dbSession;
property ShareMode : TffShareMode read dbShareMode;
property StmtList : TffSrStmtList read dbStmtList; {!!.10}
property Transaction : TffSrTransaction read dbTrans write dbSetTrans;
{ The transaction associated with the cursor. }
property TransactionID : TffTransID read dbGetTransID;
{ The transaction active for this cursor. If no transaction is
active then returns zero. }
property TransactionInfo : PffTransInfo read dbTI;
{ Returns a pointer to the cursor's transaction information. }
property TransactionLSN : TffWord32 read dbGetTransLSN;
{ Returns the LSN of the transaction associated with the cursor. }
// property ServerEngine : TffServerEngine read dbEngine; {Deleted !!.03}
end;
TffSrDatabaseList = class(TffServerObjectList)
protected {private}
protected
function GetDatabaseItem(Find : TffListFindType; Value : Longint) : TffSrDatabase;
public
procedure AddDatabase(aDatabase : TffSrDatabase);
function DatabaseCount : integer;
procedure DeleteDatabase(aDatabaseID : Longint);
function GetDatabaseForFolder(aFolder : TffSrFolder) : TffSrDatabase;
property Database [Find : TffListFindType; Value : Longint] : TffSrDatabase read GetDatabaseItem; default;
end;
TffSrSession = class(TffServerObject)
protected {private}
ssDatabaseList : TffSrDatabaseList;
ssIsDefault : boolean;
protected
function ssGetSessionID : TffSessionID;
public
constructor Create(aClient : TffSrClient; const aIsDef : boolean;
const aTimeout : Longint);
destructor Destroy; override;
function CanClose(const Mark : boolean) : boolean; override;
procedure ForceClose; override;
procedure RequestClose; override; {!!.03}
function ShouldClose : boolean; override;
property DatabaseList : TffSrDatabaseList read ssDatabaseList;
property IsDefault : boolean read ssIsDefault;
property SessionID : TffSessionID read ssGetSessionID;
end;
TffSrSessionList = class(TffServerObjectList)
protected {private}
slDefSess : TffSrSession;
slCurSess : TffSrSession;
protected
function slGetCurSess : TffSrSession;
function slGetSessionItem(Find : TffListFindType; Value : Longint) : TffSrSession;
procedure slSetCurSess(CS : TffSrSession);
public
procedure AddSession(aSession : TffSrSession);
procedure DeleteSession(aSessionID : Longint);
function SessionCount : integer;
procedure SetDefaultSession(aSession : TffSrSession);
property CurrentSession : TffSrSession read slGetCurSess write slSetCurSess;
property Session [Find : TffListFindType; Value : Longint] : TffSrSession read slGetSessionItem;
end;
{Begin !!.10}
TffBasePreparedStmt = class(TffServerObject)
protected
bpsClientID : TffClientID;
bpsDatabaseID: TffDatabaseID;
bpsEngine : TffServerEngine;
public
procedure Bind; virtual; abstract; {!!.11}
function Execute(var aLiveResult: Boolean;
var aCursorID: TffCursorID;
var aRowsAffected: Integer;
var aRecordsRead: Integer): TffResult; virtual; abstract;
function Parse(aQuery: PChar): Boolean; virtual; abstract;
property ClientID : TffClientID
read bpsClientID;
{ ID of owning client. }
property DatabaseID : TffDatabaseID
read bpsDatabaseID;
{ ID of owning database. }
property Engine : TffServerEngine
read bpsEngine;
property Handle: LongInt
read KeyAsInt;
{ Statement handle. }
end;
TffSrStmtList = class(TffServerObjectList)
protected
function GetStmt(Find : TffListFindType; Value : Longint) : TffBasePreparedStmt;
public
procedure AddStmt(aStmt : TffBasePreparedStmt);
procedure DeleteStmt(aStmtID : TffSQLStmtID);
procedure RemoveForClient(const aClientID : TffClientID);
{-Removes all prepared statements associated with a particular client. }
function StmtCount : integer;
property Stmt [Find : TffListFindType; Value : Longint] : TffBasePreparedStmt
read GetStmt; default;
end;
{End !!.10}
TffSrClient = class(TffServerObject)
protected {private}
clAccepted : boolean;
clClientName : PffShStr;
clEngine : TffServerEngine;
clExtenders : TffThreadList;
clSessionList : TffSrSessionList;
clUserID : TffName;
clFirst : TffName;
clLast : TffName;
clRights : TffUserRights;
clFirstSession: TffSrSession; {!!.03}
clClientVersion : Longint; {!!.11}
protected
function clGetClientID : TffClientID;
function clGetClientName : TffNetName;
public
constructor Create(aClientID : Longint;
const aClientName : TffNetName;
const aTimeout : Longint;
const aClientVersion : Longint; {!!.11}
aUser : TffUserItem;
anEngine : TffServerEngine);
destructor Destroy; override;
procedure AddClientExtender(anExtender : TffBaseEngineExtender);
{-Use this method to add an extender to the list of extenders
interested in clients. }
function CanClose(const Mark : boolean) : boolean; override;
procedure ForceClose; override;
function NotifyExtenders(const anAction : TffEngineAction;
const aFailAction : TffEngineAction) : TffResult;
{-Use this method to notify client extenders about a client-related
action. }
procedure RequestClose; override; {!!.03}
function ShouldClose : boolean; override;
property Accepted : boolean read clAccepted write clAccepted;
{ Returns True if the client was accepted by the client extender(s). }
property ClientID : TffClientID read clGetClientID;
property ClientVersion : Longint read clClientVersion; {!!.11}
property ClientName : TffNetName read clGetClientName;
property Rights : TffUserRights read clRights;
property SessionList : TffSrSessionList read clSessionList;
end;
TffSrClientList = class(TffServerObjectList)
protected {private}
protected
function GetClientItem(Find : TffListFindType; Value : Longint) : TffSrClient;
procedure SetClientItem(Inx : integer; CI : TffSrClient);
public
procedure AddClient(aClient : TffSrClient);
function ClientCount : integer;
procedure DeleteClient(aClientID : Longint);
property Client [Find : TffListFindType; Value : Longint] : TffSrClient read GetClientItem;
end;
PffSrRebuildParams = ^TffSrRebuildParams;
TffSrRebuildParams = record
rpDB : TffSrDatabase;
rpTableName : TffTableName;
rpIndexName : TffName;
rpIndexID : Longint;
rpRebuildStatus : TffSrRebuildStatus;
rpCursor : TffSrCursor;
rpTargetCursor : TffSrCursor;
rpFieldMap : TffSrFieldMapList;
end;
TffServerEngine = class(TffIntermediateServerEngine)
private
protected {public}
seCursorClass : TffSrCursorClass; {!!.06}
seBufMgr : TffBufferManager;
seCanLog : Boolean; { If True then can write to event log. }
seClientHash : TffHash; {!!.02}
seConfig : TffServerConfiguration;
seConfigLoaded : Boolean; { True if config tables have been loaded. }
seGarbageThread : TffTimerThread;
seLastFlush : DWORD; {!!.01}
seRebuildList : TffSrRebuildStatusList;
seStartTime : DWORD; {!!.10}
seUniqueID : TGUID; {!!.10}
seClientList : TffSrClientList;
seConfigDir : TffPath;
{ The location of the server tables for this server engine.
IMPORTANT NOTE: When retrieving this value, use the ConfigDir property
or the seGetConfigDir method directly as this method determines the
correct config dir for the server if the config dir has not been
specified (i.e., is set to ''). }
seCursorList : TffSrCursorList;
seDatabaseList : TffSrDatabaseList;
seFolderList : TffSrFolderList;
seOnRecoveryCheck : TNotifyEvent;
{ Handler called when it is time to check for recovery. }
seScriptFile : TffFullFileName;
seSessionList : TffSrSessionList;
seSQLEngine : TffBaseSQLEngine;
seTableList : TffSrTableList;
seEvtClientDone : TffEvent;
{This event is used to notify a server when a client is done
processing during server shutdown. This event is nill except
when shutting down.}
function seTransactionStart(const aDB : TffSrDatabase;
const aFailSafe, aImplicit : boolean;
var aTransactionID : TffTransID) : TffResult;
{-starts a transaction based on aImplicit setting}
function seTransactionCommitSubset(const aDB : TffSrDatabase) : TffResult;
{Begin !!.11}
function seClientAddPrim(var aClientID : TffClientID;
const aClientName : TffNetName;
const aUserID : TffName;
const aTimeout : Longint;
const aClientVersion : Longint;
var aHash : TffWord32) : TffResult;
{End !!.11}
procedure seClientRemovePrim(const aClient : TffSrClient);
function seConvertSingleField(aSourceBuf,
aTargetBuf: PffByteArray;
aSourceCursorID,
aTargetCursorID: Longint;
aSourceFldNr,
aTargetFldNr: Integer;
aBLOBBuffer: Pointer;
aBLOBBufLen: Longint): TffResult;
function seDatabaseAliasListPrim(aList : TList) : TffResult;
function seDatabaseDeleteAliasPrim(aAlias : TffName) : TffResult;
function seDatabaseGetAliasPathPrim(aAlias : TffName;
var aPath : TffPath) : TffResult;
function seDeleteTable(const aDB : TffSrDatabase;
const aTableName : TffTableName) : TffResult;
function seGetConfig : TffServerConfiguration;
function seGetDictionary(const aDB : TffSrDatabase;
const aTableName : TffTableName;
var aDict : TffDataDictionary) : TffResult;
function seIsServerTable(const aTableName : TffTableName) : Boolean;
function seGetCollectFrequency : Longint;
function seGetCollectGarbage : Boolean;
function seGetConfigDir : string; {!!.10}
function seGetMaxRAM : Longint; {!!.01}
function seGetScriptFile : string; {!!.11}
procedure seSetCollectFrequency(aFreq : Longint);
procedure seSetCollectGarbage(aValue : Boolean);
procedure seSetConfigDir(const aPath : string); {!!.10}
procedure seSetMaxRAM(const aValue: Longint); {!!.01}
procedure seSetScriptFile(const aFile : string); {!!.11}
function seTableBuildPrim(aDB : TffSrDatabase;
aOverwrite : Boolean;
const aTableName : TffTableName;
aForServer : Boolean;
aDict : TffDataDictionary) : TffResult;
function seTableDeletePrim(DB : TffSrDatabase;
const aTableName : TffTableName) : TffResult;
function seTableExistsPrim(aDB : TffSrDatabase; {!!.11}
const aTableName: TffTableName) : Boolean; {!!.11}
function seTablePackPrim(aRebuildParamsPtr: PffSrRebuildParams): TffResult;
function seTableRebuildIndexPrim(aRebuildParamsPtr: PffSrRebuildParams): TffResult;
function seTableGetRecordCountPrim(aRebuildParamsPtr : PffSrRebuildParams) : TffResult; { !!.10}
function seTransactionCommit(aDB : TffSrDatabase) : TffResult;
function seTransactionRollback(aDB : TffSrDatabase) : TffResult;
protected
{validation and checking}
{ The Check*IDAndGet routines are responsible for checking the
engine state to make sure it is ffesStarted. The
seCheck*IDAndGet avoid checking the engine state.
WARNING: Ensure changes are made to Check*IDAndGet and
seCheck*IDAndGet }
// function CheckClientIDAndGet(aClientID : TffClientID; {!!.01 - Start}
// var aClient : TffSrClient) {Moved to Public section}
// : TffResult; {!!.01 - End}
function seCheckClientIDAndGet(aClientID : TffClientID;
var aClient : TffSrClient) : TffResult;
// function CheckSessionIDAndGet(aClientID : TffClientID; {!!.01 - Start}
// aSessionID : TffSessionID; {Moved to Public section}
// var aClient : TffSrClient; {!!.01 - End}
// var aSession : TffSrSession) : TffResult;
function seCheckSessionIDAndGet(aSessionID : TffSessionID;
var aSession : TffSrSession) : TffResult;
// function CheckTransactionIDAndGet(aTransactionID : TffTransID; {!!.01 - Start}
// var aTrans : TffSrTransaction) {Moved to Public section}
// : TffResult; {!!.01 - End}
function seCheckCursorIDAndGet(aCursorID : TffCursorID;
var aCursor : TffSrBaseCursor) : TffResult;
{-Find the cursor specified by aCursorID. }
function seCheckDatabaseIDAndGet(aDatabaseID : TffDatabaseID;
var aDatabase : TffSrDatabase) : TffResult;
function GetTableInstance(aFolder : TffSrFolder;
const aTableName : TffTableName) : TffSrBaseTable;
function IsTableNameOpen(aFolder : TffSrFolder;
const aTableName : TffTableName) : boolean;
{rebuild status related stuff}
function RebuildRegister(aClientID : TffClientID;
aTotalRecords : Longint) : TffSrRebuildStatus;
procedure RebuildDeregister(aRebuildID : Longint);
function seBLOBCopy(aSrc, aTgt : TffSrBaseCursor;
aSourceBLOBNr, aTargetBLOBNr : TffInt64;
aBuffer : pointer;
aBufLen : Longint): TffResult;
function seDatabaseAddAliasPrim(const aAlias : TffName;
const aPath : TffPath;
aCheckSpace : Boolean) {!!.11}
: TffResult;
function seDatabaseOpenPrim(Session : TffSrSession;
Folder : TffSrFolder;
anAlias : TffName;
aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
aTimeout : Longint;
aCheckSpace : Boolean) {!!.11}
: TffSrDatabase;
{-Used by the public DatabaseOpenxx methods and used to open system
tables. }
function seTableRenamePrim(DB : TffSrDatabase;
const aOldName, aNewName : TffName) : TffResult;
function RecordGetNextSeq(aCursorID : TffCursorID; var aRefNr : TffInt64; aData : PffByteArray) : TffResult;
{index stuff}
function IndexClear(aCursorID : TffCursorID) : TffResult;
{misc stuff}
procedure CreateAdminUser(SaveToDisk : Boolean);
{-create the default administrator user}
procedure ReadAliasData;
{-read the aliases from the FFSALIAS.FFD table}
procedure ReadGeneralInfo;
{-read the general info from the FFSINFO.FFD table}
procedure ReadKeyProcData;
{-read the user-defined index data from the FFSINDEX.FFD table}
procedure ReadUserData;
{-read the user data from the FFSUSER.FFD table}
protected
{State methods}
procedure scInitialize; override;
procedure scPrepareForShutdown; override;
procedure scShutdown; override;
procedure scStartup; override;
{ Property methods }
function bseGetAutoSaveCfg : Boolean; override;
function bseGetReadOnly : Boolean; override;
procedure bseSetAutoSaveCfg(aValue : Boolean); override; {!!.01}
procedure bseSetReadOnly(aValue : Boolean); override; {!!.01}
procedure lcSetEventLog(anEventLog : TffBaseLog); override;
procedure lcSetLogEnabled(const aEnabled : boolean); override;
{ Misc }
procedure seCleanRebuildList(const aClientID : TffClientID); virtual;
{-Remove all entries in the rebuild status list for the specified
client. }
procedure seCollectGarbage(const aTimerEventCookie : Longint); virtual;
{-Looks for clients, sessions, databases, cursors, tables, &
folders that should be closed & freed. }
procedure seLoadConfig;
{-Reads in the server configuration tables and processes the
server script file (if present). }
procedure seForce(const aMsg : string; {!!.06 - Start}
args : array of const;
ReadOnly : Boolean); virtual; {!!.06 - End}
{-Use this method to log a formatted string to the event log. Writes to
the log whether or not logging is enabled. }
function seGetServerName : TffNetName;
{-Returns the server's name from its configuration. }
procedure seSetLoggingState;
{-Called whenever something is changed that would affect logging.
Sets a boolean flag that tells the logging routines whether or not
they can log. We centralize the logic here so that the logging
routines don't have to do the checks each time they are called. }
procedure seSetSQLEngine(anEngine : TffBaseSQLEngine);
{-Used to set the SQLEngine property of the server engine. }
{script stuff}
function CalcPriorityIndex(const PriorityStr : TffShStr) : integer;
function CalcKeyIndex(const KeyStr : TffShStr) : integer;
function ValBoolean(const BoolStr : TffShStr;
var BoolValue : boolean) : boolean;
procedure ProcessAliasScript;
{process the FFALIAS.SC$ script file to autocreate aliases}
procedure ProcessFullScript(const ScriptFileName : TffFullFileName);
{process a server script file to set general info & aliases}
procedure ProcessScriptCommand(const KeyStr, ValueStr : TffShStr;
var DeleteScript : Boolean);
public
{creation/destruction}
constructor Create(aOwner : TComponent); override;
destructor Destroy; override;
procedure FFNotificationEx(const AOp : Byte; AFrom : TffComponent; {!!.11}
const AData : TffWord32); override; {!!.11}
{ When the freeing of seSQLEngine is detected, this method
sets seSQLEngine to nil to avoid using the freed TffBaseSQLEngine. }
{ Event logging }
procedure Log(const aMsg : string); override;
{-Use this method to log a string to the event log. }
procedure LogAll(const Msgs : array of string); override;
{-Use this method to log multiple strings to the event log. }
procedure LogFmt(const aMsg : string; args : array of const); override;
{-Use this method to log a formatted string to the event log. }
{ Object validation }
function CheckCursorIDAndGet(aCursorID : TffCursorID;
var aCursor : TffSrBaseCursor)
: TffResult;
{-Find the cursor specified by aCursorID. }
function CheckDatabaseIDAndGet(aDatabaseID : TffDatabaseID;
var aDatabase : TffSrDatabase)
: TffResult;
{-Find the database specified by aDatabaseID. }
function CheckClientIDAndGet(aClientID : TffClientID; {!!.01 - Start}
var aClient : TffSrClient) {Moved from Public section}
: TffResult;
function CheckSessionIDAndGet(aClientID : TffClientID; {Moved from Public section}
aSessionID : TffSessionID;
var aClient : TffSrClient;
var aSession : TffSrSession)
: TffResult;
function CheckTransactionIDAndGet(aTransactionID : TffTransID; {Moved from Public section}
var aTrans : TffSrTransaction)
: TffResult; {!!.01 - End}
procedure GetServerNames(aList : TStrings;
aTimeout : Longint); override;
{transaction tracking}
function TransactionCommit(const aDatabaseID : TffDatabaseID) : TffResult; override;
{Begin !!.01}
function TransactionCommitSQL(const aDatabaseID : TffDatabaseID;
const notifyExtenders : Boolean) : TffResult;
{ Commit transaction for SQL engine. Does not reset timeout and controls
extender notification. }
{End !!.01}
function TransactionCommitSubset(const aDatabaseID : TffDatabaseID) : TffResult;
function TransactionRollback(const aDatabaseID : TffDatabaseID) : TffResult; override;
{Begin !!.01}
function TransactionRollbackSQL(const aDatabaseID : TffDatabaseID;
const notifyExtenders : Boolean) : TffResult;
{ Rollback transaction for SQL engine. Does not reset timeout and
controls extender notification. }
{End !!.01}
function TransactionStart(const aDatabaseID : TffDatabaseID;
const aFailSafe : boolean) : TffResult; override;
{-starts an explicit transaction}
{Begin !!.01}
function TransactionStartSQL(const aDatabaseID : TffDatabaseID;
const notifyExtenders : boolean) : TffResult;
{ For use by the SQL engine. Starts a transaction without resetting
the timeout & controls notification of extenders. }
{End !!.01}
{Begin !!.10}
function TransactionStartWith(const aDatabaseID : TffDatabaseID;
const aFailSafe : Boolean;
const aCursorIDs : TffPointerList) : TffResult; override;
{End !!.10}
{client related stuff}
function ClientAdd(var aClientID : TffClientID;
const aClientName : TffNetName;
const aUserID : TffName;
const aTimeout : Longint;
var aHash : TffWord32) : TffResult; override;
{Begin !!.11}
function ClientAddEx(var aClientID : TffClientID;
const aClientName : TffNetName;
const aUserID : TffName;
const aTimeout : Longint;
const aClientVersion : Longint;
var aHash : TffWord32) : TffResult; override;
{ Same as ClientAdd but client version is supplied via the aClientVersion
parameter. }
{End !!.11}
function ClientRemove(aClientID : TffClientID) : TffResult; override;
function ClientSetTimeout(const aClientID : TffClientID;
const aTimeout : Longint) : TffResult; override;
{client session related stuff}
function SessionAdd(const aClientID : TffClientID; const timeout : Longint;
var aSessionID : TffSessionID) : TffResult; override;
function SessionCloseInactiveTables(aClientID : TffClientID) : TffResult; override; {!!.06}
function SessionCount(aClientID : TffClientID; var aCount : integer) : TffResult; override;
function SessionGetCurrent(aClientID : TffClientID; var aSessionID : TffSessionID) : TffResult; override;
function SessionRemove(aClientID : TffClientID; aSessionID : TffSessionID) : TffResult; override;
function SessionSetCurrent(aClientID : TffClientID; aSessionID : TffSessionID) : TffResult; override;
function SessionSetTimeout(const aClientID : TffClientID;
const aSessionID : TffSessionID;
const aTimeout : Longint) : TffResult; override;
{database related stuff}
function DatabaseAddAlias(const aAlias : TffName;
const aPath : TffPath;
aCheckSpace : Boolean; {!!.11}
const aClientID : TffClientID)
: TffResult; override;
function DatabaseAliasList(aList : TList;
aClientID : TffClientID) : TffResult; override;
function RecoveryAliasList(aList : TList;
aClientID : TffClientID) : TffResult; override;
{-Return a list of database aliases for use by a journal recovery
engine. The functionality of this method is identical to
DatabaseAliasList except that it does not require the server engine
to be started. }
function DatabaseChgAliasPath(aAlias : TffName;
aNewPath : TffPath;
aCheckSpace : Boolean; {!!.11}
aClientID : TffClientID)
: TffResult; override;
function DatabaseClose(aDatabaseID : TffDatabaseID) : TffResult; override;
function DatabaseDeleteAlias(aAlias : TffName;
aClientID : TffClientID) : TffResult; override;
function DatabaseGetAliasPath(aAlias : TffName;
var aPath : TffPath;
aClientID : TffClientID) : TffResult; override;
function DatabaseGetFreeSpace(const aDatabaseID : TffDatabaseID;
var aFreeSpace : Longint) : TffResult; override;
function DatabaseModifyAlias(const aClientID : TffClientID;
const aAlias : TffName;
const aNewName : TffName;
const aNewPath : TffPath;
aCheckSpace : Boolean) {!!.11}
: TffResult; override;
function DatabaseOpen(aClientID : TffClientID;
const aAlias : TffName;
const aOpenMode : TffOpenMode;
const aShareMode : TffShareMode;
const aTimeout : Longint;
var aDatabaseID : TffDatabaseID) : TffResult; override;
function DatabaseOpenNoAlias(aClientID : TffClientID;
const aPath : TffPath;
const aOpenMode : TffOpenMode;
const aShareMode : TffShareMode;
const aTimeout : Longint;
var aDatabaseID : TffDatabaseID) : TffResult; override;
function DatabaseSetTimeout(const aDatabaseID : TffDatabaseID;
const aTimeout : Longint) : TffResult; override;
function DatabaseTableExists(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
var aExists : Boolean) : TffResult; override;
function DatabaseTableList(aDatabaseID : TffDatabaseID;
const aMask : TffFileNameExt;
aList : TList) : TffResult; override;
function DatabaseTableLockedExclusive(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
var aLocked : Boolean) : TffResult; override;
{rebuild status related stuff}
function RebuildGetStatus(aRebuildID : Longint;
const aClientID : TffClientID;
var aIsPresent : boolean;
var aStatus : TffRebuildStatus) : TffResult; override;
{table related stuff}
function TableAddIndex(const aDatabaseID : TffDatabaseID;
const aCursorID : TffCursorID;
const aTableName : TffTableName;
const aIndexDesc : TffIndexDescriptor) : TffResult; override;
function TableBuild(aDatabaseID : TffDatabaseID;
aOverWrite : boolean;
const aTableName : TffTableName;
aForServer : boolean;
aDictionary : TffDataDictionary) : TffResult; override;
function TableDelete(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName) : TffResult; override;
function TableDropIndex(aDatabaseID : TffDatabaseID;
aCursorID : TffCursorID;
const aTableName : TffTableName;
const aIndexName : TffDictItemName;
aIndexID : Longint) : TffResult; override;
function TableEmpty(aDatabaseID : TffDatabaseID;
aCursorID : TffCursorID;
const aTableName : TffTableName) : TffResult; override;
function TableGetAutoInc(aCursorID : TffCursorID;
var aValue : TffWord32) : TffResult; override;
function TableGetDictionary(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
aForServer : boolean;
aStream : TStream) : TffResult; override;
function TableGetRecCount(aCursorID : TffCursorID;
var aRecCount : Longint) : TffResult; override;
function TableGetRecCountAsync(aCursorID : TffCursorID; {!!.10}
var aTaskID : Longint) : TffResult; override; {!!.10}
function TableOpen(const aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
const aForServer : boolean;
const aIndexName : TffName;
aIndexID : Longint;
const aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
const aTimeout : Longint;
var aCursorID : TffCursorID;
aStream : TStream) : TffResult; override;
function TablePack(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
var aRebuildID : Longint): TffResult; override;
function TableRebuildIndex(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
const aIndexName : TffName;
aIndexID : Longint;
var aRebuildID : Longint): TffResult; override;
function TableRename(aDatabaseID : TffDatabaseID; const aOldName, aNewName : TffName) : TffResult; override;
function TableRestructure(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
aDictionary : TffDataDictionary;
aFieldMap : TffStringList;
var aRebuildID : Longint): TffResult; override;
function TableSetAutoInc(aCursorID : TffCursorID;
aValue : TffWord32) : TffResult; override;
{Begin !!.11}
function TableVersion(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
var aVersion : Longint) : TffResult; override;
{End !!.11}
{table locks via cursor}
function TableIsLocked(aCursorID : TffCursorID; aLockType : TffLockType;
var aIsLocked : boolean) : TffResult; override;
function TableLockAcquire(aCursorID : TffCursorID; aLockType : TffLockType) : TffResult; override;
function TableLockRelease(aCursorID : TffCursorID; aAllLocks : Boolean) : TffResult; override;
{cursor stuff}
function CursorClone(aCursorID : TffCursorID; aOpenMode : TffOpenMode;
var aNewCursorID : TffCursorID) : TffResult; override;
function CursorClose(aCursorID : TffCursorID) : TffResult; override;
function CursorCompareBookmarks(aCursorID : TffCursorID;
aBookmark1,
aBookmark2 : PffByteArray;
var aCompResult : Longint) : TffResult; override;
{Begin !!.02}
function CursorCopyRecords(aSrcCursorID,
aDestCursorID : TffCursorID;
aCopyBLOBs : Boolean) : TffResult; override;
{End !!.02}
function CursorDeleteRecords(aCursorID : TffCursorID) : TffResult; override; {!!.06}
function CursorGetBookmark(aCursorID : TffCursorID; aBookmark : PffByteArray) : TffResult; override;
function CursorGetBookmarkSize(aCursorID : TffCursorID;
var aSize : Integer) : TffResult; override;
function CursorOverrideFilter(aCursorID : Longint;
aExpression : pCANExpr;
aTimeout : TffWord32) : TffResult; override;
function CursorResetRange(aCursorID : TffCursorID) : TffResult; override;
function CursorRestoreFilter(aCursorID : Longint) : TffResult; override;
function CursorSetRange(aCursorID : TffCursorID;
aDirectKey : boolean;
aFieldCount1 : integer;
aPartialLen1 : integer;
aKeyData1 : PffByteArray;
aKeyIncl1 : boolean;
aFieldCount2 : integer;
aPartialLen2 : integer;
aKeyData2 : PffByteArray;
aKeyIncl2 : boolean) : TffResult; override;
function CursorSetTimeout(const aCursorID : TffCursorID;
const aTimeout : Longint) : TffResult; override;
function CursorSetToBegin(aCursorID : TffCursorID) : TffResult; override;
function CursorSetToBookmark(aCursorID : TffCursorID; aBookmark : PffByteArray) : TffResult; override;
function CursorSetToCursor(aDestCursorID : TffCursorID; aSrcCursorID : TffCursorID) : TffResult; override;
function CursorSetToEnd(aCursorID : TffCursorID) : TffResult; override;
function CursorSetToKey(aCursorID : TffCursorID;
aSearchAction : TffSearchKeyAction;
aDirectKey : boolean;
aFieldCount : integer;
aPartialLen : integer;
aKeyData : PffByteArray) : TffResult; override;
function CursorSwitchToIndex(aCursorID : TffCursorID;
aIndexName : TffDictItemName;
aIndexID : integer;
aPosnOnRec : boolean) : TffResult; override;
function CursorSetFilter(aCursorID : TffCursorID;
aExpression : pCANExpr;
aTimeout : TffWord32) : TffResult; override;
{Begin !!.03}
function CursorListBLOBFreeSpace(aCursorID : TffCursorID;
const aInMemory : Boolean;
aStream : TStream) : TffResult; override;
{End !!.03}
{record stuff}
function RecordDelete(aCursorID : TffCursorID; aData : PffByteArray) : TffResult; override;
function RecordDeleteBatch(aCursorID : TffCursorID;
aBMCount : Longint;
aBMLen : Longint;
aData : PffByteArray;
aErrors : PffLongintArray) : TffResult; override;
function RecordExtractKey(aCursorID : TffCursorID; aData : PffByteArray; aKey : PffByteArray) : TffResult; override;
function RecordGet(aCursorID : TffCursorID; aLockType : TffLockType; aData : PffByteArray) : TffResult; override;
function RecordGetBatch(aCursorID : TffCursorID;
aRecCount : Longint;
aRecLen : Longint;
var aRecRead : Longint;
aData : PffByteArray;
var aError : TffResult) : TffResult; override;
function RecordGetForKey(aCursorID : TffCursorID;
aDirectKey : boolean;
aFieldCount : integer;
aPartialLen : integer;
aKeyData : PffByteArray;
aData : PffByteArray;
aFirstCall : Boolean) : TffResult; override;
function RecordGetNext(aCursorID : TffCursorID; aLockType : TffLockType; aData : PffByteArray) : TffResult; override;
function RecordGetPrior(aCursorID : TffCursorID; aLockType : TffLockType; aData : PffByteArray) : TffResult; override;
function RecordInsert(aCursorID : TffCursorID; aLockType : TffLockType; aData : PffByteArray) : TffResult; override;
function RecordInsertBatch(aCursorID : TffCursorID;
aRecCount : Longint;
aRecLen : Longint;
aData : PffByteArray;
aErrors : PffLongintArray) : TffResult; override;
function RecordIsLocked(aCursorID : TffCursorID; aLockType : TffLockType;
var aIsLocked : boolean) : TffResult; override;
function RecordModify(aCursorID : TffCursorID; aData : PffByteArray; aRelLock : Boolean) : TffResult; override;
function RecordRelLock(aCursorID : TffCursorID; aAllLocks : Boolean) : TffResult; override;
{BLOB stuff}
function BLOBCreate(aCursorID : TffCursorID;
var aBlobNr : TffInt64) : TffResult; override;
function BLOBDelete(aCursorID : TffCursorID; aBLOBNr : TffInt64) : TffResult; override;
{Begin !!.03}
function BLOBListSegments(aCursorID : TffCursorID;
aBLOBNr : TffInt64;
aStream : TStream) : TffResult; override;
{End !!.03}
function BLOBRead(aCursorID : TffCursorID;
aBLOBNr : TffInt64;
aOffset : TffWord32; {!!.06}
aLen : TffWord32; {!!.06}
var aBLOB;
var aBytesRead : TffWord32) {!!.06}
: TffResult; override;
function BLOBFree(aCursorID : TffCursorID; aBLOBNr : TffInt64;
readOnly : boolean) : TffResult; override;
function BLOBGetLength(aCursorID : TffCursorID; aBLOBNr : TffInt64;
var aLength : Longint) : TffResult; override;
function BLOBTruncate(aCursorID : TffCursorID; aBLOBNr : TffInt64;
aBLOBLength : Longint) : TffResult; override;
function BLOBWrite(aCursorID : TffCursorID; aBLOBNr : TffInt64;
aOffset : Longint;
aLen : Longint;
var aBLOB ) : TffResult; override;
function FileBLOBAdd(aCursorID : TffCursorID;
const aFileName : TffFullFileName;
var aBLOBNr : TffInt64) : TffResult; override;
{query stuff}
function SQLAlloc(aClientID : TffClientID;
aDatabaseID : TffDatabaseID;
aTimeout : Longint;
var aStmtID : TffSqlStmtID) : TffResult; override;
function SQLExec(aStmtID : TffSqlStmtID;
aOpenMode : TffOpenMode;
var aCursorID : TffCursorID;
aStream : TStream) : TffResult; override;
function SQLExecDirect(aClientID : TffClientID;
aDatabaseID : TffDatabaseID;
aQueryText : PChar;
aTimeout : Longint;
aOpenMode : TffOpenMode;
var aCursorID : TffCursorID;
aStream : TStream) : TffResult; override;
function SQLFree(aStmtID : TffSqlStmtID) : TffResult; override;
function SQLPrepare(aStmtID : TffSqlStmtID;
aQueryText : PChar;
aStream : TStream) : TffResult; override;
function SQLSetParams(aStmtID : TffSqlStmtID;
aNumParams : word;
aParamDescs : Pointer;
aDataBuffer : PffByteArray;
aDataLen : integer;
aStream : TStream) : TffResult; override;
{misc stuff}
function GetServerDateTime(var aDateTime : TDateTime) : TffResult; override;
{begin !!.10}
function GetServerSystemTime(var aSystemTime : TSystemTime)
: TffResult; override;
function GetServerGUID(var aGUID : TGUID)
: TffResult; override;
function GetServerID(var aUniqueID : TGUID)
: TffResult; override;
function GetServerStatistics(var aStats : TffServerStatistics)
: TffResult; override;
function GetCommandHandlerStatistics(const aCmdHandlerIdx : Integer;
var aStats : TffCommandHandlerStatistics)
: TffResult; override;
function GetTransportStatistics(const aCmdHandlerIdx : Integer;
const aTransportIdx : Integer;
var aStats : TffTransportStatistics)
: TffResult; override;
{end !!.10}
function WriteAliasData : TffResult;
{-write the aliases to the FFSALIAS.FFD table}
function WriteGeneralInfo(aOverrideRO : Boolean) : TffResult;
{-write the general info to the FFSINFO.FFD table}
function WriteKeyProcData : TffResult;
{-write the user-defined index data to the FFSINDEX.FFD table}
function WriteUserData : TffResult;
{-write the user data to the FFSUSER.FFD table}
{properties}
property BufferManager : TffBufferManager read seBufMgr;
property ClientList : TffSrClientList read seClientList;
property Configuration : TffServerConfiguration
read seGetConfig;
property CursorList : TffSrCursorList read seCursorList;
property DatabaseList : TffSrDatabaseList read seDatabaseList;
property FolderList : TffSrFolderList read seFolderList;
property ServerName : TffNetName read seGetServerName;
property SessionList : TffSrSessionList read seSessionList;
property TableList : TffSrTableList read seTableList;
property CursorClass : TffSrCursorClass {!!.06}
read seCursorClass
write seCursorClass;
published
property CollectGarbage : Boolean
read seGetCollectGarbage
write seSetCollectGarbage
default False; {!!.01}
{ If True then the server engine is to perform garbage collection. }
property CollectFrequency : Longint
read seGetCollectFrequency
write seSetCollectFrequency
default ffcl_CollectionFrequency; {!!.01}
{ The number of milliseconds between each garbage collection run by the
server engine. }
property ConfigDir : string {!!.10}
read seGetConfigDir
write seSetConfigDir;
property MaxRAM : Longint {!!.01}
read seGetMaxRAM {!!.01}
write seSetMaxRAM
default 10; {!!.01}
property OnRecoveryCheck : TNotifyEvent
read seOnRecoveryCheck
write seOnRecoveryCheck;
{ Called when the server engine is initializing and it is time to
check for recovery of fail-safe transactions. }
property ScriptFile : string {!!.11}
read seGetScriptFile
write seSetScriptFile;
property SQLEngine : TffBaseSQLEngine
read seSQLEngine
write seSetSQLEngine;
end;
var
ffc_AdminUserID : string[5];
implementation
uses
TypInfo,
ActiveX,
ffllcomp,
ffllcomm,
ffsrjour, {!!.06}
// ffsqleng, {Deleted !!.03}
ffsrsort;
const
ffc_NumBLOBBytesToCopy = ffcl_1MB;
{ When copying BLOBs from one cursor to another, this is the initial number
of bytes to read from the source BLOB. }
ffcl_FlushRate = 5 * 60 * 1000; {!!.01}
{ Flush memory pools and other pools every 5 minutes. } {!!.01}
resourceString
ffcTable = 'table %s';
ffcTableContent = 'content of table ''%s''';
{===Utility functions================================================}
function FFMapLock(const aClientLock : TffLockType;
const isTableLock : boolean) : TffSrLockType;
{-Map a client lock type to a server lock type. }
begin
Result := ffsltNone;
if isTableLock then
case aClientLock of
ffltNoLock : Result := ffsltShare;
ffltReadLock : Result := ffsltIntentS;
ffltWriteLock : Result := ffsltExclusive;
end { case }
else
if aClientLock = ffltWriteLock then
Result := ffsltExclusive
else
Result := ffsltNone;
end;
{====================================================================}
type
{ Base thread class for rebuild operations }
TffSrRebuildBaseThread = class(TffThread)
protected { private }
protected
rthServerEngine: TffServerEngine;
rthParams: PffSrRebuildParams; {!!.13}
public
constructor Create(aServerEngine : TffServerEngine;
aRebuildParamsPtr : PffSrRebuildParams);
destructor Destroy; override;
end;
{ Thread class for table reindexing operation }
TffSrReindexThread = class(TffSrRebuildBaseThread)
protected
procedure Execute; override;
end;
{ Thread class for table packing operation }
TffSrPackThread = class(TffSrRebuildBaseThread)
protected
procedure Execute; override;
end;
{ Thread class for table restructure operation }
TffSrRestructureThread = class(TffSrRebuildBaseThread)
protected
procedure Execute; override;
end;
{Begin !!.10}
{ Thread class for asynchronous record count }
TffSrGetRecordCountThread = class(TffSrRebuildBaseThread)
protected
procedure Execute; override;
end;
{End !!.10}
{===TffSrReindexThread====================================================}
constructor TffSrRebuildBaseThread.Create(
aServerEngine : TffServerEngine;
aRebuildParamsPtr : PffSrRebuildParams);
begin
rthServerEngine := aServerEngine;
rthParams := aRebuildParamsPtr;
// Dispose(aRebuildParamsPtr); {Deleted !!.13}
inherited Create(False);
FreeOnTerminate := True;
end;
destructor TffSrRebuildBaseThread.Destroy;
begin
{Begin !!.13}
if Assigned(rthParams.rpFieldMap) then
rthParams.rpFieldMap.Free;
FFFreeMem(rthParams, SizeOf(rthParams^));
{End !!.13}
inherited Destroy;
end;
{--------}
procedure TffSrReindexThread.Execute;
begin
rthServerEngine.seTableRebuildIndexPrim(rthParams); {!!.13}
end;
{--------}
procedure TffSrPackThread.Execute;
begin
rthServerEngine.seTablePackPrim(rthParams); {!!.13}
end;
{--------}
procedure TffSrRestructureThread.Execute;
begin
{ Because we are passing a field map within the rebuild parameters,
TablePackPrim knows that we are doing a restructure. }
rthServerEngine.seTablePackPrim(rthParams); {!!.13}
end;
{Begin !!.10}
{--------}
procedure TffSrGetRecordCountThread.Execute;
begin
rthServerEngine.seTableGetRecordCountPrim(rthParams); {!!.13}
end;
{End !!.10}
{====================================================================}
{===TffServerObject==================================================}
constructor TffServerObject.Create(const aTimeout : Longint);
begin
inherited Create;
soState := ffosInactive;
soTimeout := aTimeout;
end;
{--------}
destructor TffServerObject.Destroy;
begin
inherited Destroy;
end;
{--------}
function TffServerObject.Activate : boolean;
begin
if soState in [ffosInactive, ffosActive] then begin
if soClient = nil then
soLock.Lock
else
soClient.soLock.Lock;
soState := ffosActive;
Result := True;
end
else
Result := False;
end;
{--------}
function TffServerObject.CanClose(const Mark : boolean) : boolean;
begin
Result := (soState = ffosInactive) or (soState = ffosClosing);
{ Note: If the state is ffosClosePending then the object is active &
will be freed once it has completed. Until then we have to
leave it alone. }
if (soState = ffosInactive) and Mark then
soState := ffosClosing;
end;
{--------}
procedure TffServerObject.Deactivate;
begin
case soState of
ffosActive :
soState := ffosInactive;
ffosClosePending :
begin
soState := ffosClosing;
if Self.CanClose(True) then
Self.Free;
end;
end; { case }
if soClient = nil then
soLock.Unlock
else
soClient.soLock.Unlock;
end;
{--------}
procedure TffServerObject.ForceClose;
begin
soState := ffosClosing;
end;
{--------}
procedure TffServerObject.RequestClose;
begin
if soState = ffosActive then
soState := ffosClosePending
else if soState = ffosInactive then
soState := ffosClosing;
end;
{--------}
function TffServerObject.ShouldClose : boolean;
begin
Result := (soState = ffosClosing);
end;
{====================================================================}
{===TffServerObjectList==============================================}
constructor TffServerObjectList.Create;
begin
inherited Create;
solList := TffThreadList.Create;
end;
{--------}
destructor TffServerObjectList.Destroy;
begin
solList.Free;
inherited Destroy;
end;
{--------}
procedure TffServerObjectList.BeginRead;
begin
solList.BeginRead;
end;
{--------}
procedure TffServerObjectList.BeginWrite;
begin
solList.BeginWrite;
end;
{--------}
function TffServerObjectList.CanClose(const Mark : boolean) : boolean;
var
Inx : Longint;
begin
Result := True;
for Inx := 0 to pred(solList.Count) do begin
{ If any one of the objects cannot be closed then return False.
Note we have the option to tell each Inactive object to mark itself
as closed. This makes sure the object is unavailable until we actually
free it. Note that we must call CanClose on each object. If we break
out of this loop early, an object that should be closed may never
be marked as closable. }
if (not TffServerObject(solList[Inx]).CanClose(Mark)) then
Result := False;
end;
end;
{--------}
procedure TffServerObjectList.EndRead;
begin
solList.EndRead;
end;
{--------}
procedure TffServerObjectList.EndWrite;
begin
solList.EndWrite;
end;
{--------}
procedure TffServerObjectList.ForceClose;
var
Inx : Longint;
begin
for Inx := 0 to pred(solList.Count) do
TffServerObject(solList[Inx]).ForceClose;
end;
{Begin !!.06}
{--------}
function TffServerObjectList.HasClosableState(const Mark : Boolean) : boolean;
var
Inx : Longint;
begin
Result := True;
for Inx := 0 to pred(solList.Count) do begin
{ If any one of the objects cannot be closed then return False. }
if not (TffServerObject(solList[Inx]).State in
[ffosInactive, ffosClosing]) then begin
Result := False;
Break;
end;
end;
{ If all objects are in a closable state and we are to mark them as being
closed then do so. }
if Result and Mark then
for Inx := 0 to pred(solList.Count) do
if TffServerObject(solList[Inx]).State = ffosInactive then
TffServerObject(solList[Inx]).State := ffosClosing;
end;
{End !!.06}
{--------}
procedure TffServerObjectList.RemoveUnused;
var
Index : Longint;
begin
solList.BeginWrite;
try
for Index := pred(solList.Count) downto 0 do
{Begin !!.05}
try
if TffServerObject(solList[Index]).ShouldClose then
solList.DeleteAt(Index);
except
{ If an exception occurred then it is most likely because the object
has already been deleted. Remove the invalid object from the list. }
solList.RemoveAt(Index);
end;
{End !!.05}
finally
solList.EndWrite;
end;
end;
{Begin !!.03}
{--------}
procedure TffServerObjectList.RequestClose;
var
Inx : Longint;
begin
for Inx := 0 to pred(solList.Count) do
TffServerObject(solList[Inx]).RequestClose;
end;
{End !!.03}
{--------}
function TffServerObjectList.ShouldClose : boolean;
var
Inx : Longint;
begin
Result := True;
for Inx := 0 to pred(solList.Count) do begin
{ If any one of the objects cannot be closed then return False. }
if (not TffServerObject(solList[Inx]).ShouldClose) then begin
Result := False;
break;
end;
end;
end;
{====================================================================}
{===TffSrBaseCursor==================================================}
constructor TffSrBaseCursor.Create(anEngine : TffServerEngine;
aDatabase : TffSrDatabase;
const aTimeout : Longint);
begin
inherited Create(aTimeout);
soClient := aDatabase.Client;
bcCloseTable := False;
bcCloseWTrans := False; {!!.05}
bcDatabase := aDatabase;
bcEngine := anEngine;
bcExclOwner := False;
bcExtenders := nil;
bcInfoLock := TffPadlock.Create; {!!.06}
bcTable := nil;
bcTempStore := nil;
bcNumReadLocks := 0; {!!.05}
end;
{Begin !!.01}
{--------}
function TffSrBaseCursor.CanClose(const Mark : Boolean) : Boolean;
begin
{ Cursor can be closed if it is not in a transaction or if the table is
temporary. }
Result := (bcDatabase.Transaction = nil) or
(fffaTemporary in bcTable.Files[0].fiAttributes);
if Result then
Result := inherited CanClose(Mark) {!!.05 - Start}
else if (bcDatabase.Transaction <> nil) then
bcCloseWTrans := True; {!!.05 - End}
end;
{End !!.01}
{--------}
procedure TffSrBaseCursor.Open(const aTableName : TffTableName;
const aIndexName : TffName;
const aIndexID : Longint;
const aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
aForServer : boolean;
const aExclContLock : Boolean; {!!.10}
aAttribs : TffFileAttributes);
var
aLockType : TffSrLockType;
NewTable : boolean;
OpenIntentRegistered : Boolean;
begin
bcIndexID := aIndexID;
NewTable := False;
OpenIntentRegistered := False;
{ The cursor references an instance of TffSrBaseTable. Multiple cursors may
reference that same instance of TffSrBaseTable since only 1 instance of
TffSrBaseTable is created per physical table (saves on file handles).
So we must determine whether the table has already been opened by
another cursor. But first, we must obtain write access on the engine's
table list. Why?
1. If the table has not been opened, we don't want two threads trying
to open it at the same time. Should that occur, we would wind
up with duplicate tables in our table list.
2. If the table is already open, we don't thread A closing the table
while thread B is trying to "open" the table. Good recipe for an
access violation.
Complication: If the table is open and locked, and this thread wants
to open the table in an incompatible lock mode, we must make sure
the table list is available to other threads. Otherwise, we will
freeze the server. }
bcEngine.TableList.BeginWrite;
try
{ Try & find the open table in the engine's table list. If it exists already
then reference the existing table. }
bcTable := bcEngine.GetTableInstance(bcDatabase.Folder, aTableName);
{ Is the table open? }
if assigned(bcTable) then begin
{ Yes. Register our intent to open the table. This prevents another
thread from freeing the table. }
bcTable.RegisterOpenIntent;
OpenIntentRegistered := True;
{ Release our lock on the table list. We must do so because our
request for a lock on the table may cause this thread to wait.
Retaining the lock in such a situation would freeze any threads
wanting access to the table list. }
bcEngine.TableList.EndWrite;
{ Determine the type of lock for the table, based upon the Open mode and
Share mode. }
if (aShareMode = smExclusive) then
aLockType := ffsltExclusive
else if (aOpenMode = omReadOnly) then
{ Table is to be opened as Read-only. }
aLockType := ffsltShare
else
{ Table is to be opened as Read-Write. }
aLockType := ffsltIntentS;
{ Acquire the lock. We will return from this call when the lock
is granted. Otherwise an exception will be raised (i.e., another
thread has the table locked exclusively and isn't giving it
up). }
bcTable.AcqLock(CursorID, aLockType);
end
else begin
{ No, it is not open. Open it now. }
try
bcTableOpenPrim(bcDatabase, aTableName, aOpenMode, aShareMode,
aForServer, aAttribs);
except
bcEngine.TableList.EndWrite;
raise;
end;
NewTable := true;
bcTable.RegisterOpenIntent; {!!.01}
OpenIntentRegistered := True; {!!.01}
end;
{ Make sure we meet all requirements for opening the table. }
try
bcTableOpenPreconditions(bcTable, aIndexName, bcIndexID, aOpenMode);
except
{ If we created a new table then get rid of it. }
if NewTable then begin
if OpenIntentRegistered then {!!.02}
bcTable.DeregisterOpenIntent; {!!.02}
bcTable.Free;
bcTable := nil;
end;
raise;
end;
{ Add the newly opened table to the server table list. }
if NewTable then
bcEngine.TableList.AddTable(bcTable);
bcInit(aOpenMode, aShareMode, aExclContLock); {Moved !!.01}
finally
{ If the table was not already opened then we still have the tableList
locked. Unlock it. }
if NewTable then
bcEngine.TableList.EndWrite; {!!.01}
if (bcTable <> nil) and OpenIntentRegistered then {!!.02}
{ If we registered our intent to open then deregister our intent. }
bcTable.DeregisterOpenIntent;
end;
// bcInit(aOpenMode, aShareMode); {Moved !!.01}
end;
{--------}
procedure TffSrBaseCursor.Build(const aTableName : TffTableName;
aDict : TffDataDictionary;
const aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
aForServer : boolean;
aOverWrite : boolean;
aAttribs : TffFileAttributes;
aStoreSize : TffWord32);
var
aLockType : TffSrLockType;
aTransID : TffTransID;
OpenIntentRegistered : Boolean; {!!.10}
TableDataFile : TffFileNameExt;
TmpTableName : TffTableName;
begin
bcIndexID := 0;
OpenIntentRegistered := False; {!!.10}
TmpTableName := aTableName;
if (fffaTemporary in aAttribs) then begin
{ Requirement: If the temporary file attribute is specified, the table must
have a block size of 64k. This is due to temporary storage (unit FFLLTEMP)
being restricted to 64k blocks of data. }
if (aDict.BlockSize < (64 * 1024)) then
aDict.BlockSize := (64 * 1024);
{ If no tablename specified then generate a unique table name. }
if TmpTableName = '' then
TmpTableName := IntToStr(Longint(Self));
end;
{ Obtain write access to the table list. Our purpose is to make sure
the table is not opened. By obtaining write access, we prevent other
threads from creating or opening the table. }
bcEngine.TableList.BeginWrite;
try
{ Was a tablename specified? }
if aTableName <> '' then begin
{ Yes. It is possible the table may already exist.
Try and find the open table in our list. If it exists already
obviously there's an error (we can't build a new table when it's
already open). }
bcTable := bcEngine.GetTableInstance(bcDatabase.Folder, TmpTableName);
if assigned(bcTable) then
FFRaiseException(EffException, ffStrResServer, fferrTableOpen,
[TmpTableName]);
{ The table name must be a valid file name without extension. }
if not FFVerifyFileName(TmpTableName) then
FFRaiseException(EffException, ffStrResServer, fferrInvalidTableName,
[TmpTableName]);
{ Is this a temporary table? }
if not (fffaTemporary in aAttribs) then begin
{ No. The table's data file cannot exist within the database. }
TableDataFile := FFMakeFileNameExt(TmpTableName, ffc_ExtForData);
if FFFileExists(FFMakeFullFileName(bcDatabase.Folder.Path,
TableDataFile)) then begin
if aOverWrite then
{ We want to overwrite this table - we have to delete it first. }
bcEngine.seDeleteTable(bcDatabase, TmpTableName)
else
FFRaiseException(EffException, ffStrResServer, fferrTableExists,
[TmpTableName]);
end;
end;
end;
{ Is this cursor to have its own temporary storage? }
if aStoreSize > 0 then
bcTempStore := ffcTempStorageClass.Create(bcEngine.ConfigDir,
aStoreSize, 64 * 1024)
else
bcTempStore := nil;
{ Create the table. }
bcTable := bcTableClass.Create(bcEngine, TmpTableName, bcDatabase.Folder,
bcEngine.BufferManager, omReadWrite);
try
bcTable.RegisterOpenIntent; {!!.10}
OpenIntentRegistered := True; {!!.10}
bcEngine.seTransactionStart(bcDatabase, false, true, aTransID);
try
{ Create the files comprising the table. }
bcTable.BuildFiles(bcDatabase.TransactionInfo, aForServer, aDict,
aAttribs, bcTempStore);
bcEngine.seTransactionCommit(bcDatabase);
except
bcEngine.seTransactionRollback(bcDatabase);
raise;
end;
{ Acquire the right type of lock on the table. }
if aShareMode = smExclusive then
aLockType := ffsltExclusive
else if aOpenMode = omReadOnly then
aLockType := ffsltShare
else
aLockType := ffsltIntentS;
bcTable.AcqLock(CursorID, aLockType);
bcTableOpenPreconditions(bcTable, '', bcIndexID, aOpenMode);
except
{ Destroy the table object. This will close all the files. }
bcTable.DeregisterOpenIntent;
bcTable.Free;
bcTable := nil;
raise;
end;{try..finally}
bcEngine.TableList.AddTable(bcTable);
bcInit(aOpenMode, aShareMode, False); {!!.10}
finally
bcEngine.TableList.EndWrite;
if assigned(bcTable) and OpenIntentRegistered then {!!.10}
bcTable.DeregisterOpenIntent; {!!.10}
end;
end;
{--------}
destructor TffSrBaseCursor.Destroy;
var
anExtender : TffBaseEngineExtender;
anIndex : Longint;
begin
bcEngine.TableList.BeginWrite; {!!.10}
try
{ Assumption: If cursor is being closed in the context of a transaction then
the changes made to the table should be saved. We will retain the cursor's
locks in the lock manager so that no other cursors can access those
records. The changes to the table will stay in memory until the transaction
commits or rolls back. }
{Begin !!.03}
{ If still have a record locked from TffTable.Edit then release the lock. }
if not FFI64IsZero(bcLockedRefNum) then
bcTable.RemoveLocksForCursor(bcDatabase.DatabaseID, {!!.10}
CursorID, bcLockedRefNum, {!!.10}
bcDatabase.TransactionInfo);
{End !!.03}
if (bcRecordData <> nil) then {!!.01}
FFFreeMem(bcRecordData, bcRecordLen); {!!.01}
bcBLOBCursors.Free;
if bcExclOwner then begin
bcTable.SetExclOwner(ffc_W32NoValue);
bcExclOwner := False;
end;
if assigned(bcExtenders) then begin
for anIndex := pred(bcExtenders.Count) downto 0 do begin
anExtender := TffBaseEngineExtender
(TffIntListItem(bcExtenders[anIndex]).KeyAsInt);
anExtender.Free;
end;
bcExtenders.Free;
end;
// if bcCloseTable then begin {Deleted !!.02}
if bcTable <> nil then begin {!!.02}
bcTable.CursorList.BeginWrite;
try
bcTable.CursorList.RemoveCursor(CursorID);
finally
bcTable.CursorList.EndWrite;
end;
if bcCloseTable then {!!.02}
bcEngine.TableList.RemoveIfUnused(bcTable);
end;
bcTempStore.Free;
bcInfoLock.Free; {!!.06}
finally
bcEngine.TableList.EndWrite; {!!.10}
inherited Destroy;
end;
end;
{--------}
procedure TffSrBaseCursor.AcqContentLock(const aMode : TffContentLockMode); {!!.10}
{ NOTE:: If you change this method then look at AcqContentLockCond for similar
changes. }
begin
if (fffaBLOBChainSafe in bcGetAttribs) or {!!.05}
(bcExclOwner and (not bcTable.Dictionary.HasBLOBFields)) then {!!.03}{!!.05}
Exit;
Assert(assigned(bcDatabase.Transaction) or
(aMode = ffclmRead));
{ Is a transaction active? }
if assigned(bcDatabase.Transaction) then
{ Yes. Call the appropriate table method. }
case aMode of
ffclmCommit :
bcTable.BeginCommit;
ffclmRead :
bcTable.AcqContentLock(bcDatabase.Transaction, ffsltShare, False);
ffclmWrite :
bcTable.AcqContentLock(bcDatabase.Transaction, ffsltExclusive, False);
end { case }
else begin {!!.05 - Start}
{ No transaction. This should be a reader thread that wants read access. }
if (bcNumReadLocks = 0) then
bcTable.BeginRead;
InterlockedIncrement(bcNumReadLocks);
end; {!!.05 - End}
end;
{Begin !!.10}
{--------}
function TffSrBaseCursor.AcqExclContentLock : TffResult;
{ NOTE:: If you change this method then look at AcqContentLock for similar
changes. }
begin
if not ((fffaBLOBChainSafe in bcGetAttribs) or
(bcExclOwner and (not bcTable.Dictionary.HasBLOBFields))) then begin
Assert(assigned(bcDatabase.Transaction));
Result := bcTable.AcqExclContentLock(bcDatabase.Transaction);
end
else
Result := DBIERR_NONE;
end;
{End !!.10}
{--------}
procedure TffSrBaseCursor.AppendNewRecord(aData : PffByteArray);
begin
AcqContentLock(ffclmWrite);
InsertRecord(aData, ffsltExclusive);
end;
{--------}
procedure TffSrBaseCursor.bcAddExtender(anExtender : TffBaseEngineExtender);
var
anItem : TffIntListItem;
begin
if assigned(anExtender) then begin
if not assigned(bcExtenders) then
bcExtenders := TffList.Create;
anItem := TffIntListItem.Create(Longint(anExtender));
bcExtenders.Insert(anItem);
end;
end;
{--------}
function TffSrBaseCursor.bcBLOBCopy(aSrcCursor : TffSrBaseCursor;
const aBLOBNr : TffInt64;
var aDestBLOBNr : TffInt64)
: TffResult;
var
aBLOB : PffByteArray;
aBytesRead,
aLen,
aOffset : TffWord32; {!!.06}
FileName : TffFullFileName;
begin
Result := DBIERR_NONE;
{ Assumption: Transaction has already been started by a calling routine. }
{ Is this a file BLOB? }
if FFTblGetFileNameBLOB
(aSrcCursor.bcTable.btFiles[aSrcCursor.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo, aBLOBNr, FileName) then begin
FFTblAddFileBLOB(bcTable.btFiles[Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo,
FileName, aDestBLOBNr);
end
else begin
aBytesRead := 0;
aOffset := 0;
{Begin !!.12}
aLen := FFMinI(aSrcCursor.BLOBGetLength(aBLOBNr, Result),
ffc_NumBLOBBytesToCopy);
if Result = DBIERR_NONE then begin
{End !!.12}
FFGetMem(aBLOB, aLen);
try
{ Create the BLOB in the destination cursor. }
Result := BLOBAdd(aDestBLOBNr);
if Result = DBIERR_NONE then
repeat
Result := aSrcCursor.BLOBRead(aBLOBNr, aOffset, aLen, aBLOB^,
aBytesRead);
if aBytesRead > 0 then begin
Result := BLOBWrite(aDestBLOBNr, aOffset, aBytesRead, aBLOB^);
inc(aOffset, aBytesRead);
end;
until (aBytesRead = 0) or (Result <> DBIERR_NONE);
finally
FFFreeMem(aBLOB, aLen);
end;
end; { if } {!!.12}
end;
end;
{--------}
function TffSrBaseCursor.bcBLOBLinkGetLength(const aTableName : TffTableName;
const aBLOBNr : TffInt64;
var aLength : Longint) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
Cursor := bcFindBLOBCursor(aTableName);
Assert(Assigned(Cursor));
aLength := Cursor.BLOBGetLength(aBLOBNr, Result);
end;
{--------}
function TffSrBaseCursor.bcBLOBLinkRead(const aTableName : TffTableName;
const aBLOBNr : TffInt64;
const aOffset : TffWord32; {!!.06}
const aLen : TffWord32; {!!.06}
var aBLOB;
var aBytesRead : TffWord32) {!!.06}
: TffResult;
var
Cursor : TffSrBaseCursor;
begin
Cursor := bcFindBLOBCursor(aTableName);
Assert(Assigned(Cursor));
Result := Cursor.BLOBRead(aBLOBNr, aOffset, aLen, aBLOB, aBytesRead);
end;
{--------}
function TffSrBaseCursor.bcCheckExclusiveReadWrite : TffResult;
begin
Result := DBIERR_NONE;
{ The cursor must have Exclusive access to the table. }
if (not bcExclOwner) then
Result := DBIERR_NEEDEXCLACCESS
else if (bcOpenMode = omReadOnly) and {!!.06}
not (fffaTemporary in bcTable.Files[0].fiAttributes)then {!!.06}
{ The cursor must be in read-write mode. Temporary files are excluded
from this rule. }
Result := DBIERR_TABLEREADONLY;
end;
{--------}
function TffSrBaseCursor.bcFindBLOBCursor(const aTableName : TffTableName)
: TffSrBaseCursor;
var
Inx : Longint;
UTableName : TffTableName;
begin
Result := nil;
UTableName := Uppercase(aTableName);
{ Do we have any BLOB cursors yet? }
if bcBLOBCursors = nil then
{ No. Instantiate. }
bcBLOBCursors := TffList.Create;
{ Have we opened a cursor for the referenced table? }
for Inx := 0 to pred(bcBLOBCursors.Count) do begin
if UpperCase(TffSrBaseCursor(bcBLOBCursors[Inx]).bcTable.BaseName) =
UTableName then begin
Result := TffSrBaseCursor(bcBLOBCursors[Inx]);
break;
end;
end;
{ Did we find a cursor? }
if Result = nil then begin
{ No. Create one. }
{ Limitation: BLOB links can refer only to standard cursors, not to
SQL result sets. }
Result := bcEngine.CursorClass.Create(bcEngine, {!!.06}
bcDatabase,
Timeout);
Result.Open(aTableName, '', 0, omReadOnly, smShared, False, False, []); {!!.01}
bcBLOBCursors.Insert(Result);
end;
end;
{--------}
function TffSrBaseCursor.bcGetAttribs : TffFileAttributes;
begin
Result := bcTable.Files[0]^.fiAttributes;
end;
{--------}
function TffSrBaseCursor.bcGetCursorID : TffCursorID;
begin
Result := TffCursorID(Self);
end;
{--------}
function TffSrBaseCursor.bcGetPosition : TffCursorPosition;
begin
Result := bcInfo.Pos;
end;
{--------}
function TffSrBaseCursor.bcGetRefNr : TffInt64;
begin
Result := bcInfo.RefNr;
end;
{--------}
procedure TffSrBaseCursor.bcInit(const aOpenMode : TffOpenMode;
const aShareMode : TffShareMode;
const aExclContLock : Boolean); {!!.10}
var
anIndex : Longint;
aMonitor : TffBaseEngineMonitor;
anExtender : TffBaseEngineExtender;
MonitorList : TffList;
begin
{ Assumption: This routine only called once a table has been successfully
opened by the cursor. }
bcFilter := nil;
bcFilterSav := nil;
bcNewRecBuff := nil;
bcOldRecBuff := nil;
{ Miscellaneous. }
if bcEngine.Configuration.GeneralInfo^.giReadOnly then
bcOpenMode := omReadOnly
else
bcOpenMode := aOpenMode;
FreeOnRemove := true;
{ Add ourself to the cursor lists in the table and database. }
bcTable.CursorList.BeginWrite;
try
bcTable.CursorList.AddCursor(Self);
finally
bcTable.CursorList.EndWrite;
end;
bcDatabase.CursorList.BeginWrite;
try
bcDatabase.CursorList.AddCursor(Self);
finally
bcDatabase.CursorList.EndWrite;
end;
{ If there are any monitors interested in cursors then see if they
are interested in this cursor. }
MonitorList := bcEngine.GetInterestedMonitors(TffSrBaseCursor);
if assigned(MonitorList) then begin
for anIndex := 0 to pred(MonitorList.Count) do begin
aMonitor := TffBaseEngineMonitor
(TffIntListItem(MonitorList[anIndex]).KeyAsInt);
try
anExtender := aMonitor.Interested(Self);
if assigned(anExtender) then
bcAddExtender(anExtender);
except
on E:Exception do
bcEngine.seForce('Monitor [%s] exception, bcInit: %s', {!!.06 - Start}
[aMonitor.ClassName,E.message],
bcEngine.bseGetReadOnly); {!!.06 - End}
end;
end;
MonitorList.Free;
end;
{ Get memory for a record data scratch pad. }
bcRecordLen := bcTable.Dictionary.RecordLength;
FFGetMem(bcRecordData, bcRecordLen);
{ If the cursor is the exclusive owner of the table then mark this
fact. }
if aShareMode = smExclusive then begin
bcTable.SetExclOwner(CursorID);
bcExclOwner := True;
end;
{Begin !!.10}
if aExclContLock then
bcTable.AcqContentLock(bcDatabase.Transaction, ffsltExclusive, False);
{End !!.10}
end;
{--------}
procedure TffSrBaseCursor.bcInvalidateCurKey;
begin
bcInfo.KeyValid := false;
end;
{--------}
function TffSrBaseCursor.bcIsCurKeyPathValid : boolean;
begin
Result := (bcInfo.KeyPath.kpPos <> kppUnknown);
end;
{--------}
function TffSrBaseCursor.bcIsCurKeyValid: boolean;
begin
Result := bcInfo.KeyValid;
end;
{--------}
procedure TffSrBaseCursor.bcRecordUpdated(aOp : TffRecOp;
aRefNr : TffInt64;
aIndexID : integer);
begin
{ A cursor is affected by another cursor's operations as follows:
1. When a cursor inserts a record, it may cause a Structural Modification
Operation (SMO) in the indices. Other cursors open on the same
table may now have invalid key paths.
In FF 1.x, this routine would reset the key path. In FF 2.x, we
leave the key path as is. The next time the cursor moves to the next
or previous record, the indexing code will see that the key path has
been modified and rebuild the key path.
*** We do not call this routine for a record insertion. ***
2. When a cursor deletes a record, it may cause an SMO in the indices. As
mentioned for inserts, we will rely upon the indexing code to rebuild
the key path.
If another cursor is positioned on the deleted record, we must make sure
the cursor knows the record has been deleted. This routine sets the
bcInfo.Deleted flag and positions the cursor to OnCrack. When this
notification occurs, any cursors wanting to do something with the record
will be blocked while waiting for a lock on the record so this should
be a safe operation.
3. When a cursor modifies a record, it may cause an SMO in zero or more
indicies. As mentioned for inserts, we will rely upon the indexing
code to rebuild the key path.
If another cursor is positioned on the modified record, we must make
it look like the record has been deleted. This routine sets the
bcInfo.Deleted flag and positions the cursor to OnCrack. When this
notification occurs, any cursors wanting to do something with the record
will be blocked while waiting for a lock on the record so this should
be a safe operation.
In general, this method is thread-safe. It is called only for those cursors
that belong to the same database as the cursor performing the insert,
update, or delete. Those cursors should be in the same client thread
and only one request from that client thread is executed on the server
at any given time. So operations should not be active for any of the
other cursors belonging to the same database.
}
case aOp of
roDelete :
if (FFCmpI64(aRefNr, bcInfo.RefNr) = 0) and
(bcInfo.Pos = cpOnRecord) then begin
bcInfo.Deleted := True;
if bcIsCurKeyPathValid then begin
Assert(bcInfo.KeyPath.kpCount > 0);
bcInfo.Pos := cpOnCrack;
bcInfo.KeyPath.kpPos := kppOnCrackBefore;
end
else
bcInfo.KeyPath.kpPos := kppUnknown;
end;
roModify :
if (aIndexID = IndexID) and
(FFCmpI64(aRefNr, bcInfo.RefNr) = 0) and
(bcInfo.Pos = cpOnRecord) then begin
bcInfo.Deleted := True;
if bcIsCurKeyPathValid then begin
Assert(bcInfo.KeyPath.kpCount > 0);
bcInfo.Pos := cpOnCrack;
bcInfo.KeyPath.kpPos := kppOnCrackBefore;
end
else
bcInfo.KeyPath.kpPos := kppUnknown;
end;
end;{case}
end;
{--------}
procedure TffSrBaseCursor.bcRestoreCurInfo;
begin
bcInfo := bcSavedInfo;
end;
{--------}
procedure TffSrBaseCursor.bcSaveCurInfo;
begin
bcSavedInfo := bcInfo;
end;
{--------}
procedure TffSrBaseCursor.bcTableOpenPrim(aDatabase : TffSrDatabase;
const aTableName : TffTableName;
const aOpenMode : TffOpenMode;
const aShareMode : TffShareMode;
const aForServer : boolean;
const aAttribs : TffFileAttributes);
var
aLockType : TffSrLockType;
TableDataFile : TffFileNameExt;
begin
{ The table name must be a valid file name without extension. }
if not FFVerifyFileName(aTableName) then
FFRaiseException(EffException, ffstrResServer, fferrInvalidTableName,
[aTableName]);
{ The table's data file must exist within the database. }
TableDataFile := FFMakeFileNameExt(aTableName, ffc_ExtForData);
if not FFFileExists(FFMakeFullFileName(aDatabase.Folder.Path, TableDataFile)) then
FFRaiseException(EffException, ffstrResServer, fferrUnknownTable,
[TableDataFile, aDatabase.Alias]);
{ Create the table instance. }
bcTable := bcTableClass.Create(aDatabase.Engine, aTableName, {!!.03}
aDatabase.Folder,
aDatabase.Engine.BufferManager, aOpenMode);{!!.03}
try
{ Acquire the right type of lock on the table. }
if aShareMode = smExclusive then
aLockType := ffsltExclusive
else if aOpenMode = omReadOnly then
aLockType := ffsltShare
else
aLockType := ffsltIntentS;
bcTable.AcqLock(CursorID, aLockType);
{ Open up the files in the table, making sure that all of them
are in FF format. }
bcTable.OpenFiles(aDatabase.TransactionInfo, aForServer, aAttribs);
TffSrTable(bcTable).ResolveDynamicLinks; {!!.06}
bcTable.SetAttributes(aAttribs);
except
bcTable.Free;
bcTable := nil;
raise;
end;
end;
{--------}
function TffSrBaseCursor.BLOBAdd(var aBLOBNr : TffInt64) : TffResult;
begin
Result := NotifyExtenders(ffeaBeforeBLOBCreate, ffeaBLOBCreateFail);
if Result = DBIERR_NONE then
try
AcqContentLock(ffclmWrite);
FFTblAddBLOB(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo, aBLOBNr);
NotifyExtenders(ffeaAfterBLOBCreate, ffeaNoAction);
except
NotifyExtenders(ffeaBLOBCreateFail, ffeaNoAction);
raise;
end;
end;
{--------}
function TffSrBaseCursor.BLOBLinkAdd(const aTableName : TffTableName;
const aTableBLOBNr : TffInt64;
var aBLOBNr : TffInt64) : TffResult;
begin
Result := NotifyExtenders(ffeaBeforeBLOBLinkAdd, ffeaBLOBLinkAddFail);
if Result = DBIERR_NONE then
try
AcqContentLock(ffclmWrite);
FFTblAddBLOBLink(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo, aTableName, aTableBLOBNr,
aBLOBNr);
NotifyExtenders(ffeaAfterBLOBLinkAdd, ffeaNoAction);
except
NotifyExtenders(ffeaBLOBLinkAddFail, ffeaNoAction);
raise;
end;
end;
{--------}
function TffSrBaseCursor.FileBLOBAdd(const aFileName : TffFullFileName;
var aBLOBNr : TffInt64) : TffResult;
begin
Result := NotifyExtenders(ffeaBeforeFileBLOBAdd, ffeaFileBLOBAddFail);
if Result = DBIERR_NONE then
try
AcqContentLock(ffclmWrite);
FFTblAddFileBLOB(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo, aFileName, aBLOBNr);
NotifyExtenders(ffeaAfterFileBLOBAdd, ffeaNoAction);
except
NotifyExtenders(ffeaFileBLOBAddFail, ffeaNoAction);
raise;
end;
end;
{--------}
function TffSrBaseCursor.BLOBDelete(const aBLOBNr : TffInt64) : TffResult;
begin
Result := NotifyExtenders(ffeaBeforeBLOBDelete, ffeaBLOBDeleteFail);
if Result = DBIERR_NONE then
try
AcqContentLock(ffclmWrite);
FFTblDeleteBLOB(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo, aBLOBNr);
NotifyExtenders(ffeaAfterBLOBDelete, ffeaNoAction);
except
NotifyExtenders(ffeaBLOBDeleteFail, ffeaNoAction);
raise;
end;
end;
{--------}
function TffSrBaseCursor.BLOBFree(aBLOBNr : TffInt64) : TffResult;
begin
Result := NotifyExtenders(ffeaBeforeBLOBFree, ffeaBLOBFreeFail);
if Result = DBIERR_NONE then
try
AcqContentLock(ffclmWrite);
if FFTblFreeBLOB(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo, aBLOBNr) then
Result := DBIERR_BLOBMODIFIED;
NotifyExtenders(ffeaAfterBLOBFree, ffeaNoAction);
except
NotifyExtenders(ffeaBLOBFreeFail, ffeaNoAction);
raise;
end;
end;
{--------}
function TffSrBaseCursor.BLOBGetLength(aBLOBNr : TffInt64;
var aFBError: TffResult) : Longint;
begin
Result := -1;
aFBError := NotifyExtenders(ffeaBeforeBLOBGetLength, ffeaBLOBGetLengthFail);
if aFBError = DBIERR_NONE then
try
AcqContentLock(ffclmRead);
try
Result := FFTblGetBLOBLength(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo,
aBLOBNr,
bcBLOBLinkGetLength,
aFBError);
NotifyExtenders(ffeaAfterBLOBGetLength, ffeaNoAction);
finally
RelContentLock(ffclmRead);
end;
except
NotifyExtenders(ffeaBLOBGetLengthFail, ffeaNoAction);
raise;
end;
end;
{Begin !!.03}
{--------}
function TffSrBaseCursor.BLOBIsLink(aBLOBNr : TffInt64; {!!.11 - Start}
var aSrcTableName : TffTableName;
var aSrcTableBLOBNr : TffInt64)
: Boolean;
begin
Result := FFTblIsBLOBLink(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo,
aBLOBNr,
aSrcTableName,
aSrcTableBLOBNr);
end;
{--------} {!!.11 - End}
function TffSrBaseCursor.BLOBListSegments(aBLOBNr : TffInt64;
aStream : TStream)
: TffResult;
begin
Result := DBIERR_NONE;
AcqContentLock(ffclmRead);
try
FFTblListBLOBSegments(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo, aBLOBNr,
aStream);
finally
RelContentLock(ffclmRead);
end;
end;
{End !!.03}
{--------}
function TffSrBaseCursor.BLOBRead(aBLOBNr : TffInt64;
aOffset : TffWord32; {!!.06}
aLen : TffWord32; {!!.06}
var aBLOB;
var aBytesRead : TffWord32) {!!.06}
: TffResult;
begin
Result := NotifyExtenders(ffeaBeforeBLOBRead, ffeaBLOBReadFail);
if Result = DBIERR_NONE then
try
AcqContentLock(ffclmRead);
try
{Begin !!.11}
bcTable.btBLOBEngine.Read
(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo,
aBLOBNr,
aOffset,
aLen,
bcBLOBLinkRead,
aBLOB,
aBytesRead,
Result);
{End !!.11}
NotifyExtenders(ffeaAfterBLOBRead, ffeaNoAction);
finally
RelContentLock(ffclmRead);
end;
except
NotifyExtenders(ffeaBLOBReadFail, ffeaNoAction);
raise;
end;
end;
{--------}
function TffSrBaseCursor.BLOBTruncate(aBLOBNr : TffInt64;
aLen : TffWord32) : TffResult;
begin
Result := NotifyExtenders(ffeaBeforeBLOBTruncate, ffeaBLOBTruncateFail);
if Result = DBIERR_NONE then
try
AcqContentLock(ffclmWrite);
{Begin !!.11}
bcTable.btBLOBEngine.Truncate
(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo, aBLOBNr, aLen);
{End !!.11}
NotifyExtenders(ffeaAfterBLOBTruncate, ffeaNoAction);
except
NotifyExtenders(ffeaBLOBTruncateFail, ffeaNoAction);
raise;
end;
end;
{--------}
function TffSrBaseCursor.BLOBWrite(const aBLOBNr : TffInt64;
aOffset : TffWord32;
aLen : TffWord32;
var aBLOB) : TffResult;
begin
Result := NotifyExtenders(ffeaBeforeBLOBWrite, ffeaBLOBWriteFail);
if Result = DBIERR_NONE then
try
AcqContentLock(ffclmWrite);
{Begin !!.11}
bcTable.btBLOBEngine.Write
(bcTable.Files[bcTable.Dictionary.BLOBFileNumber],
bcDatabase.TransactionInfo, aBLOBNr, aOffset, aLen,
aBLOB);
{End !!.11}
NotifyExtenders(ffeaAfterBLOBWrite, ffeaNoAction);
except
on E:Exception do begin
NotifyExtenders(ffeaBLOBWriteFail, ffeaNoAction);
raise;
end;
end;
end;
{--------}
function TffSrBaseCursor.CopyRecords(aSrcCursor : TffSrBaseCursor;
aBLOBCopyMode : TffBLOBCopyMode;
aCallback : TffSrCopyRecordsProc;
aCookie1, aCookie2 : Longint) : TffResult;
var
aAutoIncField : Integer; {!!.02}
aAutoIncHigh : TffWord32; {!!.02}
aThisAutoInc : TffWord32; {!!.02}
aBLOBFields : TffPointerList;
aBLOBNr,
aSrcBLOBNr : TffInt64;
aInx,
aOffset : integer;
aRecord : PffByteArray;
aTableName : TffTableName;
aTransID : TffTransID;
aVal : PffByteArray;
Include,
IsNull : boolean;
begin
aVal := nil;
{Begin !!.02}
{ Does the target have an autoinc field? }
if Dictionary.HasAutoIncField(aAutoIncField) then
{ Yes. Get the current seed value. }
ReadAutoIncValue(aAutoIncHigh)
else
{ No. Flag it. }
aAutoIncField := -1;
{End !!.02}
{ Requirement: The cursors must be pointing to different tables. }
if bcTable = aSrcCursor.Table then
FFRaiseExceptionNoData(EffException, ffStrResServer, fferrSameTable);
aTableName := aSrcCursor.Table.BaseName;
aBLOBFields := TffPointerList.Create;
try
{ Requirement: The dictionary field types and sizes must match. }
if not bcTable.Dictionary.HasSameFields(aSrcCursor.Dictionary, aBLOBFields) then
FFRaiseExceptionNoData(EffException, ffStrResServer, fferrIncompatDict);
{ Save the position of each cursor. }
bcSaveCurInfo;
aSrcCursor.bcSaveCurInfo;
{ Create a record buffer. }
FFGetMem(aRecord, bcTable.Dictionary.RecordLength);
try
{ Position the source cursor to BOF. }
aSrcCursor.SetToBegin;
{ Start a transaction. It will be nested if a transaction is already
in progress. }
Result := bcEngine.seTransactionStart(bcDatabase, False, True, aTransID);
try
while (Result = DBIERR_NONE) do begin
{ Grab a record from the source cursor. }
Result := aSrcCursor.GetNextRecord(aRecord, ffsltNone);
if Result = DBIERR_NONE then begin
{ Was a callback function specified? }
Include := True;
if assigned(aCallback) then
aCallback(aSrcCursor, aRecord, aCookie1, aCookie2, Include);
if Include then begin
{ Any BLOB fields? }
if aBLOBFields.Count > 0 then begin
{ Yes. Copy or link as necessary. }
for aInx := 0 to pred(aBLOBFields.Count) do begin
aOffset := bcTable.Dictionary.FieldOffset[Integer(aBLOBFields[aInx])];
{ Is the BLOB field null? }
aSrcCursor.Dictionary.GetRecordField(Integer(aBLOBFields[aInx]),
aRecord, IsNull, aVal);
if not IsNull then begin
case aBLOBCopyMode of
ffbcmNoCopy :
bcTable.Dictionary.SetRecordField
(Integer(aBLOBFields[aInx]), aRecord, nil);
ffbcmCopyFull :
begin
aSrcBLOBNr := PffInt64(@aRecord^[aOffset])^;
Result := bcBLOBCopy(aSrcCursor, aSrcBLOBNr, aBLOBNr);
if Result = DBIERR_NONE then
PffInt64(@aRecord^[aOffset])^ := aBLOBNr
else
break;
end;
else { link the BLOBs }
{ Get the BLOB reference out of the record. }
aSrcBLOBNr := PffInt64(@aRecord^[aOffset])^;
{ Add a BLOB link. }
BLOBLinkAdd(aTableName, aSrcBLOBNr, aBLOBNr);
{ Update the BLOB reference in the record. }
PffInt64(@aRecord^[aOffset])^ := aBLOBNr;
end; { case }
end; { if BLOB field not null }
end; { for }
end;
Result := InsertRecord(aRecord, ffsltNone);
{Begin !!.02}
{ If the target has an autoinc field then keep track of the
highest value. }
if (Result = DBIERR_NONE) and(aAutoIncField > -1) then begin
Dictionary.GetRecordField(aAutoIncField,
aRecord, IsNull, @aThisAutoInc);
if not IsNull and (aThisAutoInc > aAutoIncHigh) then
aAutoIncHigh := aThisAutoInc;
end;
{End !!.02}
end;
end; { if }
end; { while }
{Begin !!.02}
if Result = DBIERR_EOF then begin
{ If the destination has an autoinc field then update the seed
value. }
if aAutoIncField <> -1 then
FFTblSetAutoIncValue(Table.Files[0],
Database.TransactionInfo,
aAutoIncHigh);
Result := bcEngine.seTransactionCommit(bcDatabase);
end
{End !!.02}
else
bcEngine.seTransactionRollback(bcDatabase);
except
bcEngine.seTransactionRollback(bcDatabase);
raise;
end;
finally
{ Free the record buffer. }
FFFreeMem(aRecord, bcTable.Dictionary.RecordLength);
{ Restore the position of each cursor. }
bcRestoreCurInfo;
aSrcCursor.bcRestoreCurInfo;
end;
finally
aBLOBFields.Free;
end;
end;
{--------}
function TffSrBaseCursor.CopyRecordParts(aSrcCursor : TffSrBaseCursor;
aFields : PffLongintArray;
aNumFields : integer;
aBLOBCopyMode : TffBLOBCopyMode;
aCallback : TffSrCopyRecordsProc;
aCookie1, aCookie2 : Longint) : TffResult;
var
aBLOBFields : TffPointerList;
aInx : integer;
aDestRec, aSrcRec : PffByteArray;
aSrcBLOBNr, aBLOBNr : TffInt64;
aOffset : integer;
aTableName : TffTableName;
aTransID : TffTransID;
aVal : PffByteArray;
DestLen : integer;
Include : boolean;
IsNull : boolean;
begin
{ Requirement: The cursors must be pointing to different tables. }
if bcTable = aSrcCursor.Table then
FFRaiseExceptionNoData(EffException, ffStrResServer, fferrSameTable);
aTableName := aSrcCursor.Table.BaseName;
aBLOBFields := TffPointerList.Create;
try
{ Requirement: The dictionary field types and sizes must match. }
if not bcTable.Dictionary.HasSameFieldsEx(aSrcCursor.Dictionary, aFields,
aNumFields, aBLOBFields) then
FFRaiseExceptionNoData(EffException, ffStrResServer, fferrIncompatDict);
{ Save the position of each cursor. }
bcSaveCurInfo;
aSrcCursor.bcSaveCurInfo;
{ Create record buffers. }
DestLen := bcTable.Dictionary.RecordLength;
FFGetMem(aDestRec, DestLen);
FFGetMem(aSrcRec, aSrcCursor.Dictionary.RecordLength);
FFGetMem(aVal, aSrcCursor.Dictionary.BlockSize);
try
{ Position the source cursor to BOF. }
aSrcCursor.SetToBegin;
{ Start a transaction. It will be nested if a transaction is already
in progress. }
Result := bcEngine.seTransactionStart(bcDatabase, False, True, aTransID);
try
while (Result = DBIERR_NONE) do begin
{ Grab a record from the source cursor. }
Result := aSrcCursor.GetNextRecord(aSrcRec, ffsltNone);
if Result = DBIERR_NONE then begin
{ Was a callback function specified? }
Include := True;
if assigned(aCallback) then
aCallback(aSrcCursor, aSrcRec, aCookie1, aCookie2, Include);
if Include then begin
{ Build the destination record. }
FillChar(aDestRec^, destLen, 0);
for aInx := 0 to pred(aNumFields) do begin
aSrcCursor.Dictionary.GetRecordField(aFields^[aInx],
aSrcRec, IsNull, aVal);
if IsNull then
bcTable.Dictionary.SetRecordField(aInx, aDestRec, nil)
else begin
{ Is this a BLOB field? }
if bcTable.Dictionary.FieldType[aInx] in
[fftBLOB..fftBLOBFile] then begin
aOffset := aSrcCursor.Dictionary.FieldOffset[aFields^[aInx]];
{ Yes. How is it to be handled? }
case aBLOBCopyMode of
ffbcmNoCopy :
bcTable.Dictionary.SetRecordField(aInx, aDestRec, nil);
ffbcmCopyFull :
begin
aSrcBLOBNr := PffInt64(@aSrcRec^[aOffset])^;
Result := bcBLOBCopy(aSrcCursor, aSrcBLOBNr, aBLOBNr);
if Result = DBIERR_NONE then
PffInt64(@aDestRec^[aOffset])^ := aBLOBNr
else
break;
end;
else { link the BLOBs }
{ Get the BLOB reference out of the record. }
aSrcBLOBNr := PffInt64(@aSrcRec^[aOffset])^;
{ Add a BLOB link. }
BLOBLinkAdd(aTableName, aSrcBLOBNr, aBLOBNr);
{ Update the BLOB reference in the record. }
PffInt64(@aDestRec^[aOffset])^ := aBLOBNr;
end; { case }
end
else
bcTable.Dictionary.SetRecordField(aInx, aDestRec, aVal);
end;
end;
{ Insert the record. }
Result := InsertRecord(aDestRec, ffsltNone);
end;
end; { if }
end; { while }
if Result = DBIERR_EOF then
Result := bcEngine.seTransactionCommit(bcDatabase)
else
bcEngine.seTransactionRollback(bcDatabase);
except
bcEngine.seTransactionRollback(bcDatabase);
raise;
end;
finally
{ Free the record buffers. }
FFFreeMem(aSrcRec, aSrcCursor.Dictionary.RecordLength);
FFFreeMem(aDestRec, DestLen);
FFFreeMem(aVal, aSrcCursor.Dictionary.BlockSize);
{ Restore the position of each cursor. }
bcRestoreCurInfo;
aSrcCursor.bcRestoreCurInfo;
end;
finally
aBLOBFields.Free;
end;
end;
{--------}
function TffSrBaseCursor.DeleteRecord(aData : PffByteArray) : TffResult;
var
BTreeChanged : Boolean; {!!.05}
LockedRefNr : TffInt64; {!!.05}
begin
Result := DBIERR_NONE; {!!.01}
{ Are we on a record? }
if (bcInfo.Pos <> cpOnRecord) then begin
{ No. }
Result := DBIERR_NOCURRREC;
Exit;
end;
{ Note: By this time, any other cursor deleting the record ahead of us has
completed and has set bcInfo.Deleted. We can be assured of this because
TffServerEngine.RecordDelete calls Cursor.EnsureWritable(true) which
obtains a lock on the record to be deleted. We won't get that lock until
the other cursor has finished. }
try {!!.01}
{ Has this record already been deleted? }
if bcInfo.Deleted then begin
{ Yes. }
Result := DBIERR_KEYORRECDELETED;
Exit;
end;
AcqContentLock(ffclmWrite);
if (aData = nil) and {!!.02}
((bcFilter <> nil) or (bcExtenders <> nil)) then {!!.02}
aData := bcRecordData;
if (aData <> nil) then begin
Table.GetRecord(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, aData, {!!.10}
ffsltExclusive, True, False); { lock obtained in EnsureWritable } {!!.02}
if Assigned(bcFilter) then
if not bcFilter.MatchesRecord(aData) then begin
{ Release the record lock. }
// Table.RelRecordLock(bcDatabase.TransactionInfo, CursorID, bcInfo.refNr); {Deleted !!.01}
Result := DBIERR_NOCURRREC;
Exit;
end;
end;
{ Notify extenders. }
bcOldRecBuff := aData;
try
Result := NotifyExtenders(ffeaBeforeRecDelete, ffeaDeleteRecFail);
{ If the extenders object, we can't continue. }
if Result = DBIERR_NONE then begin
BTreeChanged := False; {!!.05 - Start}
LockedRefNr := bcInfo.refNr; {!!.05}
Result := Table.DeleteRecord(Database.TransactionInfo, CursorID,
bcInfo.refNr, True, BTreeChanged);
if (Result = DBIERR_NONE) then begin
bcTable.RelaxRecordLock(bcDatabase.TransactionInfo, CursorID,{!!.10}
bcInfo.RefNr); {!!.10}
if bcInfo.KeyPath.kpPos = kppUnknown then
bcInfo.Pos := cpUnknown
else if (BTreeChanged) then begin
bcRebuildKeyPath;
end else if (bcInfo.KeyPath.kpPos = kppOnKey) then begin
bcInfo.KeyPath.kpPos := kppOnCrackBefore;
bcInfo.Deleted := True;
bcInfo.Pos := cpOnCrack;
end; {!!.05 - End}
{ Notify extenders of successful delete. }
NotifyExtenders(ffeaAfterRecDelete, ffeaNoAction);
end else
{ Notify extenders of failed delete. }
NotifyExtenders(ffeaDeleteRecFail, ffeaNoAction);
end;
finally
bcOldRecBuff := nil;
end;
{Begin !!.01}
finally
{ Release the record lock if an error occurred or we are in an implicit
transaction. }
{Begin !!.03}
if (Result <> DBIERR_NONE) or
bcDatabase.Transaction.IsImplicit then begin
Table.RelRecordLock(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, LockedRefNr); {!!.05}{!!.10}
{ Did an edit occur just prior to the delete? }
if not FFI64IsZero(bcLockedRefNum) then begin
Table.RelRecordLock(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, bcLockedRefNum); {!!.10}
FFInitI64(bcLockedRefNum);
end;
end;
{End !!.03}
end;
{End !!.01}
end;
{Begin !!.06}
{--------}
function TffSrBaseCursor.DeleteRecords : TffResult;
var
aRecord : PffByteArray;
aTransID : TffTransID;
begin
{ Create a record buffer. }
FFGetMem(aRecord, bcTable.Dictionary.RecordLength);
try
{ Position to BOF. }
SetToBegin;
{ Start a transaction. It will be nested if a transaction is already
in progress. }
Result := bcEngine.seTransactionStart(bcDatabase, False, True, aTransID);
try
while (Result = DBIERR_NONE) do begin
{ If on a record then get it otherwise move to the next
record. }
if bcInfo.Pos = cpOnRecord then begin
Result := GetRecord(aRecord, ffsltExclusive);
{ Is a filter active? }
if Result = DBIERR_NOCURRREC then
{ Yes. The current record didn't match the filter. Find the next
record that matches the filter. }
Result := GetNextRecord(aRecord, ffsltExclusive);
end
else
Result := GetNextRecord(aRecord, ffsltExclusive);
if Result = DBIERR_NONE then
Result := DeleteRecord(aRecord);
end; { while }
if Result = DBIERR_EOF then
Result := bcEngine.seTransactionCommit(bcDatabase)
else
bcEngine.seTransactionRollback(bcDatabase);
except
bcEngine.seTransactionRollback(bcDatabase);
raise;
end;
finally
FFFreeMem(aRecord, bcTable.Dictionary.RecordLength);
end;
end;
{End !!.06}
{--------}
function TffSrBaseCursor.Empty : TffResult;
begin
{ Requirement: Transaction must be started. }
Assert(Assigned(bcDatabase.Transaction));
{ The cursor must have Exclusive Read-Write access. }
Result := bcCheckExclusiveReadWrite;
if Result <> DBIERR_NONE then
exit;
{ Get the table to empty itself. }
AcqContentLock(ffclmWrite);
Result := bcTable.EmptyFiles(Database.TransactionInfo);
end;
{--------}
function TffSrBaseCursor.EnsureWritable(aCheckCurRec, aConditionalLock : Boolean) : TffResult;
begin
{ The cursor must have been opened in read-write mode. }
if (bcOpenMode = omReadOnly) then begin
Result := DBIERR_TABLEREADONLY;
Exit;
end;
{ There cannot be any type of lock on the table (unless its ours and
is a write lock). }
if Table.btClientLocks.Count > 0 then
if Table.btClientLocks.SummaryMode = ffsltExclusive then begin
if not Table.HasClientLock(CursorID) then begin
Result := DBIERR_FILELOCKED;
Exit;
end;
end
else begin
Result := DBIERR_FILELOCKED;
Exit;
end;
{ Make sure the cursor is positioned on a record. }
if aCheckCurRec then begin
if (bcInfo.pos <> cpOnRecord) then begin
Result := DBIERR_NOCURRREC;
Exit;
end;
// if Assigned(bcFilter) then begin {Deleted !!.02}
AcqContentLock(ffclmRead);
try
Table.GetRecord(Database.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, {!!.10}
bcRecordData, ffsltExclusive, false, aConditionalLock); {!!.02}
{ Note: We assume we can ask for an Exclusive lock because this
method is passed True only from the Engine.RecordDelete and
Engine.RecordModify methods. }
if assigned(bcFilter) and {!!.02}
(not bcFilter.MatchesRecord(bcRecordData)) then begin {!!.02}
Result := DBIERR_NOCURRREC;
Exit;
end;
finally
RelContentLock(ffclmRead);
end;
// end; {Deleted !!.02}
end;
{ There must have been a transaction started for our owning database. }
if not assigned(Database.Transaction) then begin
Result := DBIERR_NOACTIVETRAN;
Exit;
end;
Result := DBIERR_NONE;
end;
{--------}
procedure TffSrBaseCursor.ReadAutoIncValue(var aValue: TffWord32);
begin
AcqContentLock(ffclmRead);
try
aValue := FFTblReadAutoIncValue(bcTable.Files[0], bcDatabase.TransactionInfo);
finally
RelContentLock(ffclmRead);
end;
end;
{--------}
function TffSrBaseCursor.bcGetDictionary : TffDataDictionary;
begin
Result := Table.Dictionary;
end;
{--------}
function TffSrBaseCursor.GetRecord(aData : PffByteArray; aLockType : TffSrLockType) : TffResult;
begin
{ Request a lock on the record prior to our testing any logic. We must
make sure that a delete in progress has finished before we make
any decisions. }
{Begin !!.03}{Begin !!.06}
if (bcInfo.pos = cpOnRecord) and (aLockType <> ffsltNone) then begin
{ If there is a write lock on the table then return an error. }
if (bcTable.btClientLocks.Count > 0) then
{ If table is write locked but not by this client then cannot obtain
a lock on the record. If table is read locked by any client then cannot
obtain a lock on the record. }
if Table.btClientLocks.SummaryMode = ffsltExclusive then begin
if (not bcTable.HasClientLock(CursorID)) then begin
Result := DBIERR_FILELOCKED;
Exit;
end;
end
else begin
Result := DBIERR_FILELOCKED;
Exit;
end;
{ Made it this far. Obtain the record lock. }
Table.GetRecordLock(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, aLockType); {!!.10}
bcLockedRefNum := bcInfo.refNr;
end; { if }
{End !!.03}{End !!.06}
if (bcInfo.pos = cpOnRecord) then begin
AcqContentLock(ffclmRead);
bcInfoLock.Lock; {!!.06}
try
Result := Table.GetRecord(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, {!!.10}
bcInfo.refNr, aData, aLockType, true, false); {!!.02}
if Assigned(bcFilter) then begin
if not bcFilter.MatchesRecord(aData) then begin
{ Release the record lock. }
Table.RelRecordLock(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.RefNr); {!!.10}
Result := DBIERR_NOCURRREC;
Exit;
end;
end;
{Begin !!.02}
if (Result = DBIERR_NONE) and (aData <> nil) then
Move(aData^, bcRecordData^, bcRecordLen);
{End !!.02}
finally
bcInfoLock.Unlock; {!!.06}
RelContentLock(ffclmRead);
end;
end
else if bcInfo.pos = cpEOF then
Result := DBIERR_EOF
else if bcInfo.Deleted then
Result := DBIERR_KEYORRECDELETED
else
Result := DBIERR_NOCURRREC;
end;
{--------}
function TffSrBaseCursor.GetRecordField(aField : integer;
aRecordBuffer : PffByteArray;
var isNull: boolean;
aFieldBuffer : pointer) : TffResult;
begin
Result := DBIERR_NONE;
bcTable.Dictionary.GetRecordField(aField, aRecordBuffer, isNull, aFieldBuffer);
end;
{--------}
function TffSrBaseCursor.IsRecordLocked(aLockType : TffSrLockType) : Boolean;
begin
Result := bcTable.IsRecordLocked(Database.TransactionInfo, CursorID,
bcInfo.refNr, aLockType);
end;
{Begin !!.03}
{--------}
procedure TffSrBaseCursor.ListBLOBFreeSpace(aTI : PffTransInfo;
const aInMemory : Boolean;
aStream : TStream);
begin
Assert(bcTable <> nil);
bcTable.ListBLOBFreeSpace(aTI, aInMemory, aStream);
end;
{End !!.03}
{--------}
function TffSrBaseCursor.NotifyExtenders(const anAction : TffEngineAction;
const aFailAction : TffEngineAction) : TffResult;
var
anExtender : TffBaseEngineExtender;
anIndex : Longint;
anIndex2 : Longint;
begin
Result := DBIERR_NONE;
if assigned(bcExtenders) then
for anIndex := 0 to pred(bcExtenders.Count) do begin
anExtender := TffBaseEngineExtender
(TffIntListItem(bcExtenders[anIndex]).KeyAsInt);
if (anAction in anExtender.InterestedActions) or
(anExtender.InterestedActions = []) then begin
Result := anExtender.Notify(Self, anAction); {!!.06}
{since we aren't ignoring Notify's error code, we must
capture it. If an extender reports an error we will not
process the rest of the extenders and we will notify the
previous extenders that we are "undoing" the previous action}
if Result <> DBIERR_NONE then begin
for anIndex2 := 0 to pred(anIndex) do begin
anExtender := TffBaseEngineExtender
(TffIntListItem(bcExtenders[anIndex2]).KeyAsInt);
anExtender.Notify(self, aFailAction);
end;
break;
end;
end;
end;
end;
{--------}
function TffSrBaseCursor.OverrideFilter(aExpression : pCANExpr;
aTimeout : TffWord32)
: TffResult;
begin
Result := DBIERR_NONE;
try
bcFilterSav := bcFilter;
bcFilter := nil;
if Assigned(aExpression) then
bcFilter := TffSrFilter.Create(self, bcTable.Dictionary, {!!.11}
aExpression,
aTimeout);
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E,
bcEngine.FEventLog,
bcEngine.bseGetReadOnly);
end;
end;
end;
{--------}
procedure TffSrBaseCursor.RelContentLock(aMode : TffContentLockMode);
begin
if (fffaBLOBChainSafe in bcGetAttribs) or {!!.05}
(bcExclOwner and (not bcTable.Dictionary.HasBLOBFields)) then {!!.03}{!!.05}
Exit;
Assert(assigned(bcDatabase.Transaction) or (aMode = ffclmRead));
if assigned(bcDatabase.Transaction) then begin
{ Content locks obtained by a transaction via AcqContentLock are freed when
the transaction's locks are released. }
if aMode = ffclmCommit then
bcTable.EndCommit(bcDatabase.DatabaseID);
end else begin {!!.05 - Start}
InterlockedDecrement(bcNumReadLocks);
{ If the number of read locks ever goes below 0, it's outta whack.}
Assert(bcNumReadLocks >= 0);
if (bcNumReadLocks = 0) then
bcTable.EndRead;
end; {!!.05 - End}
end;
{--------}
procedure TffSrBaseCursor.RelRecordLock(aAllLocks : boolean);
begin
Assert((not aAllLocks), 'Unsupported: Release all record locks for a cursor');
{Begin !!.03}
if not FFI64IsZero(bcInfo.refNr) then begin
bcTable.RemoveLocksForCursor(bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, {!!.10}
bcDatabase.TransactionInfo); {!!.10}
if FFCmpI64(bcInfo.refNr, bcLockedRefNum) = 0 then
FFInitI64(bcLockedRefNum);
end;
{End !!.03}
end;
{--------}
procedure TffSrBaseCursor.RelTableLock(aAllLocks : Boolean);
begin
bcTable.RelLock(CursorID, aAllLocks);
end;
{--------}
procedure TffSrBaseCursor.RemoveIfUnused; {!!.05 - Added}
begin
if (State = ffosClosing) then
Free;
end; {!!.05 - End added}
{--------}
function TffSrBaseCursor.RestoreFilter : TffResult;
begin
Result := DBIERR_NONE;
try
bcFilter.Free;
bcFilter := bcFilterSav;
bcFilterSav := nil;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E,
bcEngine.FEventLog,
bcEngine.bseGetReadOnly);
end;
end;
end;
{--------}
procedure TffSrBaseCursor.SetAutoIncValue(aValue: TffWord32);
begin
AcqContentLock(ffclmWrite);
FFTblSetAutoIncValue(bcTable.Files[0], bcDatabase.TransactionInfo, aValue);
end;
{--------}
function TffSrBaseCursor.SetFilter(aExpression : pCANExpr;
aTimeout : TffWord32) : TffResult;
begin
Result := DBIERR_NONE;
try
bcFilter.Free;
bcFilter := nil;
if Assigned(aExpression) then
bcFilter := TffSrFilter.Create(self, bcTable.Dictionary, {!!.11}
aExpression, aTimeout); {!!.11}
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, bcEngine.EventLog, bcEngine.bseGetReadOnly);
end;
end;
end;
{Begin !!.01}
{--------}
function TffSrBaseCursor.ShouldClose : boolean;
begin
Result := (bcDatabase.Transaction = nil) and (soState = ffosClosing);
end;
{End !!.01}
{--------}
function TffSrBaseCursor.SortRecords(aFieldsArray : TffFieldList;
const aOrderByArray : TffOrderByArray;
const aNumFields : integer) : TffResult;
var
aRecord : PffByteArray;
aTransID : TffTransID;
RecLen : Longint;
SortEngine : TffSrBaseSortEngine;
begin
{ The cursor must have Exclusive Read-Write access. }
Result := bcCheckExclusiveReadWrite;
if Result <> DBIERR_NONE then
exit;
{ Create the sort engine. }
SortEngine := ffcSortEngineClass.Create(bcEngine, bcDatabase, aFieldsArray,
aOrderByArray, aNumFields,
bcTable.Dictionary, bcIndexID);
RecLen := bcTable.Dictionary.RecordLength;
FFGetMem(aRecord, RecLen);
try
{ Start a transaction. }
bcEngine.seTransactionStart(bcDatabase, false, true, aTransID);
try
{ Position to the beginning of the table. }
Result := DBIERR_NONE;
SetToBegin;
{ Walk through the records, posting them to the sort engine. }
while (Result = DBIERR_NONE) do begin
Result := GetNextRecord(aRecord, ffsltNone);
if Result = DBIERR_NONE then begin
SortEngine.Put(aRecord);
end;
end;
bcEngine.seTransactionCommit(bcDatabase);
except
bcEngine.seTransactionRollback(bcDatabase);
raise;
end;
bcEngine.seTransactionStart(bcDatabase, false, true, aTransID);
try
{Begin !!.01}
{ Empty the table. }
// Result := Empty;
// if Result = DBIERR_NONE then begin
{ Position to start of table. We will overwrite existing records
in order to preserve BLOB data. }
Result := DBIERR_NONE;
SetToBegin;
{ Read the records back from the sort engine. }
while (Result = DBIERR_NONE) do begin
if SortEngine.Get(aRecord) then begin
GetNextRecord(nil, ffsltNone);
Result := ModifyRecord(aRecord, true);
end
else
break;
end;
// end;
{End !!.01}
bcEngine.seTransactionCommit(bcDatabase);
except
{ Rollback if an exception occurs. }
bcEngine.seTransactionRollback(bcDatabase);
raise;
end;
finally
FFFreeMem(aRecord, RecLen);
SortEngine.Free;
end;
SetToBegin;
end;
{====================================================================}
{===TffSrCursor======================================================}
constructor TffSrCursor.Create(anEngine : TffServerEngine;
aDatabase : TffSrDatabase;
const aTimeout : Longint);
begin
bcTableClass := TffSrTable;
inherited Create(anEngine, aDatabase, aTimeout);
end;
{--------}
destructor TffSrCursor.Destroy;
begin
{ Notify extenders. }
NotifyExtenders(ffeaBeforeCursorClose, ffeaNoAction);
{Begin !!.02}
bcEngine.TableList.BeginRead;
try
{ If we exclusively opened the table then remove the mark from the
table. }
if bcExclOwner then begin
bcTable.SetExclOwner(ffc_W32NoValue);
bcExclOwner := False;
end;
{ Free the table locks held by the cursor. }
if assigned(bcTable) then
bcTable.RelLock(CursorID, True);
finally
bcEngine.TableList.EndRead;
end;
{End !!.02}
// if (bcRecordData <> nil) then {!!.01}
// FFFreeMem(bcRecordData, bcRecordLen); {!!.01}
if (bcRng1Key <> nil) then begin
FFFreeMem(bcRng1Key, scKeyLen);
bcRng1Key := nil;
end;
if (bcRng2Key <> nil) then begin
FFFreeMem(bcRng2Key, scKeyLen);
bcRng2Key := nil;
end;
if (bcCurKey <> nil) then begin
FFFreeMem(bcCurKey, scKeyLen);
bcCurKey := nil;
end;
bcFilter.Free;
bcFilter := nil;
bcFilterSav.Free;
bcFilterSav := nil;
inherited Destroy;
end;
{--------}
function TffSrCursor.AddIndexToTable(const aIndexDesc : TffIndexDescriptor) : TffResult;
begin
{ The cursor must have Exclusive Read-Write access. }
Result := bcCheckExclusiveReadWrite;
if Result <> DBIERR_NONE then
exit;
{ The index descriptor cannot be a user-defined index. }
if (aIndexDesc.idCount = -1) then begin
Result := DBIERR_INVALIDINDEXTYPE;
Exit;
end;
{ The index descriptor must be valid. }
if not Table.Dictionary.IsIndexDescValid(aIndexDesc) then begin
Result := DBIERR_INVALIDIDXDESC;
Exit;
end;
{ The index name cannot already exist. }
if (Table.Dictionary.GetIndexFromName(aIndexDesc.idName) <> -1) then begin
Result := DBIERR_INDEXEXISTS;
Exit;
end;
{ There must be room for the new index. }
if (Table.Dictionary.IndexCount = ffcl_MaxIndexes) then begin
Result := DBIERR_INDEXLIMIT;
Exit;
end;
{ Let the table do its stuff. }
Result := DBIERR_NONE;
AcqContentLock(ffclmWrite);
Table.AddIndex(aIndexDesc, Database.TransactionInfo)
end;
{--------}
procedure TffSrCursor.bcInit(const aOpenMode : TffOpenMode;
const aShareMode : TffShareMode;
const aExclContLock : Boolean); {!!.10}
begin
inherited bcInit(aOpenMode, aShareMode, aExclContLock); {!!.10}
{ Resolve any special build key and compare key routine links
(i.e., user-defined indexes) for the new table. }
{TffSrTable(bcTable).ResolveDynamicLinks;} {!!.06}
{ Get our work areas for the key. }
bcKID.kidCompareData := @bcCompareData;
scKeyLen := bcTable.Dictionary.IndexKeyLength[bcIndexID];
FFGetMem(bcCurKey, scKeyLen);
FFGetMem(bcRng1Key, scKeyLen);
FFGetMem(bcRng2Key, scKeyLen);
{ Initialise our key index data record. }
bcTable.MakeKIDForCursor(bcIndexID, bcKID);
{ Set up the position of the cursor to BOF. }
SetToBegin;
end;
{--------}
procedure TffSrCursor.bcTableOpenPreconditions(aTable : TffSrBaseTable;
const aIndexName : string;
var aIndexID : Longint;
const aOpenMode : TffOpenMode);
begin
{ Validate the index information; if the index name is non-blank
it must exist and will supercede the index number; if the index
name is blank the index number must exist}
if (aIndexName <> '') then begin
aIndexID := aTable.Dictionary.GetIndexFromName(aIndexName);
if (aIndexID = -1) then
FFRaiseException(EffException, ffStrResServer, fferrUnknownIndex,
[aTable.BaseName, aIndexName, aIndexID]);
end
else if (0 > aIndexID) or (aIndexID >= aTable.Dictionary.IndexCount) then
FFRaiseException(EffException, ffStrResServer, fferrUnknownIndex,
[aTable.BaseName, aIndexName, aIndexID]);
{ If the table's data file is open in read-only mode it means the
physical file is read-only: hence this call's openmode must be
read-only as well. }
if (aTable.Files[0]^.fiOpenMode = omReadOnly) and
(aOpenMode <> omReadOnly) then
FFRaiseException(EffException, ffStrResServer, fferrCursorReadOnly,
[aTable.BaseName]);
end;
{--------}
function TffSrCursor.CheckBookmark(aBookmark : PffByteArray) : TffResult;
var
CheckHash : Longint;
begin
Result := DBIERR_INVALIDBOOKMARK;
if (aBookmark = nil) then
Exit;
with PffSrBookmark(aBookmark)^ do begin
if (sbIndexID <> IndexID) then
Exit;
if (sbKeyLen <> scKeyLen) then
Exit;
CheckHash := FFCalcElfHash(sbIndexID,
ffcl_FixedBookmarkSize - sizeof(sbHash) + sbKeyLen);
if (sbHash <> CheckHash) then
Exit;
end;
Result := DBIERR_NONE;
end;
{--------}
procedure TffSrCursor.ClearIndex;
begin
with bcCompareData do begin
cdFldCnt := 0;
cdPartLen := 0;
end;
AcqContentLock(ffclmWrite);
FFTblDeleteAllKeys(Database.TransactionInfo, bcKID);
end;
{--------}
function TffSrCursor.CloneCursor(aOpenMode : TffOpenMode) : TffSrBaseCursor;
begin
{NOTE: we are not checking rights for this action because the client
had the rights to open the cursor}
{ Resolve the open mode. }
if (bcOpenMode = omReadOnly) then
aOpenMode := omReadOnly;
{ Create the cursor. }
Result := bcEngine.CursorClass.Create(bcEngine, {!!.06}
bcDatabase,
soTimeout);
Result.Open(bcTable.BaseName , '', bcIndexID, aOpenMode, smShared,
bcTable.IsServerTable, False, []);
AcqContentLock(ffclmRead);
try
{ Set up all of the position, range, etc, fields. }
Result.bcKID := bcKID;
Result.bcKID.kidCompareData := @Result.bcCompareData;
Result.bcCompareData := bcCompareData;
Result.bcInfo := bcInfo;
if bcInfo.KeyValid then
Move(bcCurKey^, Result.bcCurKey^, scKeyLen);
Result.bcHasRange := bcHasRange;
if bcHasRange then begin
Result.bcRng1Valid := bcRng1Valid;
if bcRng1Valid then begin
Move(bcRng1Key^, Result.bcRng1Key^, scKeyLen);
Result.bcRng1FldCnt := bcRng1FldCnt;
Result.bcRng1PtlLen := bcRng1PtlLen;
Result.bcRng1Incl := bcRng1Incl;
end;
Result.bcRng2Valid := bcRng2Valid;
if bcRng2Valid then begin
Move(bcRng2Key^, Result.bcRng2Key^, scKeyLen);
Result.bcRng2FldCnt := bcRng2FldCnt;
Result.bcRng2PtlLen := bcRng2PtlLen;
Result.bcRng2Incl := bcRng2Incl;
end;
end;
if Assigned(bcFilter) then
Result.SetFilter(bcFilter.Expression,bcFilter.Timeout);
finally
RelContentLock(ffclmRead);
end;
end;
{--------}
function TffSrCursor.CompareBookmarks(aBookmark1, aBookmark2 : PffByteArray;
var CmpResult : Longint) : TffResult;
var
BM1 : PffSrBookmark absolute aBookmark1;
BM2 : PffSrBookmark absolute aBookmark2;
begin
Result := CheckBookmark(aBookmark1);
if (Result = DBIERR_NONE) then
Result := CheckBookmark(aBookmark2);
if (Result <> DBIERR_NONE) then
Exit;
case BM1^.sbPos of
cpUnknown : CmpResult := -1;
cpBOF : if (BM2^.sbPos = cpBOF) then
CmpResult := 0
else
CmpResult := -1;
cpEOF : if (BM2^.sbPos = cpEOF) then
CmpResult := 0
else
CmpResult := 1;
else
{bookmark 1 is on a crack or on a record}
case BM2^.sbPos of
cpUnknown : CmpResult := 1;
cpBOF : CmpResult := 1;
cpEOF : CmpResult := -1;
else
{bookmark 2 is also on a crack or on a record}
{check the reference numbers, if equal the key ought to be}
if (ffCmpI64(BM1^.sbRefNr, BM2^.sbRefNr) = 0) then
CmpResult := 0
else begin
{compare the keys}
with bcCompareData do begin
cdFldCnt := 0;
cdPartLen := 0;
end;
CmpResult := Table.CompareKeysForCursor(bcKID,
PffByteArray(@BM1^.sbKey),
PffByteArray(@BM2^.sbKey));
if (CmpResult = 0) then
if (ffCmpI64(BM1^.sbRefNr, BM2^.sbRefNr) = -1) then
CmpResult := -1
else
CmpResult := 1;
end;
end;{case}
end;{case}
end;
{--------}
function TffSrCursor.DropIndexFromTable(const aIndexName : TffDictItemName;
aIndexID : Longint) : TffResult;
var
CompareData : TffCompareData;
KID : TffKeyIndexData;
begin
{if the index name is set, convert to an index ID}
if (aIndexName <> '') then
aIndexID := Table.Dictionary.GetIndexFromName(aIndexName);
{check the index number (count index 0 as invalid as well)}
if (aIndexID <= 0) or (aIndexID >= Table.Dictionary.IndexCount) then begin
Result := DBIERR_NOSUCHINDEX;
Exit;
end;
{the index number cannot be our index number}
if (aIndexID = IndexID) then begin
Result := DBIERR_ACTIVEINDEX;
Exit;
end;
{ The cursor must have Exclusive Read-Write access. }
Result := bcCheckExclusiveReadWrite;
if Result <> DBIERR_NONE then
exit;
{ Delete all the keys and then drop the index. }
Result := DBIERR_NONE;
KID.kidCompareData := @CompareData;
Table.MakeKIDForCursor(aIndexID, KID);
AcqContentLock(ffclmWrite);
FFTblDeleteAllKeys(Database.TransactionInfo, KID);
Table.DropIndex(Database.TransactionInfo, aIndexID);
end;
{--------}
function TffSrCursor.ExtractKey(aData : PffByteArray; aKey : PffByteArray) : TffResult;
begin
Result := DBIERR_NOCURRREC;
if (aData = nil) and (bcInfo.pos = cpOnRecord) then begin
aData := bcRecordData;
AcqContentLock(ffclmRead);
try
Table.GetRecord(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, aData, {!!.10}
ffsltNone, false, false); {!!.02}
finally
RelContentLock(ffclmRead);
end;
if Assigned(bcFilter) then
if not bcFilter.MatchesRecord(aData) then
aData := nil;
end;
if (aData <> nil) then begin
Result := Table.BuildKeyForRecord(IndexID, aData, aKey, 0, 0);
end;
end;
{--------}
function TffSrCursor.GetBookmark(aBookmark : PffByteArray) : TffResult;
begin
Result := DBIERR_NONE;
AcqContentLock(ffclmRead);
try
FillChar(PffSrBookmark(aBookmark)^, ffcl_FixedBookmarkSize, 0);
with PffSrBookmark(aBookmark)^ do begin
sbIndexID := IndexID;
sbPos := bcInfo.pos;
sbKeyValid := bcInfo.KeyValid;
sbRefNr := bcInfo.refNr;
sbKeyLen := scKeyLen;
if bcInfo.KeyValid then
Move(bcCurKey^, sbKey, scKeyLen)
else
FillChar(sbKey, scKeyLen, 0);
sbHash := FFCalcElfHash(sbIndexID,
ffcl_FixedBookmarkSize - sizeof(sbHash) + sbKeyLen);
end;
finally
RelContentLock(ffclmRead);
end;
end;
{--------}
function TffSrCursor.GetBookmarkSize : integer;
begin
Result := ffcl_FixedBookmarkSize + scKeyLen;
end;
{--------}
function TffSrCursor.GetNextRecord(aData : PffByteArray;
aLockType : TffSrLockType) : TffResult;
var
KeyCompareResult : integer;
Action : TffSearchKeyAction;
begin
{ If we are at EOF, then obviously there's no next record. }
if (bcInfo.pos = cpEOF) then begin
Result := DBIERR_EOF;
Exit;
end;
AcqContentLock(ffclmRead);
bcInfoLock.Lock; {!!.06}
try
{ If our position is at BOF and we have a range active, position the
index at the start of the range}
if (bcInfo.pos = cpBOF) and bcHasRange and bcRng1Valid then begin
{ Position at start of range. }
if bcRng1Incl then
Action := skaGreaterEqual
else
Action := skaGreater;
{ Note: the following call will always return true in this case. }
Move(bcRng1Key^, bcCurKey^, scKeyLen);
with bcCompareData do begin
cdFldCnt := bcRng1FldCnt;
cdPartLen := bcRng1PtlLen;
end;
Table.FindKey(bcKID, bcInfo.refNr, Database.TransactionInfo,
bcCurKey, bcInfo.KeyPath, Action);
{ Is the keypath positioned at EOF? }
if (bcInfo.KeyPath.kpPos = kppEOF) then begin
{Yes. The start of the range is at EOF, so it's not likely we'll find a
'next record <g>. }
Result := DBIERR_EOF;
SetToEnd;
Exit;
end;
{ Make sure the keypath is on the crack before the key so that the next
key call returns the right record. }
if (bcInfo.KeyPath.kpPos = kppOnKey) then begin
Assert(bcInfo.keyPath.kpCount > 0);
bcInfo.KeyPath.kpPos := kppOnCrackBefore;
end;
end
{ Otherwise do we need to rebuild the key path? }
else if (not bcIsCurKeyPathValid) then
bcRebuildKeyPath; {!!.05}
{ Make sure that we have somewhere to read the record into. }
if (aData = nil) then
aData := bcRecordData;
{ Get the next record. }
with bcCompareData do begin
cdFldCnt := 0;
cdPartLen := 0;
end;
if Assigned(bcFilter) then
bcFilter.BeginTimeout;
repeat
Result := bcTable.GetNextRecord(bcDatabase.TransactionInfo,
bcDatabase.DatabaseID, {!!.10}
CursorID, bcKID, bcInfo.refNr, bcCurKey,
bcInfo.KeyPath, aData, aLockType);
if (Result <> DBIERR_NONE) then begin
if (Result = DBIERR_EOF) then
SetToEnd;
Exit;
end;
{in theory we're on a record}
bcInfo.Deleted := False;
bcInfo.KeyValid := True;
bcInfo.pos := cpOnRecord;
{check that we're in range if required}
if bcHasRange and bcRng2Valid then begin
{check whether beyond end of range}
with bcCompareData do begin
cdFldCnt := bcRng2FldCnt;
cdPartLen := bcRng2PtlLen;
end;
KeyCompareResult := bcTable.CompareKeysForCursor(bcKID, bcCurKey, bcRng2Key);
if (KeyCompareResult > 0) or
((KeyCompareResult = 0) and (not bcRng2Incl)) then begin
Result := DBIERR_EOF;
SetToEnd;
end;
end;
{Begin !!.03}
// until (Result <> DBIERR_NONE) or not Assigned(bcFilter) or
// bcFilter.MatchesRecord(aData) or bcFilter.CheckTimeout(Result);
until (Result <> DBIERR_NONE) or ((not Assigned(bcFilter) or
bcFilter.MatchesRecord(aData) or bcFilter.CheckTimeout(Result)) and
(not Assigned(bcFilterSav) or bcFilterSav.MatchesRecord(aData)));
{End !!.03}
{ Place the lock if needed... record will not be read again}
{Begin !!.02}
if (Result = DBIERR_NONE) then begin
if aData <> bcRecordData then
Move(aData^, bcRecordData^, bcRecordLen);
if (aLockType <> ffsltNone) then
Result := bcTable.GetRecord(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, {!!.10}
bcInfo.refNr, nil, aLockType, false, false); {!!.02}
end;
{End !!.02}
finally
bcInfoLock.Unlock; {!!.06}
RelContentLock(ffclmRead);
end;
end;
{--------}
function TffSrCursor.GetPriorRecord(aData : PffByteArray; aLockType : TffSrLockType) : TffResult;
var
KeyCompareResult : integer;
Action : TffSearchKeyAction;
begin
{ If we are at BOF, then obviously there's no prior record. }
if (bcInfo.pos = cpBOF) then begin
Result := DBIERR_BOF;
Exit;
end;
AcqContentLock(ffclmRead);
bcInfoLock.Lock; {!!.06}
try
{ If our position is at EOF and we have a range active, position the
index at the end of the range. }
if (bcInfo.pos = cpEOF) and bcHasRange and bcRng2Valid then begin
{ Position at end of range. }
if bcRng2Incl then
Action := skaGreater
else
Action := skaGreaterEqual;
{ Note: the following call will always return true in this case. }
Move(bcRng2Key^, bcCurKey^, scKeyLen);
with bcCompareData do begin
cdFldCnt := bcRng2FldCnt;
cdPartLen := bcRng2PtlLen;
end;
bcTable.FindKey(bcKID, bcInfo.refNr, bcDatabase.TransactionInfo,
bcCurKey, bcInfo.KeyPath, Action);
end
{ Otherwise, do we need to rebuild the key path? }
else if (not bcIsCurKeyPathValid) then
bcRebuildKeyPath; {!!.05}
{ Make sure that we have somewhere to read the record into. }
if (aData = nil) then
aData := bcRecordData;
{ Get the previous record. }
with bcCompareData do begin
cdFldCnt := 0;
cdPartLen := 0;
end;
if Assigned(bcFilter) then
bcFilter.BeginTimeout;
repeat
Result := bcTable.GetPriorRecord(bcDatabase.TransactionInfo,
bcDatabase.DatabaseID, {!!.10}
CursorID, bcKID,
bcInfo.refNr, bcCurKey,
bcInfo.KeyPath, aData, ffsltNone);
if (Result <> DBIERR_NONE) then begin
if (Result = DBIERR_BOF) then
SetToBegin;
Exit;
end;
{ In theory we're on a record. }
bcInfo.Deleted := false;
bcInfo.KeyValid := true;
bcInfo.pos := cpOnRecord;
{ Check that we're in range if required. }
if bcHasRange and bcRng1Valid then begin
{check whether beyond start of range}
with bcCompareData do begin
cdFldCnt := bcRng1FldCnt;
cdPartLen := bcRng1PtlLen;
end;
KeyCompareResult := bcTable.CompareKeysForCursor(bcKID, bcCurKey, bcRng1Key);
if (KeyCompareResult < 0) or
((KeyCompareResult = 0) and (not bcRng1Incl)) then begin
Result := DBIERR_BOF;
SetToBegin;
end;
end;
until (Result <> DBIERR_NONE) or not Assigned(bcFilter) or
bcFilter.MatchesRecord(aData) or bcFilter.CheckTimeout(Result);
{ Place the lock if needed... record will not be read again. }
{Begin !!.02}
if (Result = DBIERR_NONE) then begin
if aData <> bcRecordData then
Move(aData^, bcRecordData^, bcRecordLen);
if (aLockType <> ffsltNone) then
Result := bcTable.GetRecord(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, {!!.10}
bcInfo.refNr, nil, aLockType, false, false); {!!.02}
end;
{End !!.02}
finally
bcInfoLock.Unlock; {!!.06}
RelContentLock(ffclmRead);
end;
end;
{--------}
function TffSrCursor.GetRecordCount(var aRecCount : Longint) : TffResult;
var
Action : TffSearchKeyAction;
KeyCompareResult : integer;
SavedKey : PffByteArray;
Info : TffRecordInfo;
begin
Result := DBIERR_NONE;
AcqContentLock(ffclmRead);
try
if bcHasRange or Assigned(bcFilter) then begin
{set count to zero}
aRecCount := 0;
{save the current position}
bcSaveCurInfo;
FFGetMem(SavedKey, bcKID.kidCompareData^.cdKeyLen); {!!.06}
try
Move(bcCurKey^, SavedKey^, bcKID.kidCompareData^.cdKeyLen);
{BOF}
SetToBegin;
if bcHasRange and bcRng1Valid then begin
{position at start of range}
if bcRng1Incl then
Action := skaGreaterEqual
else
Action := skaGreater;
{note: the following FindKey call will always return true in
this case}
Move(bcRng1Key^, bcCurKey^, scKeyLen);
with bcCompareData do begin
cdFldCnt := bcRng1FldCnt;
cdPartLen := bcRng1PtlLen;
end;
Table.FindKey(bcKID, bcInfo.refNr, Database.TransactionInfo, bcCurKey,
bcInfo.KeyPath, Action);
{check whether the keypath was positioned at EOF, if so the
start of the range is at EOF, so it's not likely we'll find a
'next' key or any keys at all <g>}
if (bcInfo.KeyPath.kpPos = kppEOF) then begin
{note the reset of the cursor position still occurs}
Exit;
end;
{make sure that the keypath is on the crack before the key so that
the next key call in a minute returns the right record}
if (bcInfo.KeyPath.kpPos = kppOnKey) then begin
Assert(bcInfo.KeyPath.kpCount > 0);
bcInfo.KeyPath.kpPos := kppOnCrackBefore;
end;
end;
{while not EOF or other error do}
while (Result = DBIERR_NONE) do begin
{Begin !!.05}
{ Check for timeout. }
if FFGetRetry < GetTickCount then
FFRaiseExceptionNoData(EffServerException, ffStrResServer,
fferrGeneralTimeout);
{End !!.05}
{readnext key}
Result := Table.GetNextKey(bcKID, bcInfo.refNr, Database.TransactionInfo,
bcCurKey, bcInfo.KeyPath);
if (Result = DBIERR_NONE) then begin
{check that we're in range if required}
if bcHasRange and bcRng2Valid then begin
{check whether beyond end of range}
with bcCompareData do begin
cdFldCnt := bcRng2FldCnt;
cdPartLen := bcRng2PtlLen;
end;
KeyCompareResult :=
Table.CompareKeysForCursor(bcKID, bcCurKey, bcRng2Key);
if (KeyCompareResult > 0) or
((KeyCompareResult = 0) and (not bcRng2Incl)) then begin
Result := DBIERR_EOF;
end
else {key is in range} begin
if Assigned(bcFilter) then begin
Table.GetRecord(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, {!!.10}
bcRecordData, ffsltNone, false, false); {!!.02}
if bcFilter.MatchesRecord(bcRecordData) then
inc(aRecCount);
end else
inc(aRecCount);
end;
end
else {end of range = end of index path} begin
if Assigned(bcFilter) then begin
Table.GetRecord(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, {!!.10}
bcRecordData, ffsltNone, false, false); {!!.02}
if bcFilter.MatchesRecord(bcRecordData) then
inc(aRecCount);
end else
inc(aRecCount);
end;
end;
end;
Result := DBIERR_NONE;
{endwhile}
finally
{reset current position}
bcRestoreCurInfo;
Move(SavedKey^, bcCurKey^, bcKID.kidCompareData^.cdKeyLen);
FFFreeMem(SavedKey, bcKID.kidCompareData^.cdKeyLen); {!!.06}
end;
end
else begin
FFTblGetRecordInfo(Table.Files[0], Database.TransactionInfo, Info);
aRecCount := Info.riRecCount;
end;
finally
RelContentLock(ffclmRead);
end;
end;
{--------}
function TffSrCursor.GetRecordForKey(aDirectKey : boolean;
aFieldCount : integer;
aPartialLen : integer;
aKeyData : PffByteArray;
aData : PffByteArray;
aFirstCall : Boolean) : TffResult;
var
Action : TffSearchKeyAction;
aTI : PffTransInfo;
RecFound : boolean;
KeyToFind : PffByteArray;
TmpCompareData : TffCompareData; {!!.11}
begin
{calculate the key}
if aDirectKey then
Move(aKeyData^, bcCurKey^, scKeyLen)
else if (IndexID = 0) then begin
Result := DBIERR_INVALIDINDEXTYPE;
Exit;
end
else begin
Result := Table.BuildKeyForRecord(IndexID, aKeyData, bcCurKey, aFieldCount,
aPartialLen);
if (Result <> DBIERR_NONE) then
Exit;
end;
AcqContentLock(ffclmRead);
bcInfoLock.Lock; {!!.06}
try
{now position the index on that exact key or the one that partially
matches it}
if aFirstCall then begin
FFInitKeyPath(bcInfo.KeyPath);
bcInfo.refNr.iLow := 0;
bcInfo.refNr.iHigh := 0;
bcInfo.Deleted := false;
end;
Action := skaEqual;
{try to find the exact or partial key}
with bcCompareData do begin
cdFldCnt := aFieldCount;
cdPartLen := aPartialLen;
end;
Result := DBIERR_NONE;
aTI := Database.TransactionInfo;
KeyToFind := nil;
try
// we need a copy of the key
if Assigned(bcFilter) or (not aFirstCall) then begin
FFGetMem(KeyToFind, scKeyLen);
Move(bcCurKey^, KeyToFind^, scKeyLen)
end;
if Assigned(bcFilter) then begin
if aData = nil then
aData := bcRecordData;
bcFilter.BeginTimeout;
end;
repeat
if aFirstCall then begin
RecFound := Table.FindKey(bcKID, bcInfo.refNr, aTI,
bcCurKey, bcInfo.KeyPath, Action);
aFirstCall := False;
end else begin
RecFound := (Table.GetNextKey(bcKID, bcInfo.refNr, aTI,
bcCurKey, bcInfo.KeyPath) = DBIERR_NONE) and
(Table.CompareKeysForCursor(bcKID, bcCurKey, KeyToFind) = 0);
end;
if RecFound then begin
TmpCompareData := bcCompareData; {!!.11}
if IsInRange(bcCurKey) <> 0 then begin {!!.11}
bcCompareData := TmpCompareData; {!!.11}
Result := DBIERR_RECNOTFOUND; {!!.11}
end else begin {!!.11}
bcCompareData := TmpCompareData; {!!.11}
if Assigned(aData) then
Table.GetRecord(aTI, bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, aData, ffsltNone,{!!.10}
false, false); {!!.02}
bcInfo.KeyValid := true;
bcInfo.pos := cpOnRecord;
end
end else
Result := DBIERR_RECNOTFOUND;
until (Result <> DBIERR_NONE) or {!!.11}
((not Assigned(bcFilter)) or {!!.11}
bcFilter.MatchesRecord(aData) or {!!.11}
bcFilter.CheckTimeout(Result)); {!!.11}
{ If we didn't find the key then set to the end of the dataset. }
if Result = DBIERR_RECNOTFOUND then
SetToEnd;
finally
if Assigned(KeyToFind) then
FFFreeMem(KeyToFind, scKeyLen);
end;
finally
bcInfoLock.Unlock; {!!.06}
RelContentLock(ffclmRead);
end;
end;
{--------}
function TffSrCursor.InsertRecord(aData : PffByteArray;
aLockType : TffSrLockType) : TffResult;
var
NewRefNr : TffInt64;
SavInfo : TffSrCursorInfo; {!!.12}
SavKey : PffByteArray; {!!.12}
begin
{ Notify extenders. }
bcNewRecBuff := aData;
SavKey := nil; {!!.12}
try
Result := NotifyExtenders(ffeaBeforeRecInsert, ffeaInsertRecFail);
if Result = DBIERR_NONE then begin
AcqContentLock(ffclmWrite);
Result := bcTable.InsertRecord(bcDatabase.TransactionInfo,
CursorID, aData, aLockType, NewRefNr);
if (Result = DBIERR_NONE) then begin
{Begin !!.12}
{ If a range is active then save the current key & cursor information.
We may need to reposition the cursor to its original position if
the inserted record does not fit into the range. }
if bcHasRange then begin
FFGetMem(SavKey, scKeyLen);
Move(bcCurKey^, SavKey^, scKeyLen);
SavInfo := bcInfo;
end;
FFInitKeyPath(bcInfo.KeyPath);
bcInfo.pos := cpOnRecord;
bcInfo.refNr := NewRefNr;
bcInfo.Deleted := false;
scRebuildCurKey(aData, true);
if bcHasRange and (IsInRange(bcCurKey) <> 0) then begin
bcInfo := SavInfo;
Move(SavKey^, bcCurKey^, scKeyLen);
end;
{End !!.12}
bcTable.RelaxRecordLock(bcDatabase.TransactionInfo, CursorID, {!!.10}
bcInfo.RefNr); {!!.10}
{ Notify extenders of successful insert. }
NotifyExtenders(ffeaAfterRecInsert, ffeaNoAction);
end else
{ Notify extenders of failed insert. }
NotifyExtenders(ffeaInsertRecFail, ffeaNoAction);
end;
finally
if SavKey <> nil then {!!.12}
FFFreeMem(SavKey, scKeyLen); {!!.12}
bcNewRecBuff := nil;
end;
end;
{--------}
function TffSrCursor.InsertRecordNoDefault(aData : PffByteArray; {!!.10}
aLockType : TffSrLockType) : TffResult;
var
NewRefNr : TffInt64;
begin
{ Notify extenders. }
bcNewRecBuff := aData;
try
Result := NotifyExtenders(ffeaBeforeRecInsert, ffeaInsertRecFail);
if Result = DBIERR_NONE then begin
AcqContentLock(ffclmWrite);
Result := bcTable.InsertRecordNoDefault(bcDatabase.TransactionInfo,{!!.10}
CursorID, aData, aLockType,
NewRefNr);
if (Result = DBIERR_NONE) then begin
FFInitKeyPath(bcInfo.KeyPath);
bcInfo.pos := cpOnRecord;
bcInfo.refNr := NewRefNr;
bcInfo.Deleted := false;
scRebuildCurKey(aData, true);
bcTable.RelaxRecordLock(bcDatabase.TransactionInfo, CursorID, {!!.10}
bcInfo.RefNr); {!!.10}
{ Notify extenders of successful insert. }
NotifyExtenders(ffeaAfterRecInsert, ffeaNoAction);
end else
{ Notify extenders of failed insert. }
NotifyExtenders(ffeaInsertRecFail, ffeaNoAction);
end;
finally
bcNewRecBuff := nil;
end;
end;
{--------}
function TffSrCursor.IsInRange(aKey : PffByteArray) : integer;
var
KeyCompareResult : integer;
begin
Result := 0;
if not bcHasRange then
Exit;
if bcRng1Valid then begin
with bcCompareData do begin
cdFldCnt := bcRng1FldCnt;
cdPartLen := bcRng1PtlLen;
end;
KeyCompareResult := Table.CompareKeysForCursor(bcKID, aKey, bcRng1Key);
if (KeyCompareResult < 0) then begin
Result := -1;
Exit;
end;
if (KeyCompareResult = 0) then begin
if not bcRng1Incl then
Result := -1;
Exit;
end;
end;
if bcRng2Valid then begin
with bcCompareData do begin
cdFldCnt := bcRng2FldCnt;
cdPartLen := bcRng2PtlLen;
end;
KeyCompareResult := Table.CompareKeysForCursor(bcKID, aKey, bcRng2Key);
if (KeyCompareResult > 0) then begin
Result := 1;
Exit;
end;
if (KeyCompareResult = 0) then begin
if not bcRng2Incl then
Result := 1;
Exit;
end;
end;
end;
{--------}
function TffSrCursor.ModifyRecord(aData : PffByteArray; aRelLock : boolean) : TffResult;
var {!!.05}
aKeyChanged : Boolean; {!!.05}
SavKey : PffByteArray; {!!.05}
begin
{ Note: By this time, any other cursor deleting or modifying the record ahead
of us has completed and has set bcInfo.Deleted. We can be assured of this
because TffServerEngine.RecordDelete calls Cursor.EnsureWritable(true) which
obtains a lock on the record to be deleted. We won't get that lock until
the other cursor has finished. }
{ Has this record already been deleted? }
if bcInfo.Deleted then begin
{ Yes. }
Result := DBIERR_KEYORRECDELETED;
Exit;
end;
{ Are we on a record? }
if (bcInfo.Pos <> cpOnRecord) then begin
{ No. }
case bcInfo.Pos of
cpBOF : Result := DBIERR_BOF;
cpEOF : Result := DBIERR_EOF;
else
Result := DBIERR_NOCURRREC;
end;
Exit;
end;
{ Notify extenders. }
FFGetMem(bcOldRecBuff, bcRecordLen); {!!.02}
bcNewRecBuff := aData;
try
Move(bcRecordData^, bcOldRecBuff^, bcRecordLen); {!!.02}
Result := NotifyExtenders(ffeaBeforeRecUpdate, ffeaUpdateRecFail);
if Result = DBIERR_NONE then begin
AcqContentLock(ffclmWrite);
{Begin !!.05}
Result := bcTable.PutRecord(bcDatabase.TransactionInfo, CursorID,
bcInfo.refNr, aData, aRelLock, aKeyChanged);
if (Result = DBIERR_NONE) then begin
bcTable.RelaxRecordLock(bcDatabase.TransactionInfo, CursorID, {!!.10}
bcInfo.RefNr); {!!.10}
{ Was the key for the current index changed? }
SavKey := nil;
if aKeyChanged then begin
{ Yes. Save the current key & rebuild it so that we may reposition to
the record. }
FFGetMem(SavKey, scKeyLen);
try
Move(bcCurKey^, SavKey^, scKeyLen);
scRebuildCurKey(aData, true);
{ Does the new key fall outside of the current range? }
if IsInRange(bcCurKey) <> 0 then
{ Yes. Restore the old key. The cursor will be repositioned to the
next record. }
Move(SavKey^, bcCurKey^, scKeyLen);
finally
FFFreeMem(SavKey, scKeyLen);
end;
end;
FFInitKeyPath(bcInfo.KeyPath);
bcInfo.pos := cpOnRecord;
bcRebuildKeyPath;
{End !!.05}
{ Notify extenders of successful update. }
NotifyExtenders(ffeaAfterRecUpdate, ffeaNoAction);
end else
{ Notify extenders of failed update. }
NotifyExtenders(ffeaUpdateRecFail, ffeaNoAction);
end;
finally
FFFreeMem(bcOldRecBuff, bcRecordLen); {!!.02}
bcOldRecBuff := nil;
bcNewRecBuff := nil;
end;
end;
{--------}
procedure TffSrCursor.ResetRange;
begin
bcHasRange := false;
end;
{--------}
procedure TffSrCursor.scRebuildCurKey(aRecData : PffByteArray;
aLockObtained : boolean);
begin
bcInvalidateCurKey;
if (IndexID = 0) then begin
Move(bcInfo.refNr, bcCurKey^, scKeyLen);
bcInfo.KeyValid := true;
end
else begin
AcqContentLock(ffclmRead);
try
{ If we have been passed the record buffer then use it otherwise
read the record from the file. }
if assigned(aRecData) then
Move(aRecData^, bcRecordData^, Table.Files[0]^.fiRecordLength)
else
Table.GetRecord(bcDatabase.TransactionInfo, {!!.10}
bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, {!!.10}
bcRecordData, ffsltNone, aLockObtained, false); {!!.02}
{calculate the key for this record}
bcInfo.KeyValid :=
(Table.BuildKeyForRecord(IndexID, bcRecordData, bcCurKey, 0, 0) = DBIERR_NONE);
finally
RelContentLock(ffclmRead);
end;
end;
end;
{--------}
procedure TffSrBaseCursor.bcRebuildKeyPath; {!!.05 - Moved from TffSrCursor.scRebuildKeyPath}
var
InRangeResult : Integer;
begin
{ Assumption: Content read lock already obtained. }
{ If we have a valid key, calculate the actual key path by finding that key
within the current index. }
if bcIsCurKeyValid then begin
FFInitKeyPath(bcInfo.KeyPath);
with bcCompareData do begin
cdFldCnt := 0;
cdPartLen := 0;
end;
if Table.FindKey(bcKID, bcInfo.refNr, bcDatabase.TransactionInfo, bcCurKey,
bcInfo.KeyPath, skaGreaterEqual) then begin
{ Does the key fit within the current range? }
InRangeResult := IsInRange(bcCurKey);
if InRangeResult <> 0 then
bcInfo.pos := cpOnCrack
else
{ Make sure that the current position is set to reflect the
keypath's position. }
case bcInfo.KeyPath.kpPos of
kppBOF : SetToBegin;
kppOnCrackBefore,
kppOnCrackAfter : bcInfo.pos := cpOnCrack;
kppEOF : SetToEnd;
end;{case}
end; { if }
end;
end;
{--------}
function TffSrCursor.SetRange(aDirectKey : boolean;
aFieldCount1 : integer;
aPartialLen1 : integer;
aKeyData1 : PffByteArray;
aKeyIncl1 : boolean;
aFieldCount2 : integer;
aPartialLen2 : integer;
aKeyData2 : PffByteArray;
aKeyIncl2 : boolean) : TffResult;
begin
Result := DBIERR_NONE;
{we now have a range}
bcRng1Valid := (aKeyData1 <> nil);
bcRng2Valid := (aKeyData2 <> nil);
{calculate the keys}
if aDirectKey then begin
if bcRng1Valid then
Move(aKeyData1^, bcRng1Key^, scKeyLen);
if bcRng2Valid then
Move(aKeyData2^, bcRng2Key^, scKeyLen);
end
else begin
if bcRng1Valid then
Result := Table.BuildKeyForRecord(IndexID, aKeyData1, bcRng1Key,
aFieldCount1, aPartialLen1);
if (Result = DBIERR_NONE) and bcRng2Valid then
Result := Table.BuildKeyForRecord(IndexID, aKeyData2, bcRng2Key,
aFieldCount2, aPartialLen2);
if (Result <> DBIERR_NONE) then
Exit;
end;
{store the other fields}
if bcRng1Valid then begin
bcRng1FldCnt := aFieldCount1;
bcRng1PtlLen := aPartialLen1;
bcRng1Incl := aKeyIncl1;
end;
if bcRng2Valid then begin
bcRng2FldCnt := aFieldCount2;
bcRng2PtlLen := aPartialLen2;
bcRng2Incl := aKeyIncl2;
end;
{position ourselves at BOF}
SetToBegin;
bcHasRange := true;
end;
{--------}
procedure TffSrCursor.SetToBegin;
begin
AcqContentLock(ffclmRead);
try
bcInfo.pos := cpBOF;
FFSetKeyPathToBOF(bcInfo.KeyPath);
bcInvalidateCurKey;
ffInitI64(bcInfo.refNr);
bcInfo.Deleted := false;
finally
RelContentLock(ffclmRead);
end;
end;
{--------}
function TffSrCursor.SetToBookmark(aBookmark : PffByteArray) : TffResult;
begin
Result := CheckBookmark(aBookmark);
if (Result = DBIERR_NONE) then begin
{ Requirement: The cursor must be on the same index as the bookmark. }
if IndexID <> PffSrBookmark(aBookmark)^.sbIndexID then begin
Result := DBIERR_INVALIDBOOKMARK;
exit;
end;
AcqContentLock(ffclmRead);
bcInfoLock.Lock; {!!.06}
try
{ Initialize the key path. }
FFInitKeyPath(bcInfo.KeyPath);
with PffSrBookmark(aBookmark)^ do begin
bcInfo.pos := sbPos;
bcInfo.refNr := sbRefNr;
bcInfo.KeyValid := sbKeyValid;
bcInfo.Deleted := false;
if sbKeyValid then
Move(sbKey, bcCurKey^, sbKeyLen);
try
{ See if the record still exists by rebuilding the key path. }
bcRebuildKeyPath; {!!.05}
{ Does the record still exist? }
if (ffCmpI64(bcInfo.refNr, sbRefNr) <> 0) then begin
{ No. Position the cursor to the crack before the record. }
bcInfo.pos := cpOnCrack;
bcInfo.refNr := sbRefNr;
if (bcInfo.KeyPath.kpPos = kppOnKey) then begin
Assert(bcInfo.KeyPath.kpCount > 0);
bcInfo.KeyPath.kpPos := kppOnCrackBefore;
end;
bcInfo.Deleted := true;
end;
except
SetToBegin;
Result := DBIERR_INVALIDBOOKMARK;
end;
end;
finally
bcInfoLock.Unlock; {!!.06}
RelContentLock(ffclmRead);
end;
end;
end;
{--------}
function TffSrCursor.SetToCursor(aCursor : TffSrBaseCursor) : TffResult;
var
InRangeResult : integer;
begin
Result := DBIERR_NONE;
if (aCursor.Table <> Table) then begin
Result := DBIERR_DIFFERENTTABLES;
Exit;
end;
AcqContentLock(ffclmRead);
try
{ If the cursors are using the same index, copy over the source cursor's
information as is.}
if (aCursor.IndexID = IndexID) then begin
bcInfo := aCursor.bcInfo;
if bcInfo.KeyValid then
Move(aCursor.bcCurKey^, bcCurKey^, scKeyLen);
{ If this cursor has a range applied and the record to which it is
positioning does not fit within the range, position the cursor on crack. }
if (bcInfo.pos in [cpOnRecord, cpOnCrack]) and bcInfo.KeyValid then begin
InRangeResult := IsInRange(bcCurKey);
if InRangeResult <> 0 then
aCursor.bcInfo.Pos := cpOnCrack;
end;
end
else begin
{ Otherwise, the cursor's are on different indices. }
{ If the source cursor is not on a record then return an error. This
could happen if the source cursor was not originally on a record or
the record has been deleted by the time we were granted a lock on the
record. }
if (aCursor.bcInfo.pos <> cpOnRecord) then begin
Result := DBIERR_NOCURRREC;
Exit;
end;
{ Otherwise, position this cursor to the same record as the source cursor.
We can use the source cursor's refNr as is. We don't need to figure out
the key path. However, we do need to rebuild this cursor's key based
upon the current index. }
bcInfo.pos := cpOnRecord;
bcInfo.refNr := aCursor.bcInfo.refNr;
FFInitKeyPath(bcInfo.KeyPath);
scRebuildCurKey(nil, true);
bcRebuildKeyPath; {!!.05}
end;
finally
RelContentLock(ffclmRead);
end;
end;
{--------}
procedure TffSrCursor.SetToEnd;
begin
AcqContentLock(ffclmRead);
try
bcInfo.pos := cpEOF;
FFSetKeyPathToEOF(bcInfo.KeyPath);
bcInvalidateCurKey;
ffInitI64(bcInfo.refNr);
bcInfo.Deleted := false;
finally
RelContentLock(ffclmRead);
end;
end;
{--------}
function TffSrCursor.SetToKey(aSearchAction : TffSearchKeyAction;
aDirectKey : boolean;
aFieldCount : integer;
aPartialLen : integer;
aKeyData : PffByteArray) : TffResult;
var
aTI : PffTransInfo;
InRangeResult : integer;
begin
{calculate the key}
if aDirectKey then
Move(aKeyData^, bcCurKey^, scKeyLen)
else begin
Result := Table.BuildKeyForRecord(IndexID, aKeyData, bcCurKey,
aFieldCount, aPartialLen);
if (Result <> DBIERR_NONE) then
Exit;
end;
AcqContentLock(ffclmRead);
bcInfoLock.Lock; {!!.06}
try
{now position the index on that key or the one that partially
matches it}
FFInitKeyPath(bcInfo.KeyPath);
ffInitI64(bcInfo.refNr);
bcInfo.Deleted := false;
aTI := Database.TransactionInfo;
{try to find the key according to the search action}
with bcCompareData do begin
cdFldCnt := aFieldCount;
cdPartLen := aPartialLen;
end;
if Table.FindKey(bcKID, bcInfo.refNr, aTI, bcCurKey, bcInfo.KeyPath,
aSearchAction) then begin
{we found it}
Result := DBIERR_NONE;
{if we're at EOF, set all current key variables and exit}
if (bcInfo.KeyPath.kpPos = kppEOF) then begin
SetToEnd;
Exit;
end;
{but did we? better see whether we're in the current range}
InRangeResult := IsInRange(bcCurKey);
{the key we found is before the start of the range: position
ourselves at BOF, and only signal an error if the search action
was "search for equal"}
if (InRangeResult < 0) then begin
if aSearchAction = skaEqual then
Result := DBIERR_RECNOTFOUND;
SetToBegin;
Exit;
end;
{the key we found is after the end of the range: position
ourselves at EOF, and only signal an error if the search action
was "search for equal"}
if (InRangeResult > 0) then begin
if aSearchAction = skaEqual then
Result := DBIERR_RECNOTFOUND;
SetToEnd;
Exit;
end;
if Assigned(bcFilter) then begin
Table.GetRecord(aTI, bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, bcRecordData, ffsltNone, {!!.10}
false, false); {!!.02}
if not bcFilter.MatchesRecord(bcRecordData) then begin
if aSearchAction = skaEqual then
Result := DBIERR_RECNOTFOUND
else begin {begin !!.11}
repeat
Result := bcTable.GetNextRecord(aTI,
bcDatabase.DatabaseID,
CursorID, bcKID, bcInfo.refNr,
bcCurKey, bcInfo.KeyPath, bcRecordData,
ffsltNone);
if (Result <> DBIERR_NONE) then begin
if (Result = DBIERR_EOF) then
SetToEnd;
Exit;
end;
{in theory we're on a record}
bcInfo.Deleted := False;
bcInfo.KeyValid := True;
bcInfo.pos := cpOnRecord;
until (Result <> DBIERR_NONE) or
(not Assigned(bcFilter) or bcFilter.MatchesRecord(bcRecordData)
or bcFilter.CheckTimeout(Result));
end; {end !!.11}
if Result = DBIERR_FF_FilterTimeout then
Exit;
if Result <> DBIERR_NONE then begin
SetToEnd;
Exit;
end;
end;
end;
{SetToKey is supposed to leave the position on the crack before
the record, so make sure}
bcInfo.KeyValid := true;
bcInfo.pos := cpOnCrack;
if (bcInfo.KeyPath.kpPos = kppOnKey) then begin
Assert(bcInfo.KeyPath.kpCount > 0);
bcInfo.KeyPath.kpPos := kppOnCrackBefore;
end;
end
else {we didn't find the key} begin
{if the search action was "search for equal", signal an error and
position ourselves at BOF}
if aSearchAction = skaEqual then begin
Result := DBIERR_RECNOTFOUND;
SetToBegin;
Exit;
end;
{otherwise we're fine}
Result := DBIERR_NONE;
{if we're at EOF, set all current key variables and exit}
if (bcInfo.KeyPath.kpPos = kppEOF) then begin
SetToEnd;
Exit;
end;
{check whether we're in the current range or not}
InRangeResult := IsInRange(bcCurKey);
if InRangeResult <> 0 then begin
bcInfo.Pos := cpOnCrack;
Exit;
end;
if Assigned(bcFilter) then begin
Table.GetRecord(aTI, bcDatabase.DatabaseID, {!!.10}
CursorID, bcInfo.refNr, bcRecordData, ffsltNone, {!!.10}
false, false); {!!.02}
if not bcFilter.MatchesRecord(bcRecordData) then begin
Result := GetNextRecord(bcRecordData, ffsltNone);
if Result = DBIERR_FF_FilterTimeout then
Exit;
if Result <> DBIERR_NONE then begin
SetToEnd;
Exit;
end;
end;
end;
{otherwise set all current key variables}
bcInfo.KeyValid := true;
bcInfo.pos := cpOnCrack;
end;
finally
bcInfoLock.Unlock; {!!.06}
RelContentLock(ffclmRead);
end;
end;
{--------}
function TffSrCursor.SwitchToIndex(aIndexID : integer;
aPosnOnRec : boolean) : TffResult;
begin
{Assumption: aIndexID has been validated}
Result := DBIERR_NONE;
if aPosnOnRec and (bcInfo.pos <> cpOnRecord) then begin
Result := DBIERR_NOCURRREC;
Exit;
end;
AcqContentLock(ffclmRead);
try
{set the index}
bcIndexID := aIndexID;
{free the key buffers}
FFFreeMem(bcCurKey, scKeyLen);
FFFreeMem(bcRng1Key, scKeyLen);
FFFreeMem(bcRng2Key, scKeyLen);
{we lose our range}
bcHasRange := false;
{get our work areas for the key}
scKeyLen := bcTable.Dictionary.IndexKeyLength[aIndexID];
FFGetMem(bcCurKey, scKeyLen);
FFGetMem(bcRng1Key, scKeyLen);
FFGetMem(bcRng2Key, scKeyLen);
{initialise our key index data record}
bcTable.MakeKIDForCursor(aIndexID, bcKID);
{ Set up the position of the cursor to the current record or BOF. }
if aPosnOnRec then begin
{ Note that we've already checked that bcInfo.pos is cpOnRecord. }
scRebuildCurKey(nil, false);
bcRebuildKeyPath; {!!.05}
end else
SetToBegin;
finally
RelContentLock(ffclmRead);
end;
end;
{====================================================================}
{===TffSrCursorList==================================================}
procedure TffSrCursorList.AddCursor(aCursor : TffSrBaseCursor);
begin
solList.Insert(aCursor);
end;
{--------}
function TffSrCursorList.CursorCount : integer;
begin
Assert(Assigned(solList));
Result := solList.Count;
end;
{--------}
procedure TffSrCursorList.DeleteCursor(aCursorID : TffCursorID);
begin
solList.Delete(aCursorID);
end;
{--------}
function TffSrCursorList.GetCursorItem(Find : TffListFindType; Value : Longint) : TffSrBaseCursor;
var
Inx : integer;
begin
Result := nil;
if (Find = ftFromID) then begin
Inx := solList.Index(Value);
if (Inx <> -1) then
Result := TffSrBaseCursor(solList[Inx]);
end
else {Find = ftFromIndex}
Result := TffSrBaseCursor(solList[Value]);
end;
{--------}
procedure TffSrCursorList.RemoveCursor(aCursorID : TffCursorID);
begin
solList.Remove(aCursorID);
end;
{====================================================================}
{===TffSrBaseTable===================================================}
constructor TffSrBaseTable.Create(anEngine : TffServerEngine;
const aBaseName : TffTableName;
aFolder : TffSrFolder;
aBufMgr : TffBufferManager;
const aOpenMode : TffOpenMode);
begin
inherited Create;
btBaseName := FFShStrAlloc(aBaseName);
btBufMgr := aBufMgr;
btEngine := anEngine;
btFolder := aFolder;
{create the data dictionary, it'll be empty for now}
btDictionary := TffServerDataDict.Create(4096);
btDictionary.SetBaseName(aBaseName);
{create the list of file info records, set the capacity to 8,
generally tables will have less than this number of files}
btFiles := TffVCLList.Create;
btFiles.Capacity := 8;
{create the cursor list}
btCursorList := TffSrCursorList.Create;
btContentLocks := TffLockContainer.Create;
btClientLocks := TffLockContainer.Create;
btPortal := TffReadWritePortal.Create;
// btUseInternalRollback := False; {!!.03}{Deleted !!.11}
end;
{--------}
destructor TffSrBaseTable.Destroy;
begin
try {!!.06}
CloseFiles(false, nil);
finally {!!.06}
btCursorList.Free;
btFiles.Free;
btDictionary.Free;
btContentLocks.Free;
btClientLocks.Free;
btPortal.Free;
FFShStrFree(btBaseName);
inherited Destroy;
end; {!!.06}
end;
{--------}
procedure TffSrBaseTable.AcqClientLock(aCursorID : Longint;
const aLockType : TffSrLockType;
const aConditional : Boolean);
var
LockStatus : TffLockRequestStatus;
RetryUntil : DWORD;
TblStr : string;
TickCount : DWORD;
begin
RetryUntil := FFGetRetry;
TickCount := GetTickCount;
{ Do we have any time left? Note that we are doing this check to avoid
the situation where we ask for a lock and pass a negative timeout. }
if (RetryUntil > TickCount) and
((RetryUntil - TickCount) >= 5) then begin
{Begin !!.06}
{ If there are record locks already on the table then raise an
exception. }
if HasRecordLocks then
FFRaiseException(EffServerException, ffStrResServer, fferrLockRejected,
[FFMapLockToName(ffsltExclusive), '',
PffFileInfo(btFiles[0])^.fiName^]);
{End !!.06}
{ Obtain an exclusive lock on the table content. }
LockStatus := Folder.LockMgr.AcquireClientLock(btClientLocks,
aCursorID,
(RetryUntil - TickCount),
aLockType);
{ Raise an exception if something went awry. }
if LockStatus <> fflrsGranted then
TblStr := format(ffcTableContent,[btBaseName^]);
case LockStatus of
fflrsTimeout :
FFRaiseException(EffServerException, ffStrResServer, fferrTableLockTimeout,
[FFMapLockToName(aLockType), TblStr]);
fflrsRejected :
FFRaiseException(EffServerException, ffStrResServer, fferrLockRejected,
[FFMapLockToName(aLockType), TblStr, '']);
end; { case }
end else
{ No. Assume we will time out waiting for the resource. }
FFRaiseExceptionNoData(EffServerException, ffStrResServer,
fferrGeneralTimeout);
end;
{--------}
procedure TffSrBaseTable.AcqContentLock(aTrans : TffSrTransaction;
const aLockType : TffSrLockType;
const aConditional : Boolean);
var
LockStatus : TffLockRequestStatus;
RetryUntil : DWORD;
TblStr : string;
TickCount : DWORD;
TranLockType : TffSrLockType; {!!.03}
begin
{Begin !!.03}
{ Does the transaction have a lock container? }
if assigned(aTrans.TransLockContainer) then begin
{ Yes. Does it already have a sufficient lock on this table? }
TranLockType := TffTransContainer(aTrans.TransLockContainer).TableContentLockType(btContentLocks);
if TranLockType >= aLockType then
{ Yes. Exit. We don't need to request another lock since we have one
already. }
Exit;
{ Does this transaction already have a share lock on this table & is now
requesting an exclusive lock? }
if (TranLockType = ffsltShare) and
(aLockType = ffsltExclusive) and
(btContentLocks.Count > 1) then begin
{ Yes. Does another transaction currently have a share lock on this
table and is already waiting for an exclusive lock? }
btContentLocks.BeginRead;
try
if btContentLocks.SimpleDeadlock then
{ Yes. We have a simple deadlock situation. Raise a deadlock exception
so that this transaction is rolled back. This will free up its
share lock which may allow the other transaction to continue
processing. }
FFRaiseException(EffServerException, ffStrResServer,
fferrDeadlock,
[FFMapLockToName(aLockType),
Format(ffcTableContent,[btBaseName^]),
aTrans.TransactionID]);
finally
btContentLocks.EndRead;
end;
end;
end;
{End !!.03}
RetryUntil := FFGetRetry;
TickCount := GetTickCount;
{ Do we have any time left? Note that we are doing this check to avoid
the situation where we ask for a lock and pass a negative timeout. }
if (RetryUntil > TickCount) and
((RetryUntil - TickCount) >= 5) then begin
{ Obtain an exclusive lock on the table content. }
LockStatus := Folder.LockMgr.AcquireContentLock(btContentLocks,
Self,
aTrans,
aConditional,
(RetryUntil - TickCount),
aLockType);
{ Raise an exception if something went awry. }
if LockStatus <> fflrsGranted then
TblStr := format(ffcTableContent,[btBaseName^]);
case LockStatus of
fflrsTimeout :
FFRaiseException(EffServerException, ffStrResServer, fferrTableLockTimeout,
[FFMapLockToName(aLockType), TblStr]);
fflrsRejected :
FFRaiseException(EffServerException, ffStrResServer, fferrLockRejected,
[FFMapLockToName(aLockType), TblStr, '']);
end; { case }
end
else
{ No. Assume we will time out waiting for the resource. }
FFRaiseExceptionNoData(EffServerException, ffStrResServer,
fferrGeneralTimeout);
end;
{Begin !!.10}
{--------}
function TffSrBaseTable.AcqExclContentLock(aTrans : TffSrTransaction) : TffResult;
var
LockStatus : TffLockRequestStatus;
begin
{ Obtain an exclusive lock on the table content. }
LockStatus := Folder.LockMgr.AcquireContentLock(btContentLocks,
Self,
aTrans,
True,
0,
ffsltExclusive);
{ Set the result. }
case LockStatus of
fflrsGranted : Result := DBIERR_NONE;
fflrsTimeout : Result := fferrLockTimeout;
fflrsRejected : Result := fferrLockRejected;
else
Result := DBIERR_FF_Unknown;
end; { case }
end;
{End !!.10}
{--------}
procedure TffSrBaseTable.AcqLock(const aCursorID : TffCursorID;
const aLockType : TffSrLockType);
var
LockStatus : TffLockRequestStatus;
RetryUntil : DWORD;
TblStr : string;
TickCount : DWORD;
begin
RetryUntil := FFGetRetry;
TickCount := GetTickCount;
{ Do we have any time left? Note that we are doing this check to avoid
the situation where we ask for a lock and pass a negative timeout. }
if (RetryUntil > TickCount) and
((RetryUntil - TickCount) >= 5) then begin
{ Obtain an exclusive lock on the file header. }
LockStatus := Folder.LockMgr.AcquireTableLock(TableID, aLockType,
False,
(RetryUntil - TickCount),
aCursorID);
{ Raise an exception if something went awry. }
if LockStatus <> fflrsGranted then
TblStr := format(ffcTable,[btBaseName^]);
case LockStatus of
fflrsTimeout :
FFRaiseException(EffServerException, ffStrResServer, fferrTableLockTimeout,
[FFMapLockToName(aLockType), TblStr]);
fflrsRejected :
FFRaiseException(EffServerException, ffStrResServer, fferrLockRejected,
[FFMapLockToName(aLockType), TblStr, '']);
end; { case }
end else
{ No. Assume we will time out waiting for the resource. }
FFRaiseExceptionNoData(EffServerException, ffStrResServer,
fferrGeneralTimeout);
end;
{Begin !!.03}
{--------}
procedure TffSrBaseTable.AddAttribute(const anAttrib : TffFileAttribute);
var
Index : Longint;
begin
for Index := 0 to pred(FileCount) do
include(Files[Index].fiAttributes, anAttrib);
end;
{End !!.03}
{--------}
procedure TffSrBaseTable.BeginCommit;
begin
btPortal.BeginWrite;
end;
{--------}
procedure TffSrBaseTable.BeginRead;
begin
btPortal.BeginRead;
end;
{Begin !!.03}
{--------}
procedure TffSrBaseTable.btCommitBLOBMgr;
var
anInx : LongInt;
begin
for anInx := 0 to pred(FileCount) do
if Files[anInx].fiBLOBrscMgr <> nil then
Files[anInx].fiBLOBrscMgr.Commit;
end;
{End !!.03}
{--------}
procedure TffSrBaseTable.btCreateFile(aFileInx : Integer;
aTI : PffTransInfo;
const aExtension : TffExtension;
aForServer : Boolean;
aAttribs : TffFileAttributes;
aStore : TffBaseTempStorage);
var
RecLen : Integer;
BlockSize : Longint;
FI : PffFileInfo;
FileHeader : PffBlockHeaderFile;
aRelMethod : TffReleaseMethod;
begin
{ASSUMPTION: btFiles.Count has already been set to the correct number
of files so that the aFileInx'th element of the btFiles
array can be set
btFiles[aFileInx] is nil, except for aFileInx=0}
{create the file inforec (note that the descriptor for file 0, the
data file, has already been created)}
if (aFileInx <> 0) then begin
Files[aFileInx] := FFAllocFileInfo(FFMakeFullFileName(Folder.Path, BaseName),
aExtension, btBufMgr);
with Files[aFileInx]^ do begin
fiAttributes := aAttribs;
fiForServer := aForServer;
fiEncrypted := btEngine.Configuration.GeneralInfo^.giAllowEncrypt and
btDictionary.IsEncrypted;
fiTempStore := aStore;
end;
end;
FI := Files[aFileInx];
{ Create the file on disk. }
RecLen := Dictionary.RecordLength;
BlockSize := Dictionary.FileBlockSize[aFileInx];
FFOpenFile(FI, omReadWrite, smExclusive, true, true);
try
{patch up the file's block size for the buffer manager}
FI^.fiBlockSize := BlockSize;
FI^.fiBlockSizeK := BlockSize div 1024; {!!.11}
FI^.fiLog2BlockSize := FFCalcLog2BlockSize(BlockSize);
{add a new block for the new header}
FileHeader := PffBlockHeaderFile(btBufMgr.AddBlock(FI, aTI, 0, aRelMethod));
{set up the file header information}
with FileHeader^ do begin
bhfSignature := ffc_SigHeaderBlock;
bhfNextBlock := $FFFFFFFF;
bhfThisBlock := 0;
bhfLSN := 0;
bhfBlockSize := BlockSize;
bhfEncrypted := ord(
btEngine.Configuration.GeneralInfo^.giAllowEncrypt and
Dictionary.IsEncrypted);
bhfLog2BlockSize := FFCalcLog2BlockSize(BlockSize);
bhfUsedBlocks := 1; {ie this header block}
bhfAvailBlocks := 0;
bhf1stFreeBlock := $FFFFFFFF;
bhfRecordCount := 0;
bhfDelRecCount := 0;
bhf1stDelRec.iLow := $FFFFFFFF;
bhfRecordLength := RecLen;
bhfRecLenPlusTrailer := RecLen + Sizeof(Byte);
bhfRecsPerBlock := (BlockSize - ffc_BlockHeaderSizeData) div bhfRecLenPlusTrailer;
bhf1stDataBlock := $FFFFFFFF;
bhfLastDataBlock := $FFFFFFFF;
bhfBLOBCount := 0;
bhfDelBLOBHead.iLow := $FFFFFFFF;
bhfDelBLOBTail.iLow := $FFFFFFFF;
bhfAutoIncValue := 0;
bhfIndexCount := Dictionary.IndexCount;
bhfHasSeqIndex := 1;
bhfIndexHeader := ffc_W32NoValue;
bhfDataDict := 0;
bhfFieldCount := Dictionary.FieldCount;
bhfFFVersion := btFolder.NewTableVersion; {!!.11}
end;
aRelMethod(PffBlock(FileHeader));
except
aRelMethod(PffBlock(FileHeader));
btBufMgr.RemoveFile(FI);
FFCloseFile(FI);
if (aFileInx <> 0) then begin
FFFreeFileInfo(FI);
btFiles[aFileInx] := nil;
end;
raise;
end;{try..except}
end;
{--------}
procedure TffSrBaseTable.btDeleteBLOBsForRecord(aTI : PffTransInfo;
aData : PffByteArray);
var
FldInx : integer;
FldDesc : PffFieldDescriptor;
BLOBNr : TffInt64;
IsNull : boolean;
begin
with Dictionary do begin
for FldInx := 0 to pred(FieldCount) do begin
FldDesc := FieldDescriptor[FldInx];
if (FldDesc^.fdType >= fftBLOB) and
(FldDesc^.fdType <= ffcLastBLOBType) then begin
GetRecordField(FldInx, aData, IsNull, @BLOBNr);
if (not IsNull) and (BLOBNr.iLow <> ffc_W32NoValue) then {!!.03}
FFTblDeleteBLOB(Files[BLOBFileNumber], aTI, BLOBNr);
end;
end;
end;
end;
{--------}
function TffSrBaseTable.btGetBaseName : TffTableName;
begin
Result := btBaseName^;
end;
{--------}
function TffSrBaseTable.btGetCursorList : TffSrCursorList;
begin
Result := btCursorList;
end;
{--------}
function TffSrBaseTable.btGetDictionary : TffServerDataDict;
begin
Result := btDictionary;
end;
{--------}
function TffSrBaseTable.btGetFile(Inx : integer) : PffFileInfo;
begin
if (0 <= Inx) and (Inx < btFiles.Count) then
Result := PffFileInfo(btFiles[Inx])
else
Result := nil;
end;
{--------}
function TffSrBaseTable.btGetFileCount : integer;
begin
Result := btFiles.Count;
end;
{--------}
function TffSrBaseTable.btGetFolder : TffSrFolder;
begin
Result := btFolder;
end;
{--------}
procedure TffSrBaseTable.btInformCursors(aSrcCursorID : TffCursorID;
aOp : TffRecOp;
aRefNr : TffInt64;
aIndexID : integer);
var
Inx : integer;
Cursor, SrcCursor : TffSrBaseCursor;
begin
SrcCursor := TffSrBaseCursor(aSrcCursorID);
CursorList.BeginRead;
try
for Inx := 0 to pred(CursorList.CursorCount) do begin
Cursor := CursorList[ftFromIndex, Inx];
{ Is the cursor within the context of our transaction? }
if (Cursor.Database = SrcCursor.Database) and
(Cursor.CursorID <> aSrcCursorID) then
Cursor.bcRecordUpdated(aOp, aRefNr, aIndexID);
end;
finally
CursorList.EndRead;
end;
end;
{--------}
function TffSrBaseTable.btGetOpenIntents : Longint;
begin
Result := btOpenIntents;
end;
{Begin !!.03}
{--------}
procedure TffSrBaseTable.btRollbackBLOBMgr; {!!.05 - Rewritten}
begin
Files[btDictionary.BLOBFileNumber].fiBLOBrscMgr.RollBack;
end; {!!.05 - End rewritten}
{End !!.03}
{--------}
procedure TffSrBaseTable.btSetFile(Inx : integer; FI : PffFileInfo);
begin
btFiles[Inx] := FI;
end;
{--------}
procedure TffSrBaseTable.btSetFileCount(FC : integer);
begin
if (FC <> btFiles.Count) then
btFiles.Count := FC;
end;
{--------}
procedure TffSrBaseTable.btTableUpdated(aDatabaseID : TffDatabaseID);
var
Inx : integer;
Cursor : TffSrBaseCursor;
Database : TffSrDatabase;
begin
{ The purpose of this routine is to invalidate the key path of any
other cursors attached to this table. We do this because an operation
may have caused a Structural Modification Operation (SMO) in the index
used by the cursor and the key path is no longer valid.
This method is thread-safe for the following reasons:
1. A server thread committing a transaction must gain write access to the
table being modified. No other threads will be performing any read or
write operations on that table until the transaction has committed.
2. This routine attempts to activate a cursor if the cursor belongs to
another client. If a thread is in the middle of an operation pertaining
to the cursor's client (e.g., RecordGetNext) then this routine will not
be able to update the cursor until the other thread has finished, and
vice versa.
Future: We could get rid of this if the index structure was such that all
keys were in leaf pages. Then the cursor could just check the LSN of its
current leaf page to see if it should reset its key path. }
Database := TffSrDatabase(aDatabaseID);
CursorList.BeginRead;
try
for Inx := 0 to pred(CursorList.CursorCount) do begin
Cursor := CursorList[ftFromIndex, Inx];
{ Is this cursor attached to another database? }
if (Cursor.Database <> Database) then begin
{Begin !!.06}
Cursor.bcInfoLock.Lock;
try
FFInitKeyPath(Cursor.bcInfo.KeyPath);
finally
Cursor.bcInfoLock.Unlock;
end;
{End !!.06}
end;
end;
finally
CursorList.EndRead;
end;
end;
{--------}
procedure TffSrBaseTable.btUpdateAutoInc(aTI : PffTransInfo; aData : PffByteArray);
var
FldInx : integer;
AutoIncValue : Longint;
IsNull : boolean;
begin
with Dictionary do begin
if HasAutoIncField(FldInx) then begin
GetRecordField(FldInx, aData, IsNull, @AutoIncValue);
if IsNull or (AutoIncValue = 0) then begin
AutoIncValue := FFTblNextAutoIncValue(Files[0], aTI);
SetRecordField(FldInx, aData, @AutoIncValue);
end;
end;
end;
end;
{--------}
procedure TffSrBaseTable.CloseFiles(commitChanges : boolean; aTI : PffTransInfo);
var
FileInx : integer;
TempFile : PffFileInfo;
begin
for FileInx := 0 to pred(FileCount) do begin
TempFile := Files[FileInx];
if (TempFile <> nil) then begin
if FFFileIsOpen(TempFile) then begin
if commitChanges then
TempFile^.fiBufMgr.CommitFileChanges(TempFile, aTI^.tirTrans);
FFCloseFile(TempFile);
end;
TempFile^.fiBufMgr.RemoveFile(TempFile);
FFFreeFileInfo(TempFile);
end;
end;
end;
{--------}
procedure TffSrBaseTable.CommitChanges(aTI : PffTransInfo);
var
FileInx : integer;
TempFile : PffFileInfo;
begin
for FileInx := 0 to pred(FileCount) do begin
TempFile := Files[FileInx];
if (TempFile <> nil) and
FFFileIsOpen(TempFile) then
TempFile^.fiBufMgr.CommitFileChanges(TempFile, aTI^.tirTrans);
end;
end;
{--------}
procedure TffSrBaseTable.DeregisterOpenIntent;
begin
if btOpenIntents > 0 then
dec(btOpenIntents);
end;
{--------}
function TffSrBaseTable.EmptyFiles(aTI : PffTransInfo) : TffResult;
var
aAttribs : TffFileAttributes;
aStore : TffBaseTempStorage;
TempDict : TffServerDataDict;
begin
Result := DBIERR_NONE;
{ Preserve the existing attributes. Assume that each file managed by the
table has the same set of attributes. }
aAttribs := Files[0]^.fiAttributes;
aStore := TffBaseTempStorage(Files[0]^.fiTempStore);
TempDict := TffServerDataDict.Create(Dictionary.BlockSize);
try
TempDict.Assign(Dictionary);
{ Flush out any changes related to this file. They will be eliminated
when we rebuild the file but we want to make sure they are no longer
part of an explicit transaction. }
CloseFiles(true, aTI);
BuildFiles(aTI, false, TempDict, aAttribs, aStore);
{ Is this a temporary file? }
if not (fffaTemporary in aAttribs) then begin
{ No. Commit the changes to the file. }
CloseFiles(true, aTI);
OpenFiles(aTI, false, aAttribs);
end;
finally
TempDict.Free;
end;
end;
{--------}
procedure TffSrBaseTable.EndCommit(aDatabaseID : TffDatabaseID);
begin
btTableUpdated(aDatabaseID);
btPortal.EndWrite;
end;
{--------}
procedure TffSrBaseTable.EndRead;
begin
btPortal.EndRead;
end;
{--------}
procedure TffSrBaseTable.GetNextRecordSeq(aTI : PffTransInfo;
var aRefNr : TffInt64;
aData : PffByteArray);
begin
FFTblReadNextRecord(Files[0], aTI, aRefNr, aRefNr, aData);
end;
{--------}
procedure TffSrBaseTable.GetPrevRecordSeq(aTI : PffTransInfo;
var aRefNr : TffInt64;
aData : PffByteArray);
begin
FFTblReadPrevRecord(Files[0], aTI, aRefNr, aRefNr, aData);
end;
{--------}
function TffSrBaseTable.GetRecord(aTI : PffTransInfo;
const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID; {!!.10}
aRefNr : TffInt64;
aData : PffByteArray;
const aLockType : TffSrLockType; {!!.10}
const aLockObtained : boolean; {!!.02}{!!.10}
const aConditional : Boolean) : TffResult; {!!.02}{!!.10}
begin
Result := DBIERR_NONE;
{ Acquire a lock on the record. }
if (not aLockObtained) then
FFAcqRecordLock(Files[0], aTI, aRefNr, aLockType, aDatabaseID, {!!.10}
aCursorID, aConditional); {!!.02}{!!.10}
try
if Assigned(aData) then
FFTblReadRecord(Files[0], aTI, aRefNr, aData);
except
if aLockType <> ffsltNone then
FFRelRecordLock(Files[0], aTI, aRefNr, aDatabaseID, aCursorID);
raise;
end;
end;
{--------}
procedure TffSrBaseTable.GetRecordLock(aTI : PffTransInfo;
const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID; {!!.10}
const aRefNr : TffInt64; {!!.10}
const aLockType : TffSrLockType); {!!.10}
begin
{ Acquire a lock on the record. }
FFAcqRecordLock(Files[0], aTI, aRefNr, aLockType, aDatabaseID, {!!.10}
aCursorID, false); {!!.02}{!!.10}
end;
{Begin !!.10}
{--------}
procedure TffSrBaseTable.GetRecordNoLock(aTI : PffTransInfo;
aRefNr : TffInt64;
aData : PffByteArray);
begin
if Assigned(aData) then
FFTblReadRecord(Files[0], aTI, aRefNr, aData);
end;
{End !!.10}
{--------}
function TffSrBaseTable.HasClientLock(const aCursorID : TffCursorID) : boolean;
begin
Result := Folder.LockMgr.HasClientLock(btClientLocks, aCursorID);
end;
{--------}
function TffSrBaseTable.HasLock(const aCursorID : TffCursorID;
const aLockType : TffSrLockType) : boolean;
begin
if (aLockType = ffsltNone) then
Result := true
else
Result := Folder.LockMgr.IsTableLockedBy(TableID, aCursorID, aLockType);
end;
{Begin !!.06}
{--------}
function TffSrBaseTable.HasRecordLocks : Boolean;
var
RecordLocks : TffThreadHash64;
begin
RecordLocks := PffFileInfo(btFiles[0])^.fiRecordLocks;
Result := (RecordLocks <> nil) and (RecordLocks.Count > 0);
end;
{End !!.06}
{--------}
function TffSrBaseTable.IsContentLockedBy(aTrans : TffSrTransaction) : boolean;
begin
Result := Folder.LockMgr.IsContentLockedBy(btContentLocks, aTrans);
end;
{--------}
function TffSrBaseTable.IsRecordLocked(aTI : PffTransInfo;
aCursorID : TffCursorID;
aRefNr : TffInt64;
aLockType : TffSrLockType) : Boolean;
begin
Result := Folder.LockMgr.IsRecordLocked(aRefNr, Files[0]);
end;
{--------}
function TffSrBaseTable.IsServerTable : boolean;
begin
Result := btForServer;
end;
{Begin !!.03}
{--------}
procedure TffSrBaseTable.ListBLOBFreeSpace(aTI : PffTransInfo;
const aInMemory : Boolean;
aStream : TStream);
var
anInx : LongInt;
aStr : string;
begin
for anInx := 0 to pred(FileCount) do
if Files[anInx].fiBLOBrscMgr <> nil then begin
aStr := Files[anInx].fiName^ + #13#10;
if anInx > 0 then
aStr := #13#10 + aStr;
aStream.Write(aStr[1], Length(aStr));
Files[anInx].fiBLOBrscMgr.ListFreeSpace(Files[anInx], aTI, aInMemory,
aStream);
end;
end;
{End !!.03}
{--------}
procedure TffSrBaseTable.OpenFiles(aTI : PffTransInfo; aForServer : boolean;
aAttribs : TffFileAttributes);
var
FileInx : integer;
FileCnt : integer;
DataFile : PffFileInfo;
Page : PffBlock;
TempFile : PffFileInfo;
State : integer;
aRelMethod : TffReleaseMethod;
begin
State := 0;
FileCnt := 0;
TempFile := nil;
try
{ Allocate the first file inforec: it'll be for the data file. }
btFiles.Count := 1;
btFiles[0] := FFAllocFileInfo(FFMakeFullFileName(Folder.Path, BaseName),
ffc_ExtForData,
btBufMgr);
State := 25;
PffFileInfo(btFiles[0])^.fiAttributes := aAttribs;
PffFileInfo(btFiles[0])^.fiForServer := aForServer;
{ Open up the data file. }
DataFile := Files[0];
FFOpenFile(DataFile, omReadWrite, smExclusive, aForServer, false);
State := 50;
{ Make sure it's a proper FF file: try to load the header record,
make it fixed (this'll also check the encryption level). }
Page := btBufMgr.AddFile(DataFile, aTI, false, aRelMethod);
{Begin !!.11}
{ Adjust in-memory version if overridden via folder. }
if btFolder.ExistingTableVersion <> 0 then
Files[0].fiFFVersion := btFolder.ExistingTableVersion;
{End !!.11}
aRelMethod(Page);
{ Read the data dictionary. }
Dictionary.ReadFromFile(DataFile, aTI);
Dictionary.BindIndexHelpers;
{ Set up the count of files in the Files array. }
FileCnt := Dictionary.FileCount;
FileCount := FileCnt;
for FileInx := 1 to pred(FileCnt) do begin
Files[FileInx] := nil;
end;
{ Now read through the Dictionary's file list and allocate the
file inforecs, obviously don't do file 0 since it's been done
already. }
State := 100;
for FileInx := 1 to pred(FileCnt) do begin
Files[FileInx] := FFAllocFileInfo(FFMakeFullFileName(Folder.Path, BaseName),
Dictionary.FileExt[FileInx],
btBufMgr);
PffFileInfo(btFiles[FileInx])^.fiAttributes := aAttribs;
PffFileInfo(btFiles[FileInx])^.fiForServer := aForServer;
end;
{ Now open up all the new files, ie excepting file 0 which is
already open (it was opened to read the data dictionary); read
the header record from each file as well, as a security check
to see whether the file is in FF format. }
State := 200;
for FileInx := 1 to pred(FileCnt) do begin
TempFile := Files[FileInx];
FFOpenFile(TempFile,
DataFile^.fiOpenMode, DataFile^.fiShareMode,
DataFile^.fiWriteThru, false);
Page := btBufMgr.AddFile(TempFile, aTI, false, aRelMethod);
aRelMethod(Page);
end;
{Begin !!.11}
Files[Dictionary.BLOBFileNumber].fiBLOBrscMgr :=
TffBaseBLOBResourceMgr.GetMgr(Files[Dictionary.BLOBFileNumber]);
btBLOBEngine := TffBaseBLOBEngine.GetEngine(Files[Dictionary.BLOBFileNumber]);
{End !!.11}
State := 300;
btForServer := aForServer;
except
if (State = 300) then
{BLOB Resource Mgr created}
Files[Dictionary.BLOBFileNumber].fiBLOBrscMgr.Free;
if (State >= 200) then begin
{some files are open, all file inforecs are created}
for FileInx := 1 to pred(FileCnt) do begin
TempFile := Files[FileInx];
if FFFileIsOpen(TempFile) then
FFCloseFile(TempFile);
TempFile^.fiBufMgr.RemoveFile(TempFile);
end;
end;
if (State >= 100) then begin
{at least some of the inforecs have been created}
for FileInx := 1 to pred(FileCnt) do begin
TempFile := Files[FileInx];
FFFreeFileInfo(TempFile);
end;
end;
if (State >= 50) then begin
{at least the data file is open}
TempFile := Files[0];
if FFFileIsOpen(TempFile) then
FFCloseFile(TempFile);
TempFile^.fiBufMgr.RemoveFile(TempFile);
end;
if (State >= 25) then begin
{at least the data file inforec has been allocated}
TempFile := Files[0];
FFFreeFileInfo(TempFile);
end;
if (State >= 0) then begin
{empty the files list}
FileCount := 0;
end;
raise;
end;{try..except}
end;
{--------}
procedure TffSrBaseTable.RegisterOpenIntent;
begin
inc(btOpenIntents);
end;
{--------}
procedure TffSrBaseTable.RelClientLock(aCursorID : Longint; aRemoveAll : Boolean);
begin
if (not aRemoveAll) then {!!.03}
Folder.LockMgr.ReleaseClientLock(btClientLocks, aCursorID)
else
Folder.LockMgr.ReleaseClientLockAll(btClientLocks, aCursorID);
end;
{--------}
procedure TffSrBaseTable.RelContentLock(aTrans : TffSrTransaction);
begin
Folder.LockMgr.ReleaseContentLock(btContentLocks, aTrans);
end;
{--------}
procedure TffSrBaseTable.RelLock(const aCursorID : TffCursorID;
const aAllLocks : Boolean);
begin
if aAllLocks then
Folder.LockMgr.ReleaseTableLockAll(TableID, aCursorID)
else
Folder.LockMgr.ReleaseTableLock(TableID, aCursorID);
end;
{Begin !!.10}
{--------}
procedure TffSrBaseTable.RelaxRecordLock(aTI : PffTransInfo;
aCursorID : TffCursorID;
aRefNr : TffInt64);
begin
FFRelaxRecordLock(Files[0], aTI, aCursorID, aRefNr);
end;
{End !!.10}
{--------}
procedure TffSrBaseTable.RelRecordLock(aTI : PffTransInfo;
aDatabaseID : TffDatabaseID; {!!.10}
aCursorID : TffCursorID;
aRefNr : TffInt64);
begin
FFRelRecordLock(Files[0], aTI, aRefNr, aDatabaseID, aCursorID); {!!.10}
end;
{--------}
procedure TffSrBaseTable.RemoveLocksForCursor(const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID;
const aRefNr : TffInt64;
aTI : PffTransInfo);
begin
{ In FF 1, if aRefNr = 0 then all of a cursor's locks were
released. We do not have such a need for FF2 since the only time a cursor
has record locks is if it is in a transaction and has acquired exclusive
locks on one or more records. When the transaction is committed or rolled
back, the record locks are released. }
FFRelRecordLock(Files[0], aTI, aRefNr, aDatabaseID, aCursorID); {!!.10}
end;
{--------}
procedure TffSrBaseTable.SetAttributes(const fileAttribs : TffFileAttributes);
var
Index : Longint;
begin
for Index := 0 to pred(FileCount) do
Files[Index].fiAttributes := fileAttribs;
end;
{--------}
procedure TffSrBaseTable.SetExclOwner(const aCursorID : TffCursorID);
var
Index : Longint;
begin
for Index := 0 to pred(FileCount) do
Files[Index].fiExclOwner := aCursorId;
end;
{====================================================================}
{===TffSrTable=======================================================}
constructor TffSrTable.Create(anEngine : TffServerEngine;
const aBaseName : TffTableName;
aFolder : TffSrFolder;
aBufMgr : TffBufferManager;
const aOpenMode : TffOpenMode);
begin
inherited Create(anEngine, aBaseName, aFolder, aBufMgr, aOpenMode);
{create the user routine arrays}
stUserBuildKey := TffVCLList.Create;
stUserCompareKey := TffVCLList.Create;
{miscellaneous}
FreeOnRemove := true;
// stUseInternalRollback := False; {!!.03}
end;
{--------}
destructor TffSrTable.Destroy;
begin
stUserCompareKey.Free;
stUserBuildKey.Free;
RemoveDynamicLinks;
inherited Destroy;
end;
{--------}
procedure TffSrTable.AddIndex(const aIndexDesc : TffIndexDescriptor;
aTI : PffTransInfo);
var
IndexInx : integer;
begin
{assumption: aIndexDesc has been validated}
IndexInx := Dictionary.IndexCount;
with aIndexDesc do
Dictionary.AddIndex(idName, idDesc, idFile, idCount, idFields,
idFieldIHlprs, idDups, idAscend, idNoCase);
Dictionary.BindIndexHelpers;
Dictionary.WriteToFile(Files[0], aTI);
FFTblAddIndex(Files[aIndexDesc.idFile],
aTI,
IndexInx,
Dictionary.IndexKeyLength[IndexInx],
aIndexDesc.idDups,
IndexInx = 0);
end;
{--------}
procedure TffSrTable.BuildFiles(aTI : PffTransInfo;
aForServer : boolean;
aDictionary : TffDataDictionary;
aAttribs : TffFileAttributes;
aStore : TffBaseTempStorage);
var
FileInx : integer;
IndexInx : integer;
DataFile : PffFileInfo;
FileCnt : integer; {dup for speed}
begin
{allocate the first file inforec now: it'll be for the data file}
btFiles.Count := 1;
btFiles[0] := FFAllocFileInfo(FFMakeFullFileName(Folder.Path, BaseName),
ffc_ExtForData, btBufMgr);
with PffFileInfo(btFiles[0])^ do begin
fiAttributes := aAttribs;
fiForServer := aForServer;
fiEncrypted := btEngine.Configuration.GeneralInfo^.giAllowEncrypt and
aDictionary.IsEncrypted;
fiRecLenPlusTrailer := aDictionary.RecordLength + SizeOf(Byte);
fiRecordLength := aDictionary.RecordLength;
fiTempStore := aStore;
end;
{ Validate the dictionary. }
aDictionary.CheckValid;
{ Assimilate the dictionary. }
btDictionary.ForceOffReadOnly;
btDictionary.Assign(aDictionary);
btDictionary.BindIndexHelpers;
{ Get the file count for this table (for speed reasons, etc). }
FileCnt := Dictionary.FileCount;
FileCount := FileCnt;
{ Get the data file for speed reasons. }
DataFile := Files[0];
{ Build all the files and assume that all will contain indexes. }
for FileInx := 0 to pred(FileCnt) do begin
btCreateFile(FileInx, aTI, btDictionary.FileExt[FileInx], aForServer,
aAttribs, aStore);
FFTblPrepareIndexes(btFiles[FileInx], aTI);
end;
{ Write the dictionary. }
Dictionary.WriteToFile(DataFile, aTI);
{ Add the indexes to their associated index files. }
with btDictionary do begin
for IndexInx := 0 to pred(IndexCount) do begin
FFTblAddIndex(Files[IndexFileNumber[IndexInx]],
aTI,
IndexInx,
IndexKeyLength[IndexInx],
IndexAllowDups[IndexInx],
IndexInx = 0);
end;
end;
{Begin !!.11}
Files[btDictionary.BLOBFileNumber].fiBLOBrscMgr :=
TffBaseBLOBResourceMgr.GetMgr(Files[Dictionary.BLOBFileNumber]);
btBLOBEngine := TffBaseBLOBEngine.GetEngine(Files[btDictionary.BLOBFileNumber]);
{End !!.11}
Files[btDictionary.BLOBFileNumber].fiMaxSegSize :=
FFCalcMaxBLOBSegSize(Files[btDictionary.BLOBFileNumber]);
end;
{--------}
function TffSrTable.BuildKeyForRecord(aIndexID : integer;
aData : PffByteArray;
aKey : PffByteArray;
aFieldCount : integer;
aPartialLen : integer) : TffResult;
var
BuildKey : TffKeyBuildFunc;
LenKeyToGen : integer;
begin
if (Dictionary.IndexType[aIndexID] = itComposite) then begin
Result := stBuildCompositeKey(aIndexID, aData, aKey, aFieldCount, aPartialLen);
end
else {user-defined index} begin
BuildKey := stGetUserBuildKey(aIndexID);
if (aFieldCount = 0) and (aPartialLen = 0) then
LenKeyToGen := Dictionary.IndexKeyLength[aIndexID]
else
LenKeyToGen := aPartialLen;
if not BuildKey(aIndexID, aData, aKey^, LenKeyToGen) then
Result := DBIERR_KEYVIOL
else
Result := DBIERR_NONE;
end;
end;
{--------}
function TffSrTable.CompareKeysForCursor(var aKID : TffKeyIndexData;
aKey1 : PffByteArray;
aKey2 : PffByteArray) : integer;
var
CompareKey : TffKeyCompareFunc;
begin
with aKID, kidCompareData^ do begin
if (kidIndexType = itComposite) then begin
Result := FFKeyCompareComposite(aKey1^, aKey2^, kidCompareData);
end
else {user-defined index} if (kidIndex = 0) then begin
Result := FFCmpDW(PffWord32(aKey1)^, PffWord32(aKey2)^);
end
else {not index 0} begin
CompareKey := stGetUserCompareKey(kidIndex);
Result := CompareKey(aKey1^, aKey2^, kidCompareData);
end;
end;
end;
{--------}
function TffSrTable.DeleteRecord(aTI : PffTransInfo;
const aCursorID : TffCursorID;
const aRefNr : TffInt64;
const aLockObtained : Boolean;
var aBTreeChanged : Boolean) : TffResult; {!!.05}
var
OldData : PffByteArray;
RecLen : integer;
begin
RecLen := Dictionary.RecordLength;
FFGetMem(OldData, RecLen);
{ If we have yet to lock the record then do so. }
if (not aLockObtained) then
FFAcqRecordLock(Files[0], aTI, aRefNr, ffsltExclusive,
aTI^.tirTrans.DatabaseID, {!!.10}
aCursorID, false); {!!.02}{!!.10}
{ Note: We leave all such locks active until the transaction is committed. }
try
FFTblReadRecord(Files[0], aTI, aRefNr, OldData);
Result := stDeleteKeysForRecord(aTI, aRefNr,
OldData, aBTreeChanged); {!!.05}
if (Result <> DBIERR_NONE) then
Exit;
btDeleteBLOBsForRecord(aTI, OldData);
FFTblDeleteRecord(Files[0], aTI, aRefNr);
finally
btInformCursors(aCursorID, roDelete, aRefNr, 0);
FFFreeMem(OldData, RecLen);
end;{try..finally}
end;
{--------}
procedure TffSrTable.DropIndex(aTI : PffTransInfo; aIndexID : Longint);
var
i : integer;
begin
Dictionary.RemoveIndex(aIndexID);
Dictionary.WriteToFile(Files[0], aTI);
for i := 0 to pred(Dictionary.FileCount) do
FFTblDeleteIndex(Files[i], aTI, aIndexID);
end;
{--------}
function TffSrTable.FindKey(var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aTI : PffTransInfo;
aKey : PffByteArray;
var aKeyPath : TffKeyPath;
aAction : TffSearchKeyAction) : boolean;
begin
Result := FFTblFindKey(aKID, aRefNr, aTI, aKey, aKeyPath, aAction);
end;
{--------}
function TffSrTable.GetNextKey(var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aTI : PffTransInfo;
aKey : PffByteArray;
var aKeyPath : TffKeyPath) : TffResult;
begin
if FFTblNextKey(aKID, aRefNr, aTI, aKey, aKeyPath) then
Result := DBIERR_NONE
else
Result := DBIERR_EOF;
end;
{--------}
function TffSrTable.GetNextRecord(aTI : PffTransInfo;
const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID; {!!.10}
var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aKey : PffByteArray;
var aKeyPath : TffKeyPath;
aData : PffByteArray;
const aLockType : TffSrLockType) : TffResult; {!!.10}
begin
Result := DBIERR_NONE;
try
if FFTblNextKey(aKID, aRefNr, aTI, aKey, aKeyPath) then begin
FFAcqRecordLock(Files[0], aTI, aRefNr, aLockType, {!!.10}
aDatabaseID, aCursorID, false); {!!.10}
FFTblReadRecord(Files[0], aTI, aRefNr, aData);
end
else
Result := DBIERR_EOF;
except
if aLockType <> ffsltNone then
FFRelRecordLock(Files[0], aTI, aRefNr, aDatabaseID, aCursorID); {!!.10}
raise;
end;
end;
{--------}
function TffSrTable.GetPriorRecord(aTI : PffTransInfo;
const aDatabaseID : TffDatabaseID; {!!.10}
const aCursorID : TffCursorID; {!!.10}
var aKID : TffKeyIndexData;
var aRefNr : TffInt64;
aKey : PffByteArray;
var aKeyPath : TffKeyPath;
aData : PffByteArray;
const aLockType : TffSrLockType) : TffResult; {!!.10}
begin
Result := DBIERR_NONE;
try
if FFTblPrevKey(aKID, aRefNr, aTI, aKey, aKeyPath) then begin
FFAcqRecordLock(Files[0], aTI, aRefNr, aLockType, {!!.10}
aDatabaseID, aCursorID, false); {!!.10}
FFTblReadRecord(Files[0], aTI, aRefNr, aData);
end
else
Result := DBIERR_BOF;
except
if aLockType <> ffsltNone then
FFRelRecordLock(Files[0], aTI, aRefNr, aDatabaseID, aCursorID); {!!.10}
raise;
end;
end;
{--------}
function TffSrTable.InsertRecord(aTI : PffTransInfo;
aCursorID : TffCursorID;
aData : PffByteArray;
aLockType : TffSrLockType;
var aNewRefNr : TffInt64) : TffResult;
var
RefNr : TffInt64;
begin
RefNr.iLow := 0;
RefNr.iHigh := 0;
if not Dictionary.CheckRequiredRecordFields(aData) then
Result := DBIERR_REQDERR
else begin
{we need to add the default field values}
if Dictionary.DefaultFieldCount > 0 then
Dictionary.SetDefaultFieldValues(aData);
{ Updating the autoinc value obtains an exclusive lock on block 0 which
prevents other cursors from inserting the same or additional records
until we are done. }
btUpdateAutoInc(aTI, aData);
FFTblAddRecord(Files[0], aTI, RefNr, aData);
{initialize result to an invalid value}
Result := -1;
try
aNewRefNr := RefNr;
Result := stInsertKeysForRecord(aTI, RefNr, aData);
if (Result = DBIERR_NONE) then
FFAcqRecordLock(Files[0], aTI, aNewRefNr, aLockType, {!!.10}
aTI^.tirTrans.DatabaseID, aCursorID, false); {!!.10}
finally
{ If the insertion of the keys was not successful and we
are to cleanup after ourselves then remove the inserted record. }
if (Result <> DBIERR_NONE) then begin {!!.11}
FFTblDeleteRecord(Files[0], aTI, RefNr);
RefNr.iLow := 0;
RefNr.iHigh := 0;
end;
end;
end;
end;
{--------}
function TffSrTable.InsertRecordNoDefault(aTI : PffTransInfo; {!!.10}
aCursorID : TffCursorID;
aData : PffByteArray;
aLockType : TffSrLockType;
var aNewRefNr : TffInt64) : TffResult;
var
RefNr : TffInt64;
begin
RefNr.iLow := 0;
RefNr.iHigh := 0;
if not Dictionary.CheckRequiredRecordFields(aData) then
Result := DBIERR_REQDERR
else begin
{ Updating the autoinc value obtains an exclusive lock on block 0 which
prevents other cursors from inserting the same or additional records
until we are done. }
btUpdateAutoInc(aTI, aData);
FFTblAddRecord(Files[0], aTI, RefNr, aData);
{initialize result to an invalid value}
Result := -1;
try
aNewRefNr := RefNr;
Result := stInsertKeysForRecord(aTI, RefNr, aData);
if (Result = DBIERR_NONE) then
FFAcqRecordLock(Files[0], aTI, aNewRefNr, aLockType, {!!.10}
aTI^.tirTrans.DatabaseID, aCursorID, false); {!!.10}
finally
{ If the insertion of the keys was not successful and we
are to cleanup after ourselves then remove the inserted record. }
if (Result <> DBIERR_NONE) then begin {!!.11}
FFTblDeleteRecord(Files[0], aTI, RefNr);
RefNr.iLow := 0;
RefNr.iHigh := 0;
end;
end;
end;
end;
{--------}
procedure TffSrTable.MakeKIDForCursor(aIndexID : integer; var aKID : TffKeyIndexData);
begin
with Dictionary, aKID, kidCompareData^ do begin
kidFI := Files[IndexFileNumber[aIndexID]];
kidIndex := aIndexID;
if (aIndexID = 0) then begin
kidCompare := FFKeyCompareI64;
kidIndexType := itUserDefined;
end
else if (IndexType[aIndexID] = itComposite) then begin
kidCompare := FFKeyCompareComposite;
kidIndexType := itComposite;
end
else begin
kidCompare := stGetUserCompareKey(aIndexID);
kidIndexType := itUserDefined;
end;
cdKeyLen := IndexKeyLength[aIndexID];
cdDict := pointer(Dictionary);
cdIndex := aIndexID;
cdFldCnt := 0; {for completeness}
cdPartLen := 0; {for completeness}
cdAscend := IndexIsAscending[aIndexID];
cdNoCase := IndexIsCaseInsensitive[aIndexID];
end;
end;
{--------}
function TffSrTable.PutRecord(aTI : PffTransInfo;
aCursorID : TffCursorID;
aRefNr : TffInt64;
aData : PffByteArray;
aRelLock : boolean; {!!.05}
var aKeyChanged : Boolean) : TffResult; {!!.05}
var
OldData: PffByteArray;
RecLen : integer;
begin
{ Assumption: By the time we have reached this point, the transaction has
acquired a content lock on the table and we are the only ones who can
modify the record. }
RecLen := 0;
if not Dictionary.CheckRequiredRecordFields(aData) then begin
Result := DBIERR_REQDERR;
Exit;
end;
Result := DBIERR_NONE;
try
// try {!!.11}
RecLen := Dictionary.RecordLength;
FFGetMem(OldData, RecLen);
FFTblReadRecord(Files[0], aTI, aRefNr, OldData);
{ Acquire an exclusive lock. }
FFAcqRecordLock(Files[0], aTI, aRefNr, ffsltExclusive, {!!.10}
aTI^.tirTrans.DatabaseID, aCursorID, false); {!!.10}
try {!!.11}
{ There's no need to update index 0, the refnr has not changed. }
Result := stUpdateKeysForRecord(aCursorID, aTI, aRefNr, aData, OldData, aKeyChanged); {!!.05}
if (Result <> DBIERR_NONE) then
Exit;
FFTblUpdateRecord(Files[0], aTI, aRefNr, aData);
except
FFRelRecordLock(Files[0], aTI, aRefNr, aTI^.tirTrans.DatabaseID, {!!.10}
aCursorID); {!!.10}
raise; {!!.01}
end;
finally
FFFreeMem(OldData, RecLen);
end;{try..finally}
end;
{--------}
procedure TffSrTable.RemoveDynamicLinks;
var
i : Integer;
KeyProcItem : TffKeyProcItem;
Inx : Integer;
begin
{unlink user-defined indexes}
with btEngine.Configuration do begin
for i := 1 to pred(Dictionary.IndexCount) do begin
if (Dictionary.IndexType[i] <> itComposite) then begin
Inx := KeyProcList.KeyProcIndex(Folder.Path, BaseName, i);
if (Inx <> -1) then begin
KeyProcItem := KeyProcList[Inx];
KeyProcItem.Unlink;
end;
end;
end;
end;
end;
{--------}
procedure TffSrTable.ResolveDynamicLinks;
var
i : integer;
KeyProcItem : TffKeyProcItem;
Inx : integer;
begin
stUserBuildKey.Clear;
stUserCompareKey.Clear;
{add nil pointers for index 0 as this can never be a user-defined
index}
stUserBuildKey.Add(nil);
stUserCompareKey.Add(nil);
{fill the arrays with data for each index}
for i := 1 to pred(Dictionary.IndexCount) do begin
if (Dictionary.IndexType[i] = itComposite) then begin
stUserBuildKey.Add(nil);
stUserCompareKey.Add(nil);
end
else {it's a user-defined index} begin
with btEngine.Configuration do begin
Inx := KeyProcList.KeyProcIndex(Folder.Path, BaseName, i);
if (Inx <> -1) then begin
KeyProcItem := KeyProcList[Inx];
if KeyProcItem.Link then begin
stUserBuildKey.Add(pointer(@KeyProcItem.BuildKey));
stUserCompareKey.Add(pointer(@KeyProcItem.CompareKey));
end
else
FFRaiseExceptionNoData(EffServerException,
ffStrResServer,
fferrResolveTableLinks);
end else
FFRaiseExceptionNoData(EffServerException, ffStrResServer, fferrResolveTableLinks);
end;
end;
end;
end;
{--------}
function TffSrTable.stGetBuiltCompositeKey(aIndexID : integer;
aData : PffByteArray;
aKeyLen : Longint;
var aKey : PffByteArray) : TffResult;
var
WorkKey : PffByteArray;
begin
FFGetMem(WorkKey, aKeyLen);
try
Result := stBuildCompositeKey(aIndexID, aData, WorkKey, 0, 0);
if (Result <> DBIERR_NONE) then
FFFreeMem(WorkKey, aKeyLen) {!!.06}
else
aKey := WorkKey;
except
FFFreeMem(WorkKey, aKeyLen);
raise;
end;{try..except}
end;
{--------}
function TffSrTable.stBuildCompositeKey(aIndexID : integer;
aData : PffByteArray;
aKey : PffByteArray;
aFieldCount : integer;
aLastFldLen : integer) : TffResult;
var
KeyOffset : integer;
IndexDscrptr: PffIndexDescriptor;
FieldDesc : PffFieldDescriptor;
FieldNumber : integer;
LenToUse : integer;
FldCnt : integer;
begin
Result := DBIERR_NONE;
KeyOffset := 0;
IndexDscrptr := Dictionary.IndexDescriptor[aIndexID];
with IndexDscrptr^ do begin
{clear the entire key}
FFInitKey(aKey, idKeyLen, idCount);
{calculate the number of complete fields we can use}
if (aFieldCount = 0) then
if (aLastFldLen = 0) then
FldCnt := idCount
else {partial key}
FldCnt := 0
else
if (aLastFldLen = 0) then
FldCnt := FFMinI(aFieldCount, idCount)
else {partial key}
FldCnt := FFMinI(aFieldCount, pred(idCount));
{build using complete fields}
if (FldCnt > 0) then
for FieldNumber := 0 to pred(FldCnt) do begin
FieldDesc := Dictionary.FieldDescriptor[idFields[FieldNumber]];
with FieldDesc^ do begin
if not Dictionary.IsRecordFieldNull(idFields[FieldNumber], aData) then begin
Move(aData^[fdOffset], aKey^[KeyOffset], fdLength);
FFSetKeyFieldNonNull(aKey, idKeyLen, idCount, FieldNumber);
end;
inc(KeyOffset, fdLength);
end;
end;
{add the last partial field if required - must be string}
if (aLastFldLen <> 0) then begin
FieldNumber := idFields[FldCnt];
if not Dictionary.IsRecordFieldNull(FieldNumber, aData) then begin
FieldDesc := Dictionary.FieldDescriptor[FieldNumber];
with FieldDesc^ do
if (fdType >= fftShortString) then begin
if (fdType = fftWideString) then
LenToUse := sizeof(WideChar) * aLastFldLen
else
LenToUse := aLastFldLen;
if (fdType = fftShortString) or
(fdType = fftShortAnsiStr) then begin
Move(aData^[fdOffset], aKey^[KeyOffset], LenToUse+1);
aKey^[KeyOffset] := LenToUse;
end
else
Move(aData^[fdOffset], aKey^[KeyOffset], LenToUse);
FFSetKeyFieldNonNull(aKey, idKeyLen, idCount, FldCnt);
end
else
Result := DBIERR_INVALIDFLDTYPE;
end;
end;
end;
end;
{--------}
function TffSrTable.stDeleteKeyPrim(aInxFile : Integer;
aTI : PffTransInfo;
aRefNr : TffInt64;
aKey : PffByteArray;
aCompare : TffKeyCompareFunc;
aCmpData : PffCompareData;
var aBTreeChanged : Boolean) {!!.05}
: Boolean;
var
KID : TffKeyIndexData;
begin
with KID do begin
kidFI := Files[aInxFile];
kidIndex := aCmpData^.cdIndex;
kidCompare := aCompare;
kidCompareData := aCmpData;
end;
Result := FFTblDeleteKey(aTI, aKey, aRefNr, KID, aBTreeChanged); {!!.05}
end;
{--------}
function TffSrTable.stDeleteKeysForRecord(aTI : PffTransInfo;
aRefNr : TffInt64;
aData : PffByteArray;
var aBTreeChanged : Boolean) {!!.05}
: TffResult;
var
IndexNumber : integer;
IndexDscrptr: PffIndexDescriptor;
Key : PffByteArray;
BuildKey : TffKeyBuildFunc;
Compare : TffKeyCompareFunc;
CmpData : TffCompareData;
tmpBtreeChanged : Boolean; {!!.05}
begin
Result := DBIERR_NONE;
with CmpData do begin
cdDict := pointer(Dictionary);
cdIndex := 0;
cdFldCnt := 0;
cdPartLen := 0;
cdAscend := true; {for index 0}
cdNoCase := true; {for index 0}
end;
aBTreeChanged := True; {!!.05}
with Dictionary do begin
if not stDeleteKeyPrim(0, aTI, aRefNr, PffByteArray(@aRefNr),
FFKeyCompareI64, @CmpData,
tmpBTreeChanged) then begin {!!!.05}
Result := DBIERR_KEYVIOL;
Exit;
end;
aBTreeChanged := tmpBtreeChanged; {!!.05}
for IndexNumber := 1 to pred(IndexCount) do begin
IndexDscrptr := IndexDescriptor[IndexNumber];
with IndexDscrptr^ do begin
if (idCount <> -1) then begin {a composite index}
CmpData.cdIndex := IndexNumber;
CmpData.cdAscend := idAscend;
CmpData.cdNoCase := idNoCase;
CmpData.cdKeyLen := idKeyLen;
Result := stGetBuiltCompositeKey(IndexNumber, aData, idKeyLen, Key);
if (Result <> DBIERR_NONE) then
Exit;
try
if not stDeleteKeyPrim(idFile, aTI, aRefNr,
Key, FFKeyCompareComposite,
@CmpData, tmpBTreeChanged) then begin {!!.05}
Result := DBIERR_KEYVIOL;
Exit;
end;
if tmpBtreeChanged then {!!.05}
aBTreeChanged := true; {!!.05}
finally
FFFreeMem(Key, CmpData.cdKeyLen);
end;{try..finally}
end
else {a user-defined index} begin
CmpData.cdIndex := IndexNumber;
CmpData.cdAscend := idAscend;
CmpData.cdNoCase := idNoCase;
CmpData.cdKeyLen := idKeyLen;
FFGetMem(Key, CmpData.cdKeyLen);
try
BuildKey := stGetUserBuildKey(IndexNumber);
Compare := stGetUserCompareKey(IndexNumber);
if BuildKey(IndexNumber, aData, Key^, CmpData.cdKeyLen) then
if not stDeleteKeyPrim(idFile, aTI, aRefNr,
Key, Compare, @CmpData,
tmpBTreeChanged) then begin {!!.05}
Result := DBIERR_KEYVIOL;
Exit;
end;
if tmpBtreeChanged then {!!.05}
aBTreeChanged := true; {!!.05}
finally
FFFreeMem(Key, CmpData.cdKeyLen);
end;{try..finally}
end;
end;
end;
end;
end;
{--------}
function TffSrTable.stGetUserBuildKey(aIndexID : integer) : TffKeyBuildFunc;
begin
if (0 <= aIndexID) and (aIndexID < stUserBuildKey.Count) then
@Result := stUserBuildKey[aIndexID]
else
Result := nil;
end;
{--------}
function TffSrTable.stGetUserCompareKey(aIndexID : integer) : TffKeyCompareFunc;
begin
if (0 <= aIndexID) and (aIndexID < stUserCompareKey.Count) then
@Result := stUserCompareKey[aIndexID]
else
Result := nil;
end;
{--------}
function TffSrTable.stInsertKeyPrim(aInxFile: integer;
aTI : PffTransInfo;
aRefNr : TffInt64;
aKey : PffByteArray;
aCompare: TffKeyCompareFunc;
aCmpData: PffCompareData) : boolean;
var
KID : TffKeyIndexData;
begin
with KID do begin
kidFI := Files[aInxFile];
kidIndex := aCmpData^.cdIndex;
kidCompare := aCompare;
kidCompareData := aCmpData;
end;
Result := FFTblInsertKey(KID, aRefNr, aTI, aKey);
end;
{--------}
function TffSrTable.stInsertKeysForRecord(aTI : PffTransInfo;
aRefNr : TffInt64;
aData : PffByteArray) : TffResult;
var
IndexNumber : integer;
IndexDscrptr : PffIndexDescriptor;
Key : PffByteArray;
BuildKey : TffKeyBuildFunc;
Compare : TffKeyCompareFunc;
CmpData : TffCompareData;
BTreeChanged : Boolean; {!!.05}
Procedure RollBackInsertKeys(LastIndexAdded : integer);
var
IndexNumber : integer;
Key2 : PffByteArray; {!!.03}
begin
{ Remove any keys that were successfully added before the error occurred. }
with Dictionary do begin
for IndexNumber := LastIndexAdded downto 1 do begin
IndexDscrptr := IndexDescriptor[IndexNumber];
with IndexDscrptr^ do begin
if (idCount <> -1) then begin {a composite index}
CmpData.cdIndex := IndexNumber;
CmpData.cdAscend := idAscend;
CmpData.cdNoCase := idNoCase;
CmpData.cdKeyLen := idKeyLen;
Result := stGetBuiltCompositeKey(IndexNumber, aData,
idKeyLen, Key2); {!!.03}
if (Result = DBIERR_NONE) then try
stDeleteKeyPrim(idFile, aTI, aRefNr, Key2, {!!.03}
FFKeyCompareComposite, @CmpData,
BTreeChanged); {!!.05}
finally
FFFreeMem(Key2, CmpData.cdKeyLen); {!!.03}
end;{try..finally}
end
else {a user-defined index} begin
CmpData.cdIndex := IndexNumber;
CmpData.cdAscend := idAscend;
CmpData.cdNoCase := idNoCase;
CmpData.cdKeyLen := idKeyLen;
FFGetMem(Key, CmpData.cdKeyLen);
try
BuildKey := stGetUserBuildKey(IndexNumber);
Compare := stGetUserCompareKey(IndexNumber);
if BuildKey(IndexNumber, aData, Key2^, CmpData.cdKeyLen) then {!!.03}
stInsertKeyPrim(idFile, aTI, aRefNr,
Key2, Compare, @CmpData); {!!.03}
finally
FFFreeMem(Key2, CmpData.cdKeyLen); {!!.03}
end;{try..finally}
end;
end;
end;
{delete the internal RefNr key}
with CmpData do begin
cdDict := pointer(Dictionary);
cdIndex := 0;
cdFldCnt := 0;
cdPartLen := 0;
cdAscend := true; {for index 0}
cdNoCase := true; {for index 0}
end;
stDeleteKeyPrim(0, aTI, aRefNr, PffByteArray(@aRefNr),
FFKeyCompareI64, @CmpData, BTreeChanged); {!!.05}
end;
end;
begin
Result := DBIERR_NONE;
with CmpData do begin
cdDict := pointer(Dictionary);
cdIndex := 0;
cdFldCnt := 0;
cdPartLen := 0;
cdAscend := true; {for index 0}
cdNoCase := true; {for index 0}
end;
with Dictionary do begin
if not stInsertKeyPrim(0, aTI, aRefNr, PffByteArray(@aRefNr),
FFKeyCompareI64, @CmpData) then begin
Result := DBIERR_KEYVIOL;
Exit;
end;
for IndexNumber := 1 to pred(IndexCount) do begin
IndexDscrptr := IndexDescriptor[IndexNumber];
with IndexDscrptr^ do begin
if (idCount <> -1) then begin {a composite index}
CmpData.cdIndex := IndexNumber;
CmpData.cdAscend := idAscend;
CmpData.cdNoCase := idNoCase;
CmpData.cdKeyLen := idKeyLen;
Result := stGetBuiltCompositeKey(IndexNumber, aData, idKeyLen, Key);
if (Result <> DBIERR_NONE) then
Exit;
try
if not stInsertKeyPrim(idFile, aTI, aRefNr, Key,
FFKeyCompareComposite, @CmpData) then begin
// if UseInternalRollBack then {Deleted !!.11}
RollBackInsertKeys(Pred(IndexNumber));
Result := DBIERR_KEYVIOL;
Exit;
end;
finally
FFFreeMem(Key, idKeyLen); {!!.06}
end;{try..finally}
end
else {a user-defined index} begin
CmpData.cdIndex := IndexNumber;
CmpData.cdAscend := idAscend;
CmpData.cdNoCase := idNoCase;
CmpData.cdKeyLen := idKeyLen;
FFGetMem(Key, CmpData.cdKeyLen);
try
BuildKey := stGetUserBuildKey(IndexNumber);
Compare := stGetUserCompareKey(IndexNumber);
if BuildKey(IndexNumber, aData, Key^, CmpData.cdKeyLen) then
if not stInsertKeyPrim(idFile, aTI, aRefNr,
Key, Compare, @CmpData) then begin
// if UseInternalRollBack then {Deleted !!.11}
RollBackInsertKeys(Pred(IndexNumber));
Result := DBIERR_KEYVIOL;
Exit;
end;
finally
FFFreeMem(Key, CmpData.cdKeyLen);
end;{try..finally}
end;
end;
end;
end;
end;
{--------}
function TffSrTable.stUpdateKeysForRecord(aCursorID : TffCursorID;
aTI : PffTransInfo;
aRefNr : TffInt64;
aData,
aOldData : PffByteArray; {!!.05}
var aKeyChanged : Boolean) : TffResult; {!!.05}
{Reorganized !!.10}
var
IndexNumber : Integer;
CurrentIndexNum : Integer; {!!.05}
IndexDscrptr : PffIndexDescriptor;
OldKey : PffByteArray;
NewKey : PffByteArray;
CompResult : Integer;
BuildKey : TffKeyBuildFunc;
Compare : TffKeyCompareFunc;
CmpData : TffCompareData;
OldKeyBuilt : Boolean;
NewKeyBuilt : Boolean;
IndexChanged : array [1..255] of Boolean;
Procedure RollbackUpdateKeys( LastIndexUpdated : Integer;
DoLastInsertOnly : Boolean);
var
OldKey2 : PffByteArray;
NewKey2 : PffByteArray;
IndexNumber2 : Integer;
begin
for IndexNumber2 := LastIndexUpdated downto 1 do begin
IndexDscrptr := Dictionary.IndexDescriptor[IndexNumber2];
OldKey2 := nil;
NewKey2 := nil;
CmpData.cdIndex := IndexNumber2;
CmpData.cdAscend := IndexDscrptr^.idAscend;
CmpData.cdNoCase := IndexDscrptr^.idNoCase;
CmpData.cdKeyLen := IndexDscrptr^.idKeyLen;
with IndexDscrptr^ do
try
if (idCount <> -1) then begin {a composite index}
Result := stGetBuiltCompositeKey(IndexNumber2, aOldData,
idKeyLen, OldKey2);
if (Result = DBIERR_NONE) then
Result := stGetBuiltCompositeKey(IndexNumber2, aData,
idKeyLen, NewKey2);
if (Result <> DBIERR_NONE) then
Continue; {carry on with the next index in case of error}
CompResult := FFKeyCompareComposite(OldKey2^, NewKey2^, @CmpData);
if (CompResult <> 0) then begin
if (not DoLastInsertOnly) then
{Remove the NewKey on this index}
stDeleteKeyPrim(idFile, aTI, aRefNr, NewKey2,
FFKeyCompareComposite, @CmpData,
IndexChanged[IndexNumber2]); {!!.05}
{Restore the OldKey value on this index}
stInsertKeyPrim(idFile, aTI, aRefNr, OldKey2,
FFKeyCompareComposite, @CmpData);
end;
end
else {a user-defined index} begin
BuildKey := stGetUserBuildKey(IndexNumber2);
Compare := stGetUserCompareKey(IndexNumber2);
FFGetMem(OldKey2, CmpData.cdKeyLen);
FFGetMem(NewKey2, CmpData.cdKeyLen);
OldKeyBuilt := BuildKey(IndexNumber2, aOldData,
OldKey2^, CmpData.cdKeyLen);
NewKeyBuilt := BuildKey(IndexNumber2, aData,
NewKey2^, CmpData.cdKeyLen);
if OldKeyBuilt and NewKeyBuilt then
CompResult := Compare(OldKey2^, NewKey2^, @CmpData)
else if (OldKeyBuilt or NewKeyBuilt) then
CompResult := 1 {value doesn't matter so long as it's <> 0}
else
CompResult := 0;
if (CompResult <> 0) then begin
if NewKeyBuilt and (not DoLastInsertOnly) then
{Remove the NewKey on this index}
stDeleteKeyPrim(idFile, aTI, aRefNr,
NewKey2, Compare, @CmpData,
IndexChanged[IndexNumber2]); {!!.05}
if OldKeyBuilt then
{Restore the OldKey value on this index}
stInsertKeyPrim(idFile, aTI, aRefNr,
OldKey2, Compare, @CmpData);
end;
end; { if }
finally
if Assigned(NewKey2) then
FFFreeMem(NewKey2, CmpData.cdKeyLen);
if Assigned(OldKey2) then
FFFreeMem(OldKey2, CmpData.cdKeyLen);
end;{try..finally}
end; { for }
end;
begin
Result := DBIERR_NONE;
CurrentIndexNum := TffSrBaseCursor(aCursorID).IndexID; {!!.05}
aKeyChanged := False; {!!.05}
with CmpData do begin
cdDict := pointer(Dictionary);
cdFldCnt := 0;
cdPartLen := 0;
end;
with Dictionary do try
for IndexNumber := 1 to pred(IndexCount) do begin
IndexChanged[IndexNumber] := False;
IndexDscrptr := IndexDescriptor[IndexNumber];
OldKey := nil;
NewKey := nil;
CmpData.cdIndex := IndexNumber;
CmpData.cdAscend := IndexDscrptr^.idAscend;
CmpData.cdNoCase := IndexDscrptr^.idNoCase;
CmpData.cdKeyLen := IndexDscrptr^.idKeyLen;
with IndexDscrptr^ do
try
if (idCount <> -1) then begin {a composite index}
Result := stGetBuiltCompositeKey(IndexNumber, aOldData, idKeyLen, OldKey);
if (Result = DBIERR_NONE) then
Result := stGetBuiltCompositeKey(IndexNumber, aData, idKeyLen, NewKey);
if (Result <> DBIERR_NONE) then
Exit;
CompResult := FFKeyCompareComposite(OldKey^, NewKey^, @CmpData);
if (CompResult <> 0) then begin
if (IndexNumber = CurrentIndexNum) then {!!.05}
aKeyChanged := True; {!!.05}
if not stDeleteKeyPrim(idFile, aTI, aRefNr, OldKey,
FFKeyCompareComposite, @CmpData,
IndexChanged[IndexNumber]) then begin {!!.05}
Result := DBIERR_KEYVIOL;
Exit;
end;
if not stInsertKeyPrim(idFile, aTI, aRefNr, NewKey,
FFKeyCompareComposite, @CmpData) then begin
// if UseInternalRollBack then {Deleted !!.11}
RollbackUpdateKeys(IndexNumber,True);
Result := DBIERR_KEYVIOL;
Exit;
end;
IndexChanged[IndexNumber] := True; {!!.06}
end;
end
else {a user-defined index} begin
BuildKey := stGetUserBuildKey(IndexNumber);
Compare := stGetUserCompareKey(IndexNumber);
FFGetMem(OldKey, CmpData.cdKeyLen);
FFGetMem(NewKey, CmpData.cdKeyLen);
OldKeyBuilt := BuildKey(IndexNumber, aOldData, OldKey^, CmpData.cdKeyLen);
NewKeyBuilt := BuildKey(IndexNumber, aData, NewKey^, CmpData.cdKeyLen);
if OldKeyBuilt and NewKeyBuilt then
CompResult := Compare(OldKey^, NewKey^, @CmpData)
else if (OldKeyBuilt or NewKeyBuilt) then
CompResult := 1 {value doesn't matter so long as it's <> 0}
else
CompResult := 0;
if (CompResult <> 0) then begin
if (IndexNumber = CurrentIndexNum) then {!!.05}
aKeyChanged := True; {!!.05}
if OldKeyBuilt then
if not stDeleteKeyPrim(idFile, aTI, aRefNr,
OldKey, Compare, @CmpData,
IndexChanged[IndexNumber]) then begin {!!.05}
// if UseInternalRollBack then {Deleted !!.11}
RollbackUpdateKeys(Pred(IndexNumber),False);
Result := DBIERR_KEYVIOL;
Exit;
end;
if NewKeyBuilt then
if not stInsertKeyPrim(idFile, aTI, aRefNr,
NewKey, Compare, @CmpData) then begin
// if UseInternalRollBack then {Deleted !!.11}
RollbackUpdateKeys(IndexNumber,True);
Result := DBIERR_KEYVIOL;
Exit;
end;
IndexChanged[IndexNumber] := True;
end;
end; { if }
finally
if Assigned(NewKey) then
FFFreeMem(NewKey, idKeyLen);
if Assigned(OldKey) then
FFFreeMem(OldKey, idKeyLen);
end;{try..finally}
end; { for }
finally {with dictionary do try...}
{Inform other cursors at end when we are sure everything worked}
if Result = DBIERR_NONE then begin
for IndexNumber := 1 to pred(IndexCount) do
if IndexChanged[IndexNumber] then
btInformCursors(aCursorID, roModify, aRefNr, IndexNumber);
end;
end; { with dictionary do }
end;
{====================================================================}
{===TffSrSystemTable=================================================}
function TffSrSystemTable.IsServerTable : boolean;
begin
Result := True;
end;
{====================================================================}
{===TffSrTableList===================================================}
constructor TffSrTableList.Create;
begin
inherited Create;
tlList := TffThreadList.Create;
end;
{--------}
destructor TffSrTableList.Destroy;
begin
tlList.Free;
inherited Destroy;
end;
{--------}
procedure TffSrTableList.AddTable(aTable : TffSrBaseTable);
begin
tlList.Insert(aTable);
end;
{--------}
function TffSrTableList.BeginRead : TffSrTableList;
begin
tlList.BeginRead;
Result := Self;
end;
{--------}
function TffSrTableList.BeginWrite : TffSrTableList;
begin
tlList.BeginWrite;
Result := Self;
end;
{--------}
procedure TffSrTableList.DeleteTable(aTableID : Longint);
begin
tlList.Delete(aTableID);
end;
{--------}
procedure TffSrTableList.EndRead;
begin
tlList.EndRead;
end;
{--------}
procedure TffSrTableList.EndWrite;
begin
tlList.EndWrite;
end;
{--------}
function TffSrTableList.GetTableFromName(const aTableName : TffTableName) : TffSrBaseTable;
var
Inx : integer;
begin
for Inx := 0 to pred(tlList.Count) do begin
Result := TffSrTable(tlList[Inx]);
if (FFCmpShStrUC(Result.BaseName, aTableName, 255) = 0) then
Exit;
end;
Result := nil;
end;
{--------}
function TffSrTableList.GetTableItem(Find : TffListFindType; Value : Longint) : TffSrBaseTable;
var
Inx : integer;
begin
Result := nil;
if (Find = ftFromID) then begin
Inx := tlList.Index(Value);
if (Inx <> -1) then
Result := TffSrTable(tlList[Inx]);
end
else {Find = ftFromIndex} begin
if (0 <= Value) and (Value < tlList.Count) then
Result := TffSrTable(tlList[Value]);
end;
end;
{--------}
procedure TffSrTableList.RemoveIfUnused(aTable : TffSrBaseTable);
begin
{ Assumption: TableList has not been write locked by the calling routine. }
tlList.BeginWrite;
try
if (aTable.CursorList.CursorCount = 0) and
(aTable.OpenIntents = 0) then begin
aTable.Free;
end;
finally
tlList.EndWrite;
end;
end;
{--------}
procedure TffSrTableList.RemoveUnusedTables;
var
Inx : Integer;
Table : TffSrTable;
begin
{ Assumption: TableList has not been write locked by the calling routine. }
tlList.BeginWrite;
try
for Inx := pred(TableCount) downto 0 do begin
Table := TffSrTable(tlList[Inx]);
if (Table.CursorList.CursorCount = 0) and
(Table.OpenIntents = 0) then
{Begin !!.06}
try
Table.Free;
except
on E:Exception do
if FOwner <> nil then
FOwner.seForce('Exception removing unused table: %s',
[E.Message],
FOwner.bseGetReadOnly);
end;
{End !!.06}
end;
finally
tlList.EndWrite;
end;
end;
{--------}
function TffSrTableList.TableCount : integer;
begin
Result := tlList.Count;
end;
{=====================================================================}
{== TffSrDatabase ====================================================}
constructor TffSrDatabase.Create(anEngine : TffServerEngine;
aSession : TffSrSession;
aFolder : TffSrFolder;
anAlias : TffName;
aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
aTimeout : Longint;
aCheckSpace : Boolean); {!!.11}
var {!!.11}
OSVerInfo : TOSVersionInfo; {!!.11}
begin
inherited Create(aTimeout);
dbAlias := FFShStrAlloc(anAlias);
dbEngine := anEngine;
dbExtenders := nil;
soClient := aSession.Client;
dbCursorList := TffSrCursorList.Create;
dbFolder := aFolder;
// FDeadlocked := False;
dbOpenMode := aOpenMode;
dbSession := aSession;
dbShareMode := aShareMode;
dbStmtList := TffSrStmtList.Create; {!!.10}
{ Initialize the transaction information. }
FFGetZeroMem(dbTI, SizeOf(TffTransInfo));
with dbTI^ do begin
tirTrans := nil;
tirLockMgr := dbFolder.LockMgr;
end;
dbTrans := nil;
FreeOnRemove := True;
Session.DatabaseList.BeginWrite;
try
Session.DatabaseList.AddDatabase(Self);
finally
Session.DatabaseList.EndWrite;
end;
OSVerInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo); {!!.11 - Start}
if ((aCheckSpace) and
(GetVersionEx(OSVerInfo))) then
dbCheckSpace := ((OSVerInfo.dwPlatformId = VER_PLATFORM_WIN32_NT) or
((OSVerInfo.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS) and
(OSVerInfo.dwBuildNumber > 1000)))
else
dbCheckSpace := False; {!!.11 - End}
end;
{--------}
destructor TffSrDatabase.Destroy;
var
anIndex : Longint;
anExtender : TffBaseEngineExtender;
begin
{ If a transaction is active then the transaction must be rolled back. }
if assigned(dbTrans) then
dbEngine.seTransactionRollback(Self);
{ Free all registered extenders. }
if assigned(dbExtenders) then begin
for anIndex := pred(dbExtenders.Count) downto 0 do begin
anExtender := TffBaseEngineExtender
(TffIntListItem(dbExtenders[anIndex]).KeyAsInt);
anExtender.Free;
end;
dbExtenders.Free;
end;
FFShStrFree(dbAlias);
{Begin !!.10}
for anIndex := pred(dbStmtList.StmtCount) downto 0 do
dbEngine.SQLEngine.FreeStmt(dbStmtList.Stmt[ftFromIndex, anIndex].Handle);
dbStmtList.Free;
{End !!.10}
dbCursorList.Free;
Folder.DecRefCount;
dbFolder := nil;
FFFreeMem(dbTI, SizeOf(TffTransInfo));
inherited Destroy;
end;
{--------}
function TffSrDatabase.CanClose(const Mark : boolean) : boolean;
begin
CursorList.BeginRead;
dbStmtList.BeginRead; {!!.10}
try
Result := (inherited CanClose(Mark)) and {!!.06}
CursorList.HasClosableState(Mark) and {!!.06}{!!.10}
dbStmtList.CanClose(Mark); {!!.10}
finally
dbStmtList.EndRead; {!!.10}
CursorList.EndRead;
end;
end;
{--------}
procedure TffSrDatabase.ForceClose;
begin
inherited ForceClose;
{Begin !!.01}
{ If a transaction is active then the transaction must be rolled back. }
if assigned(dbTrans) then
dbEngine.seTransactionRollback(Self);
{End !!.01}
CursorList.BeginRead;
dbStmtList.BeginRead; {!!.10}
try
CursorList.ForceClose;
dbStmtList.ForceClose; {!!.10}
finally
dbStmtList.EndRead; {!!.10}
CursorList.EndRead;
end;
end;
{--------}
function TffSrDatabase.NotifyExtenders(const anAction : TffEngineAction;
const aFailAction : TffEngineAction) : TffResult;
var
anExtender : TffBaseEngineExtender;
anIndex : Longint;
anIndex2 : Longint;
begin
Result := DBIERR_NONE;
if assigned(dbExtenders) then
for anIndex := 0 to pred(dbExtenders.Count) do begin
anExtender := TffBaseEngineExtender
(TffIntListItem(dbExtenders[anIndex]).KeyAsInt);
if (anAction in anExtender.InterestedActions) or
(anExtender.InterestedActions = []) then begin
Result := anExtender.Notify(Self, anAction); {!!.06}
{since we aren't ignoring Notify's error code, we must
capture it. If an extender reports an error we will not
process the rest of the extenders and we will notify the
previous extenders that we are "undoing" the previous action}
if Result <> DBIERR_NONE then begin
for anIndex2 := 0 to pred(anIndex) do begin
anExtender := TffBaseEngineExtender
(TffIntListItem(dbExtenders[anIndex2]).KeyAsInt);
anExtender.Notify(self, aFailAction);
end;
break;
end;
end;
end;
end;
{--------}
procedure TffSrDatabase.dbAddExtender(anExtender : TffBaseEngineExtender);
var
anItem : TffIntListItem;
begin
if assigned(anExtender) then begin
if not assigned(dbExtenders) then
dbExtenders := TffThreadList.Create;
anItem := TffIntListItem.Create(Longint(anExtender));
dbExtenders.Insert(anItem);
end;
end;
{--------}
function TffSrDatabase.dbGetAlias : TffName;
begin
Result := dbAlias^;
end;
{--------}
function TffSrDatabase.dbGetTransID : TffTransID;
begin
if assigned(dbTrans) then
Result := dbTrans.TransactionID
else
Result := 0;
end;
{--------}
function TffSrDatabase.dbGetTransLSN : TffWord32;
begin
if assigned(dbTrans) then
Result := dbTrans.LSN
else
Result := 0;
end;
{Begin !!.11}
{--------}
procedure TffSrDatabase.dbSetExistingTableVersion(const Version : Longint);
begin
dbFolder.ExistingTableVersion := Version;
end;
{--------}
procedure TffSrDatabase.dbSetNewTableVersion(const Version : Longint);
begin
dbFolder.NewTableVersion := Version;
end;
{--------}
procedure TffSrDatabase.dbSetPackSrcTableVersion(const Version : Longint);
begin
dbFolder.PackSrcTableVersion := Version;
end;
{End !!.11}
{--------}
procedure TffSrDatabase.dbSetTrans(aTransaction : TffSrTransaction);
begin
dbTrans := aTransaction;
dbTI^.tirTrans := aTransaction;
end;
{--------}
function TffSrDatabase.dbGetDatabaseID : TffDatabaseID;
begin
Result := TffDatabaseID(Self);
end;
{Begin !!.03}
{--------}
procedure TffSrDatabase.RequestClose;
begin
CursorList.BeginRead;
dbStmtList.BeginRead; {!!.10}
try
inherited RequestClose;
CursorList.RequestClose;
dbStmtList.RequestClose; {!!.10}
finally
dbStmtList.EndRead; {!!.10}
CursorList.EndRead;
end;
end;
{End !!.03}
{--------}
function TffSrDatabase.ShouldClose : boolean;
{Begin !!.01}
var
aCursor : TffSrBaseCursor;
aStmt : TffBasePreparedStmt; {!!.10}
anInx : Longint;
begin
Result := inherited ShouldClose;
{ Database can close? }
if Result then begin
{ Yes. Lock the cursor list for read-only access. }
CursorList.BeginRead;
dbStmtList.BeginRead; {!!.10}
try
{ Is a transaction active? }
if assigned(dbTrans) then begin
{ Yes. See if state of all cursors will allow us to rollback the
transaction. }
{Begin !!.10}
for anInx := 0 to pred(dbStmtList.StmtCount) do begin
aStmt := dbStmtList.Stmt[ftFromIndex, anInx];
if aStmt.State <> ffosClosing then begin
Result := False;
Break;
end;
end;
if Result then
{End !!.10}
for anInx := 0 to pred(CursorList.CursorCount) do begin
aCursor := CursorList.Cursor[ftFromIndex, anInx];
if aCursor.State <> ffosClosing then begin
Result := False;
Break;
end;
end;
if Result then
dbEngine.seTransactionRollback(Self);
end
else
{ No transaction is active. See if cursors may be closed. }
Result := Result and CursorList.ShouldClose and {!!.10}
dbStmtList.ShouldClose; {!!.10}
finally
dbStmtList.EndRead; {!!.10}
CursorList.EndRead;
end;
end;
{End !!.01}
end;
{====================================================================}
{===TffSrDatabaseList================================================}
procedure TffSrDatabaseList.AddDatabase(aDatabase : TffSrDatabase);
begin
solList.Insert(aDatabase);
end;
{--------}
function TffSrDatabaseList.DatabaseCount : integer;
begin
Result := solList.Count;
end;
{--------}
procedure TffSrDatabaseList.DeleteDatabase(aDatabaseID : Longint);
begin
solList.Delete(aDatabaseID);
end;
{--------}
function TffSrDatabaseList.GetDatabaseForFolder(aFolder : TffSrFolder) : TffSrDatabase;
var
Inx : integer;
begin
for Inx := 0 to pred(solList.Count) do begin
Result := TffSrDatabase(solList[Inx]);
if (Result.Folder = aFolder) then
Exit;
end;
Result := nil;
end;
{--------}
function TffSrDatabaseList.GetDatabaseItem(Find : TffListFindType; Value : Longint) : TffSrDatabase;
var
Inx : integer;
begin
Result := nil;
if (Find = ftFromID) then begin
Inx := solList.Index(Value);
if (Inx <> -1) then
Result := TffSrDatabase(solList[Inx]);
end
else {Find = ftFromIndex} begin
if (0 <= Value) and (Value < solList.Count) then
Result := TffSrDatabase(solList[Value]);
end;
end;
{====================================================================}
{===TffSrSession===============================================}
constructor TffSrSession.Create(aClient : TffSrClient;
const aIsDef : boolean;
const aTimeout : Longint);
begin
inherited Create(aTimeout);
soClient := aClient;
ssDatabaseList := TffSrDatabaseList.Create;
ssIsDefault := aIsDef;
FreeOnRemove := true;
aClient.SessionList.BeginWrite;
try
aClient.SessionList.AddSession(Self);
finally
aClient.SessionList.EndWrite;
end;
end;
{--------}
destructor TffSrSession.Destroy;
begin
ssDatabaseList.Free;
inherited Destroy;
end;
{--------}
function TffSrSession.CanClose(const Mark : boolean) : boolean;
begin
DatabaseList.BeginRead;
try
Result := (inherited CanClose(Mark)) and DatabaseList.CanClose(Mark);
finally
DatabaseList.EndRead;
end;
end;
{--------}
procedure TffSrSession.ForceClose;
begin
inherited ForceClose;
DatabaseList.BeginRead;
try
DatabaseList.ForceClose;
finally
DatabaseList.EndRead;
end;
end;
{--------}
function TffSrSession.ssGetSessionID : TffSessionID;
begin
Result := TffSessionID(Self);
end;
{Begin !!.03}
{--------}
procedure TffSrSession.RequestClose;
begin
DatabaseList.BeginRead;
try
inherited RequestClose;
DatabaseList.RequestClose;
finally
DatabaseList.EndRead;
end;
end;
{End !!.03}
{--------}
function TffSrSession.ShouldClose : boolean;
begin
DatabaseList.BeginRead;
try
Result := (inherited ShouldClose) and DatabaseList.ShouldClose;
finally
DatabaseList.EndRead;
end;
end;
{====================================================================}
{Begin !!.10}
{===TffSrStmtList====================================================}
procedure TffSrStmtList.AddStmt(aStmt : TffBasePreparedStmt);
begin
solList.Insert(aStmt);
end;
{--------}
function TffSrStmtList.StmtCount : integer;
begin
Result := solList.Count;
end;
{--------}
procedure TffSrStmtList.DeleteStmt(aStmtID : TffSQLStmtID);
begin
solList.Delete(aStmtID);
end;
{--------}
function TffSrStmtList.GetStmt(Find : TffListFindType; Value : Longint) : TffBasePreparedStmt;
var
Inx : integer;
begin
Result := nil;
if (Find = ftFromID) then begin
Inx := solList.Index(Value);
if (Inx <> -1) then
Result := TffBasePreparedStmt(solList[Inx]);
end
else {Find = ftFromIndex} begin
if (0 <= Value) and (Value < solList.Count) then
Result := TffBasePreparedStmt(solList[Value]);
end;
end;
{--------}
procedure TffSrStmtList.RemoveForClient(const aClientID : TffClientID);
var
anInx : Longint;
begin
with solList.BeginWrite do
try
for anInx := Pred(solList.Count) downto 0 do begin
if TffBasePreparedStmt(solList[anInx]).ClientID = aClientID then
solList.DeleteAt(anInx);
end;
finally
solList.EndWrite;
end;
end;
{====================================================================}
{End !!.10}
{===TffSrSessionList====================================================}
procedure TffSrSessionList.AddSession(aSession : TffSrSession);
begin
solList.Insert(aSession);
end;
{--------}
procedure TffSrSessionList.DeleteSession(aSessionID : Longint);
begin
solList.Delete(aSessionID);
end;
{--------}
function TffSrSessionList.slGetCurSess : TffSrSession;
begin
Result := slCurSess;
end;
{--------}
function TffSrSessionList.slGetSessionItem(Find : TffListFindType; Value : Longint) : TffSrSession;
var
Inx : Longint;
begin
Result := nil;
if (Find = ftFromID) then begin
Inx := solList.Index(Value);
if (Inx <> -1) then
Result := TffSrSession(solList[Inx]);
end
else {Find = ftFromIndex}
if (0 <= Value) and (Value < solList.Count) then
Result := TffSrSession(solList[Value]);
end;
{--------}
function TffSrSessionList.SessionCount : integer;
begin
Result := solList.Count;
end;
{--------}
procedure TffSrSessionList.slSetCurSess(CS : TffSrSession);
begin
if (slCurSess = nil) then
slCurSess := slDefSess;
if (slCurSess <> CS) then
if (CS = nil) then {CS=nil means the default session}
slCurSess := slDefSess
else
slCurSess := CS;
end;
{--------}
procedure TffSrSessionList.SetDefaultSession(aSession : TffSrSession);
begin
slDefSess := aSession;
CurrentSession := nil;
end;
{====================================================================}
{===TffSrClient=====================================================}
constructor TffSrClient.Create(aClientID : Longint;
const aClientName : TffNetName;
const aTimeout : Longint;
const aClientVersion : Longint; {!!.11}
aUser : TffUserItem;
anEngine : TffServerEngine);
//var {Deleted !!.03}
// DefSess : TffSrSession; {Deleted !!.03}
begin
inherited Create(aTimeout);
clAccepted := False;
clClientName := FFShStrAlloc(aClientName);
clClientVersion := aClientVersion; {!!.11}
clEngine := anEngine;
clExtenders := nil;
soLock := TffPadLock.Create;
clSessionList := TffSrSessionList.Create;
clFirstSession := TffSrSession.Create(Self, true, timeout); {!!.03}
SessionList.BeginWrite;
try
SessionList.SetDefaultSession(clFirstSession); {!!.03}
finally
SessionList.EndWrite;
end;
FreeOnRemove := true;
{Note: we do NOT save the reference to the user object, these get
destroyed and rebuilt ad hoc}
if (aUser <> nil) then
with aUser do begin
clUserID := UserID;
clFirst := FirstName;
clLast := LastName;
clRights := Rights;
end;
end;
{--------}
destructor TffSrClient.Destroy;
var
anExtender : TffBaseEngineExtender;
anIndex : Longint;
begin
try {!!.03}
{ Notify the extenders. }
if clAccepted then
NotifyExtenders(ffeaBeforeRemoveClient, ffeaNoAction);
{ Get rid of the rebuild status info associated with this client. }
clEngine.seCleanRebuildList(ClientID);
{ Free all registered extenders. }
if assigned(clExtenders) then begin
for anIndex := pred(clExtenders.Count) downto 0 do begin
anExtender := TffBaseEngineExtender
(TffIntListItem(clExtenders[anIndex]).KeyAsInt);
anExtender.Free;
end;
clExtenders.Free;
end;
{Begin !!.03}
{ Remove any SQL prepared statements associated with this client. }
// if Assigned(clEngine.seSQLEngine) then {Deleted !!.10}
// clEngine.seSQLEngine.RemoveForClient(ClientID); {Deleted !!.10}
{End !!.03}
clSessionList.Free;
FFShStrFree(clClientName);
soLock.Free;
finally {!!.03}
inherited Destroy;
end; {!!.03}
end;
{--------}
procedure TffSrClient.AddClientExtender(anExtender : TffBaseEngineExtender);
var
anItem : TffIntListItem;
begin
if assigned(anExtender) then begin
if not assigned(clExtenders) then
clExtenders := TffThreadList.Create;
anItem := TffIntListItem.Create(Longint(anExtender));
clExtenders.Insert(anItem);
end;
end;
{--------}
function TffSrClient.CanClose(const Mark : boolean) : boolean;
begin
SessionList.BeginRead;
try
Result := (inherited CanClose(Mark)) and SessionList.CanClose(Mark);
finally
SessionList.EndRead;
end;
end;
{--------}
function TffSrClient.clGetClientID : TffClientID;
begin
result := TffClientID(Self);
end;
{--------}
procedure TffSrClient.ForceClose;
begin
inherited ForceClose;
SessionList.BeginRead;
try
SessionList.ForceClose;
finally
SessionList.EndRead;
end;
end;
{--------}
function TffSrClient.clGetClientName : TffNetName;
begin
Result := clClientName^;
end;
{--------}
function TffSrClient.NotifyExtenders(const anAction : TffEngineAction;
const aFailAction : TffEngineAction) : TffResult;
var
anExtender : TffBaseEngineExtender;
anIndex : Longint;
anIndex2 : Longint;
begin
Result := DBIERR_NONE;
if assigned(clExtenders) then
for anIndex := 0 to pred(clExtenders.Count) do begin
anExtender := TffBaseEngineExtender
(TffIntListItem(clExtenders[anIndex]).KeyAsInt);
if (anAction in anExtender.InterestedActions) or
(anExtender.InterestedActions = []) then begin
Result := anExtender.Notify(Self, anAction);
{ If an extender reports a failure, subsequent extenders will not be
notified of the action. }
if Result <> DBIERR_NONE then begin
for anIndex2 := 0 to pred(anIndex) do begin
anExtender := TffBaseEngineExtender(TffIntListItem(clExtenders[anIndex2]).KeyAsInt);
anExtender.Notify(self, aFailAction);
end;
break;
end;
end;
end;
end;
{Begin !!.03}
{--------}
procedure TffSrClient.RequestClose;
begin
SessionList.BeginRead;
try
inherited RequestClose;
SessionList.RequestClose;
finally
SessionList.EndRead;
end;
end;
{End !!.03}
{--------}
function TffSrClient.ShouldClose : boolean;
begin
SessionList.BeginRead;
try
Result := (inherited ShouldClose) and SessionList.ShouldClose;
finally
SessionList.EndRead;
end;
end;
{====================================================================}
{===TffSrClientList====================================================}
procedure TffSrClientList.AddClient(aClient : TffSrClient);
begin
solList.Insert(aClient)
end;
{--------}
function TffSrClientList.ClientCount : integer;
begin
Result := solList.Count;
end;
{--------}
procedure TffSrClientList.DeleteClient(aClientID : Longint);
begin
solList.Delete(aClientID);
end;
{--------}
function TffSrClientList.GetClientItem(Find : TffListFindType; Value : Longint) : TffSrClient;
var
Inx : integer;
begin
Result := nil;
if (Find = ftFromID) then begin
Inx := solList.Index(Value);
if (Inx <> -1) then
Result := TffSrClient(solList[Inx]);
end
else {Find = ftFromIndex}
if (0 <= Value) and (Value < solList.Count) then
Result := TffSrClient(solList[Value]);
end;
{--------}
procedure TffSrClientList.SetClientItem(Inx : integer; CI : TffSrClient);
begin
solList[Inx] := CI;
end;
{=====================================================================}
{===TffServerEngine===================================================}
constructor TffServerEngine.Create(aOwner : TComponent);
begin
inherited Create(aOwner);
CursorClass := TffSrCursor; {!!.06}
FileProcsInitialize;
seCanLog := False;
seClientHash := TffHash.Create(ffc_Size127); {!!.02}
{create the configuration object}
seConfig := TffServerConfiguration.Create;
seConfigLoaded := False;
{create the client list, the open database list, the open table
list, the transaction list}
seClientList := TffSrClientList.Create;
seSessionList := TffSrSessionList.Create;
seDatabaseList := TffSrDatabaseList.Create;
seTableList := TffSrTableList.Create;
seTableList.Owner := Self; {!!.06}
seCursorList := TffSrCursorList.Create;
seConfigDir := '';
seFolderList := TffSrFolderList.Create;
seRebuildList := TffSrRebuildStatusList.Create;
{ Create the buffer manager. Temporary storage size will be updated after
reading FFSINFO. }
seBufMgr := TffBufferManager.Create(ConfigDir, ffcl_TempStorageSize);
{ Ensure the seEvtClientDone is set to nil. }
seEvtClientDone := nil;
seOnRecoveryCheck := nil;
seScriptFile := '';
end;
{--------}
destructor TffServerEngine.Destroy;
begin
{ Tell garbage collector to end. }
if assigned(seGarbageThread) then begin
seGarbageThread.DieDieDie;
seGarbageThread.WaitFor;
seGarbageThread.Free;
end;
{ Make sure we are shutdown. }
State := ffesInactive;
FFNotifyDependents(ffn_Destroy); {!!.01}{!!.11 moved}
if Assigned(seSQLEngine) then {!!.11}
seSQLEngine.FFRemoveDependent(Self); {!!.11}
seCursorList.Free;
seTableList.Free;
seDatabaseList.Free;
seSessionList.Free;
seClientList.Free;
seFolderList.Free;
seConfig.Free;
seBufMgr.Free;
seRebuildList.Free;
seClientHash.Free; {!!.02}
inherited Destroy;
end;
{--------}
{Rewritten !!.11}
procedure TffServerEngine.FFNotificationEx(const AOp : Byte; AFrom : TffComponent;
const AData : TffWord32);
var
RecalcLogFlag : boolean;
begin
RecalcLogFlag := (AFrom = FEventLog);
inherited;
if (AFrom = seSQLEngine) and (AOp in [ffn_Destroy, ffn_Remove]) then begin
seSQLEngine.FFRemoveDependent(Self);
seSQLEngine := nil;
end;
if RecalcLogFlag then
seSetLoggingState;
end;
{--------}
procedure TffServerEngine.scInitialize;
begin
LogAll(['FF Server initializing...',
format(' Version: %5.4f %s',
[ffVersionNumber / 10000,
ffSpecialString])]);
seLoadConfig;
{Begin !!.06}
Log('Performing recovery check...');
if assigned(seOnRecoveryCheck) then
seOnRecoveryCheck(Self)
else
with FFRecoveryClass.Create do
try
Check(Self);
finally
Free;
end;
Log('Finished recovery check...');
{End !!.06}
{ Perform garbage collection? }
if Configuration.GeneralInfo^.giCollectEnabled then
{ Yes. Start the garbage collector thread. }
seGarbageThread := TffTimerThread.Create
(Configuration.GeneralInfo^.giCollectFreq,
seCollectGarbage, 0, false);
seLastFlush := GetTickCount; {!!.01}
{$IFDEF DebugDelCount}
FFTBDATA.aLog := FEventLog;
{$ENDIF}
{$IFDEF RAMPageCheck}
FFSRBASE.aLog := FEventLog;
{$ENDIF}
end;
{--------}
procedure TffServerEngine.scPrepareForShutdown;
var
aClient : TffSrClient;
ClientDoneEvent : TffEvent;
i : Integer;
begin
Log('FF Server preparing for shutdown.');
{ Kill the garbage collection thread. } {!!.01}
if assigned(seGarbageThread) then {!!.01}
seGarbageThread.DieDieDie; {!!.01}
{Begin !!.03}
{ Ask the SQL engine to get rid of any remaining prepared statements. }
if Assigned(seSQLEngine) then
seSQLEngine.RequestClose;
{End !!.03}
if ClientList.ClientCount > 0 then
{ Attempt to clear out those clients in a "closing" state. }
seCollectGarbage(0);
FFNotifyDependents(ffn_Deactivate); {!!.03}
if ClientList.ClientCount > 0 then begin
{Create an event to wait on the clients to finish what they're
doing. We will give them a chance to signal us that they're done
and then we'll just cut them off.}
ClientDoneEvent := TffEvent.Create;
try
seEvtClientDone := ClientDoneEvent;
try
ClientDoneEvent.WaitFor(ffc_ClientShutdownTime);
except
for i := Pred(ClientList.ClientCount) downto 0 do begin
aClient := ClientList.Client[ftFromIndex, i];
aClient.ForceClose;
seClientRemovePrim(aClient);
end;
end;
finally
seEvtClientDone := nil;
ClientDoneEvent.Free;
end;
end;
end;
{--------}
procedure TffServerEngine.scStartup;
begin
Log('FF Server started.');
seStartTime := GetTickCount; {!!.10}
CoCreateGUID(seUniqueID); {!!.10}
end;
{--------}
procedure TffServerEngine.scShutDown;
begin
Log('FF Server shutting down.');
end;
{--------}
procedure TffServerEngine.seCleanRebuildList(const aClientID : TffClientID);
begin
if assigned(seRebuildList) then
seRebuildList.DeleteAllForClient(aClientID);
end;
{--------}
procedure TffServerEngine.seCollectGarbage(const aTimerEventCookie : Longint);
begin
try {!!.01}
if assigned(seSQLEngine) then {!!.01}
seSQLEngine.CollectGarbage; {!!.01}
ClientList.RemoveUnused;
// SessionList.RemoveUnused; {Deleted !!.10}
// DatabaseList.RemoveUnused; {Deleted !!.10}
// CursorList.RemoveUnused; {Deleted !!.10}
TableList.RemoveUnusedTables;
FolderList.RemoveUnusedFolders;
{ Time to flush pools? } {!!.01}
if (GetTickCount - seLastFlush) >= ffcl_FlushRate then begin {!!.01}
FFLockContainerPool.Flush; {!!.01}
FFSemPool.Flush; {!!.01}
{Begin !!.05}
seBufMgr.Lock;
try
seBufMgr.FlushPools([]); {!!.01}
finally
seBufMgr.Unlock;
end;
{End !!.05}
seLastFlush := GetTickCount; {!!.01}
end;
except {!!.01}
on E:EffException do {!!.01}
seForce('Error in garbage collection: %s', {!!.01}{!!.06 - Start}
[E.Message], {!!.01}
bseGetReadOnly); {!!.01}{!!.06 - End}
end; {!!.01}
end;
{--------}
function TffServerEngine.seDatabaseAddAliasPrim(const aAlias : TffName;
const aPath : TffPath;
aCheckSpace : Boolean) {!!.11}
: TffResult;
begin
{ Assumption: Thread-safeness enforced at a higher level. }
{ Does the alias already exist? }
if seConfig.AliasList.AliasExists(aAlias) then
{ No. Return error code. }
Result := DBIERR_NAMENOTUNIQUE
else begin
{ Yes. Add the new Alias and its path. }
seConfig.AddAlias(aAlias, aPath, aCheckSpace); {!!.11}
Result := DBIERR_NONE;
end;
end;
{--------}
function TffServerEngine.seDeleteTable(const aDB : TffSrDatabase;
const aTableName : TffTableName)
: TffResult;
var
Dict : TffDataDictionary;
begin
Dict := TffDataDictionary.Create(4096);;
try
Result := seGetDictionary(aDB, aTableName, Dict);
{ Retrieved the dictionary? }
if Result = DBIERR_NONE then begin
{ Yes. Delete the files specified by the dictionary. }
FFTblHlpDelete(aDB.Folder.Path, aTableName, Dict);
Result := DBIERR_NONE;
end
else if (Result <> DBIERR_INVALIDTABLENAME) and
(Result <> DBIERR_NOSUCHTABLE) then
{ No. Assuming the result code is not one of the above errors then the
file exists but has no dictionary. Delete the data file. }
FFDeleteFile(FFMakeFullFileName(aDB.Folder.Path,
FFMakeFileNameExt(aTableName,
ffc_ExtForData)));
finally
Dict.Free;
end;
end;
{--------}
function TffServerEngine.seGetCollectFrequency : Longint;
begin
Result := Configuration.GeneralInfo^.giCollectFreq;
end;
{--------}
function TffServerEngine.seGetCollectGarbage : Boolean;
begin
Result := Configuration.GeneralInfo^.giCollectEnabled;
end;
{--------}
function TffServerEngine.seGetConfig : TffServerConfiguration;
begin
if (not seConfigLoaded) then
seLoadConfig;
Result := seConfig;
end;
{Begin !!.01}
{--------}
function TffServerEngine.seGetMaxRAM : Longint;
begin
Result := Configuration.GeneralInfo^.giMaxRAM;
end;
{End !!.01}
{--------}
function TffServerEngine.seGetScriptFile : string; {!!.11}
begin
Result := seScriptFile;
end;
{--------}
function TffServerEngine.seIsServerTable(const aTableName : TffTableName) : boolean;
var
aPrefix, aSuffix : TffTableName;
begin
Result := False;
aPrefix := Uppercase(Copy(aTableName, 1, 3));
{ Is this prefixed with characters normally used for server tables? }
if (aPrefix = ffc_SavPrefix) or
(aPrefix = ffc_StdPrefix) or
(aPrefix = ffc_TmpPrefix) then begin
aSuffix := Uppercase(Copy(aTableName, 4, 5));
Result := (aSuffix = ffc_AliasSuffix) or
(aSuffix = ffc_IndexSuffix) or
(aSuffix = ffc_InfoSuffix) or
(aSuffix = ffc_UserSuffix);
end;
end;
{--------}
function TffServerEngine.seGetDictionary(const aDB : TffSrDatabase;
const aTableName : TffTableName;
var aDict : TffDataDictionary) : TffResult;
var
Table : TffSrTable;
TableDataFile : TffFileNameExt;
begin
Result := DBIERR_NONE;
Assert(assigned(aDB));
try
Table := TffSrTable(GetTableInstance(aDB.Folder, aTableName));
if Table = nil then begin
if not FFVerifyFileName(aTableName) then begin
Result := DBIERR_INVALIDTABLENAME;
Exit;
end;
TableDataFile := FFMakeFileNameExt(aTableName, ffc_ExtForData);
if not FFFileExists(FFMakeFullFileName(aDB.Folder.Path, TableDataFile)) then begin
Result := DBIERR_NOSUCHTABLE;
Exit;
end;
Table := TffSrTable.Create(self, aTableName, aDB.Folder, seBufMgr, omReadOnly);
try
Table.OpenFiles(aDB.dbTI, seIsServerTable(aTableName), []);
aDict.Assign(Table.Dictionary);
finally
Table.Free;
end;
end else
aDict.Assign(Table.Dictionary);
except
on E: Exception do
Result := ConvertServerException(E, EventLog);
end;
end;
{--------}
function TffServerEngine.seGetServerName : TffNetName;
begin
Result := seConfig.GeneralInfo^.giServerName;
end;
{--------}
procedure TffServerEngine.seLoadConfig;
var {!!.01}
aRemainingTime : Longint; {!!.01}
begin
if (not seConfigLoaded) and {!!.03}
(not (csLoading in ComponentState)) and {!!.03}
(not (csDestroying in ComponentState)) then {!!.03}
try
aRemainingTime := FFGetRemainingTime; {!!.01}
{ Mark config as loaded. We must do this in order to avoid recursive
calls by CreateAdminUser. }
seConfigLoaded := True;
{ Read the general info. }
ReadGeneralInfo;
{ Update the buffer manager's Max RAM. } {!!.01}
seBufMgr.MaxRAM := Configuration.GeneralInfo^.giMaxRAM; {!!.01}
{ Do we need to update the temporary storage size? }
if Configuration.GeneralInfo^.giTempStoreSize <>
seBufMgr.TempStoreSize then
seBufMgr.TempStoreSize := Configuration.GeneralInfo^.giTempStoreSize;
{ Read the aliases. }
ReadAliasData;
{ Read the users. }
ReadUserData;
if (seConfig.UserList.Count = 0) then
CreateAdminUser(IsReadOnly);
{ Read the keyprocs. }
ReadKeyProcData;
{ Process alias script and full script (if present). }
ProcessAliasScript;
if seScriptFile <> '' then
ProcessFullScript(seScriptFile);
{ Save out the changes that may have been made via scripts. }
WriteGeneralInfo(false);
WriteAliasData;
FFSetRetry(aRemainingTime); {!!.01}
except
seConfigLoaded := False;
raise;
end;
end;
{--------}
procedure TffServerEngine.seSetLoggingState;
begin
seCanLog := FLogEnabled and assigned(FEventLog) and (not IsReadOnly);
end;
{--------}
procedure TffServerEngine.seSetCollectFrequency(aFreq : Longint);
begin
Configuration.GeneralInfo^.giCollectFreq := aFreq;
if not ((csLoading in ComponentState) or {!!.01}
(csDesigning in ComponentState)) then {!!.01}
WriteGeneralInfo(False);
end;
{--------}
procedure TffServerEngine.seSetCollectGarbage(aValue : Boolean);
begin
Configuration.GeneralInfo^.giCollectEnabled := aValue;
if not ((csLoading in ComponentState) or {!!.01}
(csDesigning in ComponentState)) then {!!.01}
WriteGeneralInfo(False);
end;
{--------}
procedure TffServerEngine.seSetConfigDir(const aPath : string); {!!.10}
begin
// scCheckInactive; {Deleted !!.01}
seConfigDir := aPath;
end;
{Begin !!.01}
{--------}
procedure TffServerEngine.seSetMaxRAM(const aValue : Longint);
begin
Configuration.GeneralInfo^.giMaxRAM := aValue;
seBufMgr.MaxRAM := aValue;
if not ((csLoading in ComponentState) or
(csDesigning in ComponentState)) then
WriteGeneralInfo(False);
end;
{--------}
procedure TffServerEngine.seSetScriptFile(const aFile: string); {!!.11}
begin
seScriptFile := aFile;
end;
{--------}
function TffServerEngine.seGetConfigDir : string; {!!.10}
begin
if (csDesigning in ComponentState) then
Result := seConfigDir
else
{ If we are not in design mode, then we want to make sure the Default
config dir setting is the application's path. }
if (seConfigDir = '') then begin {!!.06 - Start}
Result := FFExtractPath(Application.ExeName);
if (Result[Length(Result)] <> '\') then
Result := Result + '\';
end else {!!.06 - End}
Result := seConfigDir;
end;
{--------}
procedure TffServerEngine.seSetSQLEngine(anEngine : TffBaseSQLEngine);
begin
if seSQLEngine = anEngine then Exit;
if assigned(seSQLEngine) then
seSQLEngine.FFRemoveDependent(Self); {!!.11}
if assigned(anEngine) then
anEngine.FFAddDependent(Self); {!!.11}
seSQLEngine := anEngine;
end;
{--------}
procedure TffServerEngine.Log(const aMsg : string);
begin
if seCanLog then
FEventLog.WriteString(aMsg);
end;
{--------}
procedure TffServerEngine.LogAll(const Msgs : array of string);
begin
if seCanLog then
FEventLog.WriteStrings(Msgs);
end;
{--------}
procedure TffServerEngine.LogFmt(const aMsg : string; args : array of const);
begin
if seCanLog then
FEventLog.WriteString(format(aMsg, args));
end;
{--------}
procedure TffServerEngine.seForce(const aMsg : string; {!!.06 - Start}
args : array of const;
ReadOnly : Boolean);
begin
if ((FEventLog <> nil) and
(not ReadOnly)) then {!!.06 - End}
FEventLog.WriteString(Format(aMsg, args));
end;
{--------}
function TffServerEngine.seTransactionCommit(aDB : TffSrDatabase)
: TffResult;
var
aContainer : TffTransContainer;
aInx : Longint;
aTable : TffSrTable;
aTableList : TffPointerList;
Nested : Boolean;
Committed : Boolean; {!!.05}
begin
Committed := False; {!!.05}
{ Obtain a commit lock on all tables this transaction has modified.
We must do this to make sure the readers have finished. }
aTableList := TffPointerList.Create;
aContainer := TffTransContainer(aDB.Transaction.TransLockContainer);
Nested := aDB.Transaction.Nested;
try
if assigned(aContainer) and (not Nested) then
for aInx := 0 to pred(aContainer.ContentCount) do begin
if aContainer.ContentLockType[aInx] = ffsltExclusive then begin
aTable := TffSrTable(aContainer.ContentTable[aInx]);
aTable.BeginCommit;
aTableList.Append(Pointer(aTable));
end;
end;
Result := aDB.Folder.TransactionMgr.Commit(aDB.TransactionID, Nested);
Committed := (Result = DBIERR_NONE); {!!.05}
if (not Nested) then
aDB.Transaction := nil;
finally
if (not Nested) then
for aInx := 0 to pred(aTableList.Count) do begin
aTable := TffSrTable(aTableList.List[aInx]);
if (Committed) then {!!.05}
aTable.btCommitBLOBMgr; {!!.03}
aTable.EndCommit(aDB.DatabaseID);
end;
aTableList.Free;
end;
if (not Nested) and Committed then begin {!!.05 - Start}{!!.10}
for aInx := Pred(aDB.dbCursorList.CursorCount) downto 0 do {!!.13}
if ((TffSrBaseCursor(aDB.dbCursorList.solList.Items[aInx]) <> nil) and
(TffSrBaseCursor(aDB.dbCursorList.solList.Items[aInx]).bcCloseWTrans)) then
TffSrBaseCursor(aDB.dbCursorList.solList.Items[aInx]).RemoveIfUnused;
end; {!!.05 - End}
end;
{--------}
function TffServerEngine.seTransactionRollback(aDB : TffSrDatabase)
: TffResult;
{Rewritten !!.03}
var
aContainer : TffTransContainer;
aInx : Longint;
aTable : TffSrTable;
aTableList : TffPointerList;
Nested : Boolean;
begin
Result := DBIERR_NONE;
// Assert(assigned(aDB.Transaction));
if aDB.Transaction <> nil then begin {!!.05}
aTableList := TffPointerList.Create;
aContainer := TffTransContainer(aDB.Transaction.TransLockContainer);
Nested := aDB.Transaction.Nested;
try
{ Determine which tables were affected by the transaction. We will rollback
the changes to their BLOB mgr's in-memory deleted chain. }
if assigned(aContainer) and (not Nested) then
for aInx := 0 to pred(aContainer.ContentCount) do
if aContainer.ContentLockType[aInx] = ffsltExclusive then begin
aTable := TffSrTable(aContainer.ContentTable[aInx]);
aTableList.Append(Pointer(aTable));
end;
{ Tell the transaction manager to rollback. }
aDB.Folder.TransactionMgr.Rollback(aDB.TransactionID, Nested);
{ Nested transaction? }
if (not Nested) then begin
{ No. For each table involved, rollback the changes to the BLOB resource
manager's in-memory deleted chain. }
for aInx := 0 to pred(aTableList.Count) do begin
aTable := TffSrTable(aTableList.List[aInx]);
aTable.btRollbackBLOBMgr;
end;
aDB.Transaction := nil;
for aInx := Pred(aDB.dbCursorList.CursorCount) downto 0 do {!!.13}
if ((TffSrBaseCursor(aDB.dbCursorList.solList.Items[aInx]) <> nil) and
(TffSrBaseCursor(aDB.dbCursorList.solList.Items[aInx]).bcCloseWTrans)) then
TffSrBaseCursor(aDB.dbCursorList.solList.Items[aInx]).RemoveIfUnused;
{!!.05 - End}
end;
finally
aTableList.Free;
end;
end; {!!.05}
end;
{--------}
function TffServerEngine.BLOBCreate(aCursorID : TffCursorID;
var aBLOBNr : TffInt64) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
StartedTrans : boolean;
TransID : TffTransID;
begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if Result = DBIERR_NONE then
try
StartedTrans := False;
try
FFSetRetry(Cursor.Timeout);
if Result = DBIERR_NONE then begin
Result := Cursor.EnsureWritable(False, False); {!!.02}
if (Result = DBIERR_NOACTIVETRAN) or
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, False,
ffcl_TrImplicit, TransID);
StartedTrans := (Result = DBIERR_NONE);
end;
if (Result = DBIERR_NONE) then begin
Result := Cursor.BLOBAdd(aBLOBNr);
if StartedTrans then
if Result = DBIERR_NONE then
seTransactionCommit(Cursor.Database)
else
seTransactionRollback(Cursor.Database);
end;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.BLOBDelete(aCursorID : TffCursorID; aBLOBNr : TffInt64) : TffResult;
var
Cursor : TffSrBaseCursor;
StartedTrans : boolean;
TransID : TffTransID;
{Restructured !!.10}
begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
StartedTrans := False;
try
FFSetRetry(Cursor.Timeout);
if Result = DBIERR_NONE then begin
Result := Cursor.EnsureWritable(False, False); {!!.02}
if (Result = DBIERR_NOACTIVETRAN) or
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := (Result = DBIERR_NONE);
end;
if (Result = DBIERR_NONE) then begin
Result := Cursor.BLOBDelete(aBLOBNr);
if StartedTrans then
if Result = DBIERR_NONE then
seTransactionCommit(Cursor.Database)
else
seTransactionRollback(Cursor.Database);
end;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.BLOBFree(aCursorID : TffCursorID;
aBLOBNr : TffInt64;
ReadOnly : boolean) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
StartedTrans : boolean;
TransID : TffTransID;
begin
{ If the BLOB was opened in read-only mode then nothing to do. }
if readOnly then begin
Result := DBIERR_NONE;
Exit;
end;
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
StartedTrans := False;
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.EnsureWritable(False, False); {!!.02}
if (Result = DBIERR_NOACTIVETRAN) or
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := (Result = DBIERR_NONE);
end;
if (Result = DBIERR_NONE) then begin
Result := Cursor.BLOBFree(aBLOBNr);
if StartedTrans then
if (Result = DBIERR_NONE) or {!!.01}
(Result = DBIERR_BLOBMODIFIED) then {!!.01}
seTransactionCommit(Cursor.Database)
else
seTransactionRollback(Cursor.Database);
end; { if }
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.BLOBGetLength(aCursorID : TffCursorID; aBLOBNr : TffInt64;
var aLength : Longint) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if Result = DBIERR_NONE then
try
FFSetRetry(Cursor.Timeout);
if (Result = DBIERR_NONE) then
aLength := Cursor.BLOBGetLength(aBLOBNr, Result);
finally
Cursor.Deactivate;
end; { try..finally }
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{Begin !!.03}
{--------}
function TffServerEngine.BLOBListSegments(aCursorID : TffCursorID;
aBLOBNr : TffInt64;
aStream : TStream) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
if Result = DBIERR_NONE then
Result := Cursor.BLOBListSegments(aBLOBNr, aStream);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{End !!.03}
{--------}
function TffServerEngine.BLOBRead(aCursorID : TffCursorID;
aBLOBNr : TffInt64;
aOffset : TffWord32; {!!.06}
aLen : TffWord32; {!!.06}
var aBLOB;
var aBytesRead : TffWord32) {!!.06}
: TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
if Result = DBIERR_NONE then
Result := Cursor.BLOBRead(aBLOBNr, aOffset, aLen, aBLOB, aBytesRead);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.BLOBTruncate(aCursorID : TffCursorID;
aBLOBNr : TffInt64;
aBLOBLength : Longint) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
StartedTrans : boolean;
TransID : TffTransID;
begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if Result = DBIERR_NONE then
try
StartedTrans := False;
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.EnsureWritable(False, False); {!!.02}
if (Result = DBIERR_NOACTIVETRAN) or
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := (Result = DBIERR_NONE);
end;
if (Result = DBIERR_NONE) then begin
Result := Cursor.BLOBTruncate(aBLOBNr, aBLOBLength);
if StartedTrans then
if Result = DBIERR_NONE then
seTransactionCommit(Cursor.Database)
else
seTransactionRollback(Cursor.Database);
end; { if }
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.BLOBWrite(aCursorID : TffCursorID;
aBLOBNr : TffInt64;
aOffset : Longint;
aLen : Longint;
var aBLOB) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
StartedTrans : boolean;
TransID : TffTransID;
begin
Result := DBIERR_NONE; {!!.01 - Start}
if aLen = 0 then
Exit; {!!.01 - End}
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if Result = DBIERR_NONE then
try
StartedTrans := False;
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.EnsureWritable(False, False); {!!.02}
if (Result = DBIERR_NOACTIVETRAN) or
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := (Result = DBIERR_NONE);
end;
if (Result = DBIERR_NONE) then begin
Result := Cursor.BLOBWrite(aBLOBNr, aOffset, aLen, aBLOB);
if StartedTrans then
if Result = DBIERR_NONE then
seTransactionCommit(Cursor.Database)
else
seTransactionRollback(Cursor.Database);
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.FileBLOBAdd(aCursorID : TffCursorID;
const aFileName : TffFullFileName;
var aBLOBNr : TffInt64) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
StartedTrans : boolean;
TransID : TffTransID;
begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if Result = DBIERR_NONE then
try
StartedTrans := False;
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.EnsureWritable(False, False); {!!.02}
if (Result = DBIERR_NOACTIVETRAN) or
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := (Result = DBIERR_NONE);
end;
if (Result = DBIERR_NONE) then begin
Result := Cursor.FileBLOBAdd(aFileName, aBLOBNr);
if StartedTrans then
if Result = DBIERR_NONE then
seTransactionCommit(Cursor.Database)
else
seTransactionRollback(Cursor.Database);
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.CheckClientIDAndGet(aClientID : TffClientID;
var aClient : TffSrClient) : TffResult;
begin
if State <> ffesStarted then begin
Result := DBIERR_FF_ServerUnavail;
Exit;
end;
Result := seCheckClientIDAndGet(aClientID, aClient);
if Result = DBIERR_NONE then begin
Result := DBIERR_FF_UnknownClient;
if aClient.Activate then
Result := DBIERR_NONE;
end;
end;
{--------}
function TffServerEngine.seCheckClientIDAndGet(aClientID : TffClientID;
var aClient : TffSrClient) : TffResult;
begin
Result := DBIERR_FF_UnknownClient;
try
if TObject(aClientID) is TffSrClient then begin
aClient := TffSrClient(aClientID);
Result := DBIERR_NONE;
end;
except
{ An exception may be raised if the ID is bogus. Swallow the exception.}
end;
end;
{--------}
function TffServerEngine.CheckCursorIDAndGet(aCursorID : TffCursorID;
var aCursor : TffSrBaseCursor) : TffResult;
begin
if State <> ffesStarted then begin
Result := DBIERR_FF_ServerUnavail;
Exit;
end;
Result := seCheckCursorIDAndGet(aCursorID, aCursor);
if Result = DBIERR_NONE then begin
Result := DBIERR_FF_UnknownCursor;
if aCursor.Activate then
Result := DBIERR_NONE;
end;
end;
{--------}
function TffServerEngine.seCheckCursorIDAndGet(aCursorID : TffCursorID;
var aCursor : TffSrBaseCursor) : TffResult;
begin
Result := DBIERR_FF_UnknownCursor;
try
if TObject(aCursorID) is TffSrBaseCursor then begin
aCursor := TffSrBaseCursor(aCursorID);
Result := DBIERR_NONE;
end;
except
{ An exception may be raised if the ID is bogus. Swallow the exception.}
end;
end;
{--------}
function TffServerEngine.CheckDatabaseIDAndGet(aDatabaseID : TffDatabaseID;
var aDatabase : TffSrDatabase) : TffResult;
begin
if State <> ffesStarted then begin
Result := DBIERR_FF_ServerUnavail;
Exit;
end;
Result := seCheckDatabaseIDAndGet(aDatabaseID, aDatabase);
if Result = DBIERR_NONE then begin
Result := DBIERR_FF_UnknownDB;
if aDatabase.Activate then
Result := DBIERR_NONE;
end;
end;
{--------}
function TffServerEngine.seCheckDatabaseIDAndGet(aDatabaseID : TffDatabaseID;
var aDatabase : TffSrDatabase) : TffResult;
begin
Result := DBIERR_FF_UnknownDB;
try
if TObject(aDatabaseID) is TffSrDatabase then begin
aDatabase := TffSrDatabase(aDatabaseID);
Result := DBIERR_NONE;
end;
except
{ An exception may be raised if the ID is bogus. Swallow the exception.}
end;
end;
{--------}
function TffServerEngine.CheckTransactionIDAndGet(aTransactionID : TffTransID;
var aTrans : TffSrTransaction) : TffResult;
begin
if State <> ffesStarted then begin
Result := DBIERR_FF_ServerUnavail;
Exit;
end;
Result := DBIERR_INVALIDHNDL;
try
if TObject(aTransactionID) is TffSrTransaction then begin
aTrans := TffSrTransaction(aTransactionID);
Result := DBIERR_NONE;
end;
except
{ An exception may be raised if the ID is bogus. Swallow the exception.}
end;
end;
{Begin !!.11}
{--------}
function TffServerEngine.ClientAdd( var aClientID : TffClientID;
const aClientName : TffNetName;
const aUserID : TffName;
const aTimeout : Longint;
var aHash : TffWord32) : TffResult;
begin
Result := seClientAddPrim(aClientID, aClientName, aUserID, aTimeout,
FFVersionNumber, aHash);
end;
{--------}
function TffServerEngine.ClientAddEx(var aClientID : TffClientID;
const aClientName : TffNetName;
const aUserID : TffName;
const aTimeout : Longint;
const aClientVersion : Longint;
var aHash : TffWord32) : TffResult;
begin
Result := seClientAddPrim(aClientID, aClientName, aUserID, aTimeout,
aClientVersion, aHash);
end;
{--------}
function TffServerEngine.seClientAddPrim( var aClientID : TffClientID;
const aClientName : TffNetName;
const aUserID : TffName;
const aTimeout : Longint;
const aClientVersion : Longint;
var aHash : TffWord32) : TffResult;
var
aMonitor : TffBaseEngineMonitor;
anExtender : TffBaseEngineExtender;
anIndex : Longint;
MonitorList : TffList;
NewClient : TffSrClient;
User : TffUserItem;
begin
FFSetRetry(aTimeout); { Probably not needed but let's do it just in case. }
aClientID := ffc_NoClientID;
try
if seConfig.GeneralInfo^.giIsSecure and
(seConfig.UserList.Count <> 0) then begin
if not seConfig.UserList.UserExists(aUserID) then begin
Result := DBIERR_INVALIDUSRPASS;
Exit;
end;
with seConfig.UserList do begin
User := UserItem[UserIndex(aUserID)];
aHash := PasswordHash[aUserID];
end;
end
else begin
User := nil;
aHash := 0;
end;
NewClient := TffSrClient.Create(aClientID, aClientName, aTimeout,
aClientVersion, User, Self);
{ If there are any monitors interested in client then see if they
are interested in this client. }
MonitorList := GetInterestedMonitors(TffSrClient);
if assigned(MonitorList) then begin
for anIndex := 0 to pred(MonitorList.Count) do begin
aMonitor := TffBaseEngineMonitor
(TffIntListItem(MonitorList[anIndex]).KeyAsInt);
try
anExtender := aMonitor.Interested(NewClient);
if assigned(anExtender) then
NewClient.AddClientExtender(anExtender);
except
on E:Exception do
seForce('Monitor [%s] exception, ClientAdd: %s', {!!.06 - Start}
[aMonitor.ClassName, E.message],
bseGetReadOnly); {!!.06 - End}
end;
end;
MonitorList.Free;
end;
{ Now notify the extenders about the client. If somebody complains
then disallow the client. }
Result := NewClient.NotifyExtenders(ffeaAfterCreateClient, ffeaNoAction);
if Result <> DBIERR_NONE then begin
NewClient.Free;
exit;
end else begin
NewClient.Accepted := True;
try
ClientList.BeginWrite;
try
ClientList.AddClient(NewClient);
seClientHash.Add(NewClient.ClientID, nil); {!!.02}
finally
ClientList.EndWrite;
end;
{add the default session to our session list}
SessionList.BeginWrite;
try
{ Assumption: No need to lock NewClient.SessionList since
we have not confirmed creation of client to the client. }
SessionList.AddSession(NewClient.SessionList.Session[ftFromIndex, 0]);
finally
SessionList.EndWrite;
end;
except
NewClient.Free;
raise;
end;{try..except}
aClientID := NewClient.ClientID;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
procedure TffServerEngine.seClientRemovePrim(const aClient : TffSrClient);
begin
if aClient.CanClose(True) then begin
ClientList.DeleteClient(aClient.ClientID);
TableList.RemoveUnusedTables;
FolderList.RemoveUnusedFolders;
{If the server is waiting on us to finish, let it know we're
done so it can move on.}
if ((Assigned(seEvtClientDone)) and
(ClientList.ClientCount = 0)) then
seEvtClientDone.SignalEvent;
end else
aClient.RequestClose;
end;
{--------}
function TffServerEngine.ClientRemove(aClientID : TffClientID) : TffResult;
var
Client : TffSrClient;
begin
try
{ Note: We lock the client list because we may have 2 threads trying to
do a remove for the same client. Thread A could be processing the
RemoveClient request from the remote client while thread B could be
processing a remote client hangup (i.e., initiated from transport level).}
ClientList.BeginWrite;
try
{Begin !!.02}
{ Is the client is listed in the hash table? }
if not seClientHash.Remove(aClientID) then begin
{ No. The client has already been removed. }
Result := DBIERR_FF_UnknownClient;
Exit;
end;
{End !!.02}
{ Find the client object. Note that we will always get an exception on
the 2nd removal request for each client. The exception is swallowed
in seCheckClientIDAndGet. We get the exception because the client is
already freed. We live with the exception because we don't want to
pay the cost of doing a sequential scan through the list of clients.
This could be onerous when hundreds of clients are connected to the
server. }
Result := seCheckClientIDAndGet(aClientID, Client);
if Result = DBIERR_NONE then begin
FFSetRetry(Client.Timeout);
seClientRemovePrim(Client);
end;
finally
ClientList.EndWrite;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.ClientSetTimeout(const aClientID : TffClientID;
const aTimeout : Longint) : TffResult;
var
Client : TffSrClient;
begin
try
Result := CheckClientIDAndGet(aClientID, Client);
if Result = DBIERR_NONE then
try
Client.Timeout := aTimeout;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.seBLOBCopy(aSrc,
aTgt : TffSrBaseCursor;
aSourceBLOBNr,
aTargetBLOBNr : TffInt64;
aBuffer : Pointer;
aBufLen : Longint)
: TffResult;
var
SourceLen : Longint;
SegmentLen : Longint;
BytesRead : TffWord32; {!!.06}
Offset : Longint;
FileName : TffFullFileName;
begin
with aSrc.Table do begin
{ Assumption: Transaction has already been started by a calling routine. }
{ See if we have a file BLOB }
if FFTblGetFileNameBLOB(Files[Dictionary.BLOBFileNumber],
aSrc.Database.TransactionInfo,
aSourceBLOBNr, FileName) then begin
FFTblAddFileBLOB(Files[Dictionary.BLOBFileNumber],
aSrc.Database.TransactionInfo,
FileName, aTargetBLOBNr);
Result := DBIERR_NONE;
end
else begin
{ Otherwise copy the BLOB in segments based on the size of the
given transfer buffer }
SourceLen := aSrc.BLOBGetLength(aSourceBLOBNr, Result);
if Result <> DBIERR_NONE then Exit;
Offset := 0;
SegmentLen := FFMinI(aBufLen, SourceLen);
while Offset < SourceLen do begin
Result := aSrc.BLOBRead(aSourceBLOBNr, Offset, SegmentLen, aBuffer^,
BytesRead);
if Result <> DBIERR_NONE then Exit;
Result := aTgt.BLOBWrite(aTargetBLOBNr, Offset, BytesRead, aBuffer^);
if Result <> DBIERR_NONE then Exit;
Inc(Offset, BytesRead);
end; { while }
end;
end; { with }
end;
{--------}
function TffServerEngine.SessionAdd(const aClientID : TffClientID;
const timeout : Longint;
var aSessionID : TffSessionID) : TffResult;
var
Client : TffSrClient;
Session : TffSrSession;
begin
try
Result := CheckClientIDAndGet(aClientID, Client);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Client.Timeout); { Just in case }
// Session := TffSrSession.Create(Client, false, timeout); {Deleted !!.03}
SessionList.BeginWrite;
try
{Begin !!.03}
if Assigned(Client.clFirstSession) then begin
Session := Client.clFirstSession;
Client.clFirstSession := nil;
end else begin
Session := TffSrSession.Create(Client, false, timeout);
SessionList.AddSession(Session);
end;
{End !!.03}
finally
SessionList.EndWrite;
end;
aSessionID := Session.SessionID;
finally
Client.Deactivate;
end
else
aSessionID := 0;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{Begin !!.06}
{--------}
function TffServerEngine.SessionCloseInactiveTables(aClientID : TffClientID) : TffResult;
var
Client : TffSrClient;
begin
try
Result := CheckClientIDAndGet(aClientID, Client);
if (Result = DBIERR_NONE) then
try
TableList.RemoveUnusedTAbles;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.SessionCount(aClientID : TffClientID;
var aCount : integer) : TffResult;
var
Client : TffSrClient;
begin
try
Result := CheckClientIDAndGet(aClientID, Client);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Client.Timeout); { Just in case }
Client.SessionList.BeginRead;
try
aCount := Client.SessionList.SessionCount
finally
Client.SessionList.EndRead;
end
finally
Client.Deactivate;
end
else
aCount := 0;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.SessionGetCurrent(aClientID : TffClientID;
var aSessionID : TffSessionID) : TffResult;
var
Client : TffSrClient;
aSession : TffSrSession;
begin
try
aSessionID := 0;
Result := CheckClientIDAndGet(aClientID, Client);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Client.Timeout); { just in case }
Client.SessionList.BeginRead;
try
aSession := Client.SessionList.CurrentSession;
finally
Client.SessionList.EndRead;
end;
if assigned(aSession) then
aSessionID := aSession.SessionID;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.SessionRemove(aClientID : TffClientID;
aSessionID : TffSessionID) : TffResult;
var
Session : TffSrSession;
begin
try
Result := seCheckSessionIDAndGet(aSessionID, Session);
if (Result = DBIERR_NONE) then begin
FFSetRetry(Session.Timeout); { just in case }
if Session.CanClose(True) then begin
Session.Free;
TableList.RemoveUnusedTables;
end else
Session.RequestClose;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.SessionSetCurrent(aClientID : TffClientID;
aSessionID : TffSessionID) : TffResult;
var
Client : TffSrClient;
aSession : TffSrSession;
begin
try
Result := CheckSessionIDAndGet(aClientID, aSessionID, Client, aSession);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Client.Timeout); { just in case }
Client.SessionList.BeginWrite;
try
Client.SessionList.CurrentSession := aSession;
finally
Client.SessionList.EndWrite;
end;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.SessionSetTimeout(const aClientID : TffClientID;
const aSessionID : TffSessionID;
const aTimeout : Longint) : TffResult;
var
Client : TffSrClient;
Session : TffSrSession;
begin
try
Result := CheckSessionIDAndGet(aClientID, aSessionID, Client, Session);
if Result = DBIERR_NONE then
try
Session.Timeout := aTimeout;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorClone(aCursorID : TffCursorID;
aOpenMode : TffOpenMode;
var aNewCursorID : TffCursorID)
: TffResult;
var
aCursor, {!!.03}
aNewCursor : TffSrBaseCursor; {!!.03}
begin
try
Result := CheckCursorIDAndGet(aCursorID, aCursor);
if (Result = DBIERR_NONE) then begin {!!.06 - Start}
FFSetRetry(aCursor.Timeout);
aNewCursor := aCursor.CloneCursor(aOpenMode); {!!.03}
CursorList.BeginWrite;
try
CursorList.AddCursor(aNewCursor); {!!.03}
aNewCursorID := aNewCursor.CursorID; {!!.03}
finally
CursorList.EndWrite;
aCursor.Deactivate;
end; { try..finally }
end; { if } {!!.06 - End}
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorClose(aCursorID : TffCursorID) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := seCheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then begin
FFSetRetry(Cursor.Timeout);
if Cursor.CanClose(True) then
Cursor.Free
else
Cursor.RequestClose;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorCompareBookmarks(aCursorID : TffCursorID;
aBookmark1,
aBookmark2 : PffByteArray;
var aCompResult : Longint) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if Result = DBIERR_NONE then
Result := Cursor.CompareBookmarks(aBookmark1, aBookmark2, aCompResult);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{Begin !!.02}
{--------}
function TffServerEngine.CursorCopyRecords(aSrcCursorID,
aDestCursorID : TffCursorID;
aCopyBLOBs : Boolean) : TffResult;
var
aBLOBCopyMode : TffBLOBCopyMode;
SrcCursor,
DestCursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aSrcCursorID, SrcCursor);
if (Result = DBIERR_NONE) then
try
Result := CheckCursorIDAndGet(aDestCursorID, DestCursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(DestCursor.Timeout); {!!.10}
if aCopyBLOBs then
aBLOBCopyMode := ffbcmCopyFull
else
aBLOBCopyMode := ffbcmNoCopy;
Result := DestCursor.CopyRecords(SrcCursor, aBLOBCopyMode, nil,
0, 0);
finally
DestCursor.Deactivate;
end;
finally
SrcCursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{End !!.02}
{Begin !!.06}
{--------}
function TffServerEngine.CursorDeleteRecords(aCursorID : TffCursorID) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout); {!!.10}
Result := Cursor.DeleteRecords;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{End !!.02}
{--------}
function TffServerEngine.CursorGetBookmark(aCursorID : TffCursorID;
aBookmark : PffByteArray) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout); { just in case }
Result := Cursor.GetBookmark(aBookmark);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorGetBookmarkSize(aCursorID : TffCursorID;
var aSize : integer) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout); { just in case }
aSize := Cursor.GetBookmarkSize;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{Begin !!.03}
{--------}
function TffServerEngine.CursorListBLOBFreeSpace(aCursorID : TffCursorID;
const aInMemory : Boolean;
aStream : TStream) : TffResult;
var
Cursor : TffSrBaseCursor;
StartedTrans : Boolean;
TransID : TffTransID;
begin
StartedTrans := False;
try
{get the cursor}
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
if Cursor.Database.Transaction = nil then begin
Result := seTransactionStart(Cursor.Database, False,
ffcl_TrImplicit, TransID);
StartedTrans := (Result = DBIERR_NONE);
end;
FFSetRetry(Cursor.Timeout);
Cursor.ListBLOBFreeSpace(Cursor.Database.TransactionInfo, aInMemory,
aStream);
finally
if StartedTrans then
seTransactionRollback(Cursor.Database);
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{End !!.03}
{--------}
function TffServerEngine.CursorOverrideFilter(aCursorID : Longint;
aExpression : pCANExpr;
aTimeout : TffWord32) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
{get the cursor}
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.OverrideFilter(aExpression, aTimeout);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorResetRange(aCursorID : TffCursorID) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
if (Cursor.IndexID = 0) then
Result := DBIERR_NOASSOCINDEX
else begin
FFSetRetry(Cursor.Timeout); { just in case }
Cursor.ResetRange;
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorRestoreFilter(aCursorID : Longint) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
{get the cursor}
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.RestoreFilter;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorSetFilter(aCursorID : TffCursorID;
aExpression : pCANExpr;
aTimeout : TffWord32) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
{get the cursor}
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
if aExpression^.iTotalSize <= SizeOf(CANExpr) then {!!.01}
aExpression:= nil; {!!.01}
Result := Cursor.SetFilter(aExpression, aTimeout);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorSetRange(aCursorID : TffCursorID;
aDirectKey : boolean;
aFieldCount1 : integer;
aPartialLen1 : integer;
aKeyData1 : PffByteArray;
aKeyIncl1 : boolean;
aFieldCount2 : integer;
aPartialLen2 : integer;
aKeyData2 : PffByteArray;
aKeyIncl2 : boolean) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
if (Cursor.IndexID = 0) then
Result := DBIERR_NOASSOCINDEX
else begin
FFSetRetry(Cursor.Timeout); { just in case }
Result := Cursor.SetRange(aDirectKey,
aFieldCount1, aPartialLen1, aKeyData1, aKeyIncl1,
aFieldCount2, aPartialLen2, aKeyData2, aKeyIncl2);
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorSetTimeout(const aCursorID : TffCursorID;
const aTimeout : Longint) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if Result = DBIERR_NONE then
try
Cursor.Timeout := aTimeout;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorSetToBegin(aCursorID : TffCursorID) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Cursor.SetToBegin;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorSetToBookmark(aCursorID : TffCursorID;
aBookmark : PffByteArray) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.SetToBookmark(aBookmark);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorSetToCursor(aDestCursorID : TffCursorID; aSrcCursorID : TffCursorID) : TffResult;
var
DestCursor : TffSrBaseCursor;
SrcCursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aDestCursorID, DestCursor);
if (Result = DBIERR_NONE) then
try
Result := seCheckCursorIDAndGet(aSrcCursorID, SrcCursor);
{ We call the primitive seCheckCursorIDAndGet here because
the client was just locked by the call to get the destination
cursor. }
if (Result = DBIERR_NONE) then begin
FFSetRetry(DestCursor.Timeout);
Result := DestCursor.SetToCursor(SrcCursor);
end;
finally
DestCursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorSetToEnd(aCursorID : TffCursorID) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Cursor.SetToEnd;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorSetToKey(aCursorID : TffCursorID;
aSearchAction : TffSearchKeyAction;
aDirectKey : boolean;
aFieldCount : integer;
aPartialLen : integer;
aKeyData : PffByteArray) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
if (Cursor.IndexID = 0) then
Result := DBIERR_NOASSOCINDEX
else begin
FFSetRetry(Cursor.Timeout);
Result := Cursor.SetToKey(aSearchAction, aDirectKey,
aFieldCount, aPartialLen, aKeyData);
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.CursorSwitchToIndex(aCursorID : TffCursorID;
aIndexName : TffDictItemName;
aIndexID : integer;
aPosnOnRec : boolean) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
{get the cursor}
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
{validate the index information; if the index name is non-blank
it must exist and will supercede the index number; if the index
name is blank the index number must exist}
if (aIndexName <> '') then begin
aIndexID := Cursor.Table.Dictionary.GetIndexFromName(aIndexName);
if (aIndexID = -1) then
Result := DBIERR_NOSUCHINDEX;
end
else if (0 > aIndexID) or
(aIndexID >= Cursor.Table.Dictionary.IndexCount) then
Result := DBIERR_NOSUCHINDEX;
{switch indexes}
if (Result = DBIERR_NONE) then
if (aIndexID <> Cursor.IndexID) then begin
FFSetRetry(Cursor.Timeout);
Result := Cursor.SwitchToIndex(aIndexID, aPosnOnRec);
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.DatabaseAddAlias(const aAlias : TffName;
const aPath : TffPath;
aCheckSpace : Boolean; {!!.11}
const aClientID : TffClientID)
: TffResult;
var
Client : TffSrClient;
begin
try
Result := CheckClientIDAndGet(aClientID, Client);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Client.Timeout);
Result := Client.NotifyExtenders(ffeaBeforeDBInsert, ffeaDBInsertFail);
if (Result = DBIERR_NONE) then begin
seConfig.AliasList.BeginWrite;
try
Result := seDatabaseAddAliasPrim(aAlias,
aPath,
aCheckSpace); {!!.11}
if (Result = DBIERR_NONE) then
WriteAliasData
else
Client.NotifyExtenders(ffeaDBInsertFail, ffeaNoAction);
finally
seConfig.AliasList.EndWrite;
end;
end;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.seDatabaseAliasListPrim(aList : TList) : TffResult;
var
Inx : integer;
AliasItem : TffAliasItem;
TempDescr : PffAliasDescriptor;
begin
{ Assumption: Thread-safeness enforced at a higher level. }
Result := DBIERR_NONE;
for Inx := 0 to pred(seConfig.AliasList.Count) do begin
FFGetMem(TempDescr, sizeOf(TffAliasDescriptor));
AliasItem := seConfig.AliasList[Inx];
with AliasItem do begin
TempDescr^.adAlias := KeyAsStr;
TempDescr^.adPath := Path;
end;
aList.add(TempDescr);
end;
end;
{--------}
function TffServerEngine.DatabaseAliasList(aList : TList;
aClientID : TffClientID) : TffResult;
var
Client : TffSrClient;
begin
try
Result := CheckClientIDandGet(aClientID, Client);
if Result = DBIERR_NONE then
try
FFSetRetry(Client.Timeout);
Result := Client.NotifyExtenders(ffeaBeforeDBRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
seConfig.AliasList.BeginRead;
try
Result := seDatabaseAliasListPrim(aList);
finally
seConfig.AliasList.EndRead;
end;
end; { if }
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.RecoveryAliasList(aList : TList;
aClientID : TffClientID) : TffResult;
var
Client : TffSrClient;
begin
try
Result := seCheckClientIDandGet(aClientID, Client);
if Result = DBIERR_NONE then begin
FFSetRetry(Client.Timeout);
Result := Client.NotifyExtenders(ffeaBeforeDBRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
seConfig.AliasList.BeginRead;
try
Result := seDatabaseAliasListPrim(aList);
finally
seConfig.AliasList.EndRead;
end;
end; { if }
end; { if }
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.DatabaseChgAliasPath(aAlias : TffName;
aNewPath : TffPath;
aCheckSpace : Boolean; {!!.11}
aClientID : TffClientID)
: TffResult;
var
Client : TffSrClient;
begin
try
Result := CheckClientIDandGet(aClientID, Client);
if Result = DBIERR_NONE then
try
FFSetRetry(Client.Timeout);
{check whether the alias exists}
seConfig.AliasList.BeginWrite;
try
if not seConfig.AliasList.AliasExists(aAlias) then begin
Result := DBIERR_UNKNOWNDB;
Exit;
end;
{delete the old alias}
seConfig.AliasList.DeleteAlias(aAlias);
{add the Alias again and its new path}
seConfig.AddAlias(aAlias, aNewPath, aCheckSpace); {!!.11}
WriteAliasData;
finally
seConfig.AliasList.EndWrite;
end;
Result := DBIERR_NONE;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.DatabaseClose(aDatabaseID : TffDatabaseID) : TffResult;
var
DB : TffSrDatabase;
begin
try
Result := seCheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result = DBIERR_NONE) then begin
FFSetRetry(DB.Timeout);
{ We can free the database if there are no open cursors
& if the database is not active.
Note: We are protected by the TableOpen method's behavior.
If a table is in the process of being opened
then DB's state will be ffosActive & we won't free the
database. }
if DB.CanClose(True) then begin
DB.Free;
TableList.RemoveUnusedTables;
end else
DB.RequestClose;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.seDatabaseDeleteAliasPrim(aAlias : TffName) : TffResult;
begin
{ Assumption: Thread-safeness enforced at a higher level. }
Result := DBIERR_NONE;
{ Does the alias exist? }
if not seConfig.AliasList.AliasExists(aAlias) then
{ No. Notify client. }
Result := DBIERR_UNKNOWNDB
else
{ Delete the old alias}
seConfig.AliasList.DeleteAlias(aAlias);
end;
{--------}
function TffServerEngine.DatabaseDeleteAlias(aAlias : TffName;
aClientID : TffClientID) : TffResult;
var
Client : TffSrClient;
begin
try
Result := CheckClientIDandGet(aClientID, Client);
if Result = DBIERR_NONE then
try
FFSetRetry(Client.Timeout);
Result := Client.NotifyExtenders(ffeaBeforeDBDelete, ffeaDBDeleteFail);
if Result = DBIERR_NONE then begin
seConfig.AliasList.BeginWrite;
try
Result := seDatabaseDeleteAliasPrim(aAlias);
if Result = DBIERR_NONE then
WriteAliasData
else
Client.NotifyExtenders(ffeaDBDeleteFail, ffeaNoAction);
finally
seConfig.AliasList.EndWrite;
end;
Result := DBIERR_NONE;
end; { if }
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.DatabaseOpen(aClientID : TffClientID;
const aAlias : TffName;
const aOpenMode : TffOpenMode;
const aShareMode : TffShareMode;
const aTimeout : Longint;
var aDatabaseID : TffDatabaseID)
: TffResult;
var
aDatabase : TffSrDatabase;
aSession : TffSrSession;
Folder : TffSrFolder;
Client : TffSrClient;
DB : TffSrDatabase;
UNCPath : TffPath;
CheckSpace : Boolean; {!!.11}
begin
aDatabase := nil;
Folder := nil;
try
{the client must exist}
Result := CheckClientIDAndGet(aClientID, Client);
if (Result <> DBIERR_NONE) then
Exit;
try
FFSetRetry(Client.Timeout);
Result := Client.NotifyExtenders(ffeaBeforeDBRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
{get the current session}
Client.SessionList.BeginRead;
try
aSession := Client.SessionList.CurrentSession;
finally
Client.SessionList.EndRead;
end;
{check to see whether the Alias exists}
seConfig.AliasList.BeginRead;
try
if not seConfig.AliasList.AliasExists(aAlias) then begin
Result := DBIERR_UNKNOWNDB;
Exit;
end;
{get the Alias path}
UNCPath := seConfig.AliasList.Path[aAlias];
CheckSpace := seConfig.AliasList.CheckDiskSpace(aAlias); {!!.11}
finally
seConfig.AliasList.EndRead;
end;
{check to see whether the directory exists}
if not FFDirectoryExists(UNCPath) then begin
Result := DBIERR_INVALIDDIR;
Exit;
end;
{get a path id for this path}
FolderList.BeginWrite;
try
Folder := FolderList.AddFolder(UNCPath, IsReadOnly, seBufMgr);
finally
FolderList.EndWrite;
end;
UNCPath := Folder.Path;
{check to see whether this Alias has already been opened and in
a non-compatible state (ie we or some other client/session
wants it opened exclusively)}
DatabaseList.BeginWrite;
try
DB := DatabaseList.GetDatabaseForFolder(Folder);
if assigned(DB) then begin
if ((DB.ShareMode = smExclusive) or (aShareMode = smExclusive)) and
((TffSrClient(DB.Client).ClientID <> aClientID) or
(DB.Session <> aSession)) then begin
Result := DBIERR_NEEDEXCLACCESS;
Exit;
end;
end;
{create a new database object, add it to the global list}
aDatabase := seDatabaseOpenPrim(aSession,
Folder,
aAlias,
aOpenMode,
aShareMode,
aTimeout,
CheckSpace); {!!.11}
aDatabaseID := aDatabase.DatabaseID;
finally
DatabaseList.EndWrite;
end;
end;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
if (aDatabase <> nil) then
aDatabase.Free
else {aDatabase was never created}
if (Folder <> nil) then begin
FolderList.BeginWrite;
try
FolderList.DeleteFolderByID(Folder.FolderID);
finally
FolderList.EndWrite;
end;
end;
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.DatabaseOpenNoAlias(aClientID : TffClientID;
const aPath : TffPath;
const aOpenMode : TffOpenMode;
const aShareMode : TffShareMode;
const aTimeout : Longint;
var aDatabaseID : TffDatabaseID)
: TffResult;
var
aDatabase : TffSrDatabase;
anAlias : TffName;
aSession : TffSrSession;
Folder : TffSrFolder;
Client : TffSrClient;
DatabaseExists : Boolean;
DB : TffSrDatabase;
UNCPath : TffPath;
CheckSpace : Boolean; {!!.11}
begin
aDatabase := nil;
Folder := nil;
try
{ The path cannot be empty. }
if (aPath = '') then begin
Result := DBIERR_INVALIDDIR;
Exit;
end;
{ The client must exist. }
Result := CheckClientIDAndGet(aClientID, Client);
if (Result <> DBIERR_NONE) then
Exit;
try
FFSetRetry(Client.Timeout);
Result := Client.NotifyExtenders(ffeaBeforeDBRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
{get the current session}
Client.SessionList.BeginRead;
try
aSession := Client.SessionList.CurrentSession;
finally
Client.SessionList.EndRead;
end;
{check to see whether the directory exists}
if not FFDirectoryExists(aPath) then begin
Result := DBIERR_INVALIDDIR;
Exit;
end;
{get a folder for this path}
FolderList.BeginWrite;
try
Folder := FolderList.AddFolder(aPath, IsReadOnly, seBufMgr);
finally
FolderList.EndWrite;
end;
UNCPath := Folder.Path;
{check to see whether this path has already been opened and in
a non-compatible state (ie we or some other client/session
wants it opened exclusively)}
anAlias := '';
CheckSpace := True; {!!.11}
DatabaseList.BeginWrite;
try
DB := DatabaseList.GetDatabaseForFolder(Folder);
DatabaseExists := assigned(DB);
if DatabaseExists then begin
CheckSpace := DB.CheckSpace; {!!.11}
if ((DB.ShareMode = smExclusive) or (aShareMode = smExclusive)) and
((TffSrClient(DB.Client).ClientID <> aClientID) or
(DB.Session <> aSession)) then begin
Result := DBIERR_NEEDEXCLACCESS;
Exit;
end;
anAlias := DB.Alias;
end;
{ Create a new database object, add it to the global list. }
aDatabase := seDatabaseOpenPrim(aSession,
Folder,
anAlias,
aOpenMode,
aShareMode,
aTimeout,
CheckSpace); {!!.11}
aDatabaseID := aDatabase.DatabaseID;
finally
DatabaseList.EndWrite;
end;
end;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
if assigned(aDatabase) then
aDatabase.Free
else {database was never created}
if (Folder <> nil) then begin
FolderList.BeginWrite;
try
FolderList.DeleteFolderByID(Folder.FolderID);
finally
FolderList.EndWrite;
end;
end;
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.DatabaseSetTimeout(const aDatabaseID : TffDatabaseID;
const aTimeout : Longint) : TffResult;
var
DB : TffSrDatabase;
begin
try
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if Result = DBIERR_NONE then
try
DB.Timeout := aTimeout;
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.DatabaseTableExists(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
var aExists : Boolean) : TffResult;
var
DB : TffSrDatabase;
SearchPath : TffPath;
begin
try
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result <> DBIERR_NONE) then
Exit;
try
FFSetRetry(DB.Timeout);
Result := DB.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
SearchPath := DB.Folder.Path;
if (SearchPath[length(SearchPath)] <> '\') then
FFShStrAddChar(SearchPath, '\');
aExists := FFFileExists(SearchPath + FFForceExtension(aTableName, ffc_ExtForData));
end;
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{Begin !!.11}
{--------}
function TffServerEngine.seTableExistsPrim(aDB : TffSrDatabase;
const aTableName: TffTableName) : Boolean;
var
SearchPath : TffPath;
begin
{ The table name must be a valid file name without extension. }
if not FFVerifyFileName(aTableName) then
FFRaiseException(EffException, ffstrResServer,
fferrInvalidTableName, [aTableName]);
SearchPath := aDB.Folder.Path;
if (SearchPath[length(SearchPath)] <> '\') then
FFShStrAddChar(SearchPath, '\');
Result := FFFileExists(SearchPath +
FFForceExtension(aTableName, ffc_ExtForData));
end;
{End !!.11}
{--------}
function TffServerEngine.DatabaseTableList(aDatabaseID : TffDatabaseID;
const aMask : TffFileNameExt;
aList : TList) : TffResult;
var
DB : TffSrDatabase;
FindRes : integer;
TableDesc : PffTableDescriptor;
SearchRec : TffSearchRec;
SearchMask : TffPath;
begin
try
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result <> DBIERR_NONE) then
exit;
try
FFSetRetry(DB.Timeout);
Result := DB.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
SearchMask := DB.Folder.Path;
if (SearchMask[length(SearchMask)] <> '\') then
FFShStrAddChar(SearchMask, '\');
if (aMask = '') then begin
FFShStrConcat(SearchMask, '*.');
FFShStrConcat(SearchMask, ffc_ExtForData);
end
else begin {BEGIN !!.01}
FFShStrConcat(SearchMask, aMask);
{$IFDEF OnlyRetrieveTables}
FFForceExtension(SearchMask, ffc_ExtForData);
{$ENDIF}
end; {END !!.01}
FindRes := FFFindFirst(SearchMask, [ditFile], diaAnyAttr, SearchRec);
while (FindRes = 0) do begin
FFGetMem(TableDesc, sizeOf(TffTableDescriptor));
with SearchRec do begin
TableDesc^.tdTableName := FFExtractFileName(srName);
TableDesc^.tdExt := FFExtractExtension(srName);
TableDesc^.tdSizeLo := srSize;
TableDesc^.tdSizeHi := srSizeHigh;
TableDesc^.tdTimeStamp := srTime;
end;
aList.Add(TableDesc);
FindRes := FFFindNext(SearchRec);
end;
FFFindClose(SearchRec);
end;
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.DatabaseTableLockedExclusive(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
var aLocked : Boolean) : TffResult;
var
DB : TffSrDatabase;
Table : TffSrBaseTable;
begin
aLocked := False;
try
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result <> DBIERR_NONE) then
exit;
try
FFSetRetry(DB.Timeout);
Result := DB.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
Table := GetTableInstance(DB.Folder, aTableName);
{ Is the table open? }
if Assigned(Table) then
aLocked := Table.Folder.LockMgr.TableLockGranted(Table.TableID) = ffsltExclusive;
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.GetTableInstance(aFolder : TffSrFolder;
const aTableName : TffTableName) : TffSrBaseTable;
var
Inx : integer;
begin
{ Assumption: Calling routine has locked TableList appropriately. }
for Inx := 0 to pred(TableList.TableCount) do begin
Result := TableList[ftFromIndex, Inx];
with Result do
if (Folder = aFolder) and
(FFCmpShStrUC(BaseName, aTableName, 255) = 0) then
Exit;
end;
Result := nil;
end;
{--------}
function TffServerEngine.IndexClear(aCursorID : TffCursorID) : TffResult;
{Restructured !!.01}
var
Cursor : TffSrBaseCursor;
StartedTrans : boolean;
TransID : TffTransID;
begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
StartedTrans := false;
try
FFSetRetry(Cursor.Timeout);
{ Make sure a read-only transaction is active. }
if not assigned(Cursor.Database.Transaction) then begin
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := (Result = DBIERR_NONE);
end;
if Result = DBIERR_NONE then begin
Cursor.ClearIndex;
if StartedTrans then
seTransactionCommit(Cursor.Database);
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.IsTableNameOpen(aFolder : TffSrFolder;
const aTableName : TffTableName) : boolean;
var
Inx : integer;
begin
Result := true;
TableList.BeginRead;
try
for Inx := 0 to pred(TableList.TableCount) do
with TableList[ftFromIndex, Inx] do
if (Folder = aFolder) and
(FFCmpShStrUC(BaseName, aTableName, 255) = 0) then
Exit;
finally
TableList.EndRead;
end;
Result := false;
end;
{--------}
function TffServerEngine.seCheckSessionIDAndGet(aSessionID : TffSessionID;
var aSession : TffSrSession) : TffResult;
begin
Result := DBIERR_FF_UnknownSession;
try
if TObject(aSessionID) is TffSrSession then begin
aSession := TffSrSession(aSessionID);
Result := DBIERR_NONE;
end;
except
{ An exception may be raised if the ID is bogus. Swallow the exception.}
end;
end;
{--------}
function TffServerEngine.CheckSessionIDAndGet(aClientID : TffClientID;
aSessionID : TffSessionID;
var aClient : TffSrClient;
var aSession : TffSrSession) : TffResult;
begin
if State <> ffesStarted then begin
Result := DBIERR_FF_ServerUnavail;
Exit;
end;
Result := CheckClientIDAndGet(aClientID, aClient);
if (Result = DBIERR_NONE) then
Result := seCheckSessionIDAndGet(aSessionID, aSession);
end;
{--------}
procedure TffServerEngine.lcSetEventLog(anEventLog : TffBaseLog);
begin
inherited lcSetEventLog(anEventLog);
seSetLoggingState;
end;
{--------}
procedure TffServerEngine.lcSetLogEnabled(const aEnabled : boolean);
begin
inherited lcSetLogEnabled(aEnabled);
seSetLoggingState;
end;
{--------}
function TffServerEngine.RebuildRegister(aClientID : TffClientID;
aTotalRecords : Longint) : TffSrRebuildStatus;
begin
Result := seRebuildList.AddRebuildStatus(aClientID, aTotalRecords);
end;
{--------}
procedure TffServerEngine.RebuildDeregister(aRebuildID : Longint);
begin
seRebuildList.MarkRebuildStatusFinished(aRebuildID);
end;
{--------}
function TffServerEngine.RebuildGetStatus(aRebuildID : Longint;
const aClientID : TffClientID;
var aIsPresent : boolean;
var aStatus : TffRebuildStatus) : TffResult;
var
Client : TffSrClient;
begin
Result := seCheckClientIDAndGet(aClientID, Client);
if Result = DBIERR_NONE then begin
aIsPresent := seRebuildList.GetRebuildStatus(aRebuildID, aStatus);
Result := DBIERR_NONE;
end;
end;
{--------}
function TffServerEngine.RecordDelete(aCursorID : TffCursorID; aData : PffByteArray) : TffResult;
var
Cursor : TffSrBaseCursor;
Trans : TffSrTransaction;
TransID : TffTransID;
StartedTrans : boolean;
begin
StartedTrans := false;
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if Result = DBIERR_NONE then
try
FFSetRetry(Cursor.Timeout);
if (Result = DBIERR_NONE) then begin
Result := Cursor.EnsureWritable(True, True); {!!.02}
if (Result = DBIERR_NOACTIVETRAN) or Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := Result = DBIERR_NONE;
end;
try
if (Result = DBIERR_NONE) then begin
Result := Cursor.DeleteRecord(aData);
if (Result <> DBIERR_NONE) and not StartedTrans then begin
Trans := Cursor.Database.Transaction;
Trans.IsCorrupt := true;
end;
end;
finally
if StartedTrans then
if (Result = DBIERR_NONE) then
Result := seTransactionCommit(Cursor.Database)
else
seTransactionRollback(Cursor.Database);
end;{try..finally}
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;{try..except}
end;
{--------}
function TffServerEngine.RecordDeleteBatch(aCursorID : TffCursorID;
aBMCount : Longint;
aBMLen : Longint;
aData : PffByteArray;
aErrors : PffLongintArray
) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
TransID : TffTransID;
Offset : Longint;
IRRes : TffResult;
RecInx : integer;
StartedTrans : boolean;
begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
StartedTrans := false;
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.EnsureWritable(False, False); {!!.02}
if (Result = DBIERR_NOACTIVETRAN) or
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := true;
end;
if (Result = DBIERR_NONE) then begin
try
Offset := 0;
for RecInx := 0 to pred(aBMCount) do begin
IRRes := CursorSetToBookmark(aCursorID, {!!.10}
PffByteArray(@aData^[Offset])); {!!.10}
if IRRes = DBIERR_NONE then
IRRes := RecordDelete(aCursorID, nil);
aErrors^[RecInx] := IRRes;
inc(Offset, aBMLen);
end;
finally
if StartedTrans then
Result := seTransactionCommit(Cursor.Database);
end;{try..finally}
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.RecordExtractKey(aCursorID : TffCursorID;
aData : PffByteArray;
aKey : PffByteArray) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeRecRead, ffeaNoAction);
if Result = DBIERR_NONE then
if (Cursor.IndexID = 0) then
Result := DBIERR_NOASSOCINDEX
else begin
Result := Cursor.ExtractKey(aData, aKey);
if Result = DBIERR_NONE then
Cursor.NotifyExtenders(ffeaAfterRecRead, ffeaNoAction);
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.RecordGet(aCursorID : TffCursorID;
aLockType : TffLockType;
aData : PffByteArray) : TffResult;
var
Cursor : TffSrBaseCursor;
ServerLockType : TffSrLockType;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if Result = DBIERR_NONE then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeRecRead, ffeaNoAction);
if (Result = DBIERR_NONE) then begin
ServerLockType := FFMapLock(aLockType, false);
Result := Cursor.GetRecord(aData, ServerLockType);
if Result = DBIERR_NONE then
Cursor.NotifyExtenders(ffeaAfterRecRead, ffeaNoAction);
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.RecordGetBatch(aCursorID : TffCursorID;
aRecCount : Longint;
aRecLen : Longint;
var aRecRead : Longint;
aData : PffByteArray;
var aError : TffResult) : TffResult;
var
Cursor : TffSrBaseCursor;
Offset : Longint;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
if Result = DBIERR_NONE then begin
Offset := 0;
aError := Cursor.GetNextRecord(PffByteArray(@aData^[Offset]), ffsltNone);
if (aError = DBIERR_NONE) then
aRecRead := 1
else
aRecRead := 0;
if aError = DBIERR_FF_FilterTimeout then
Result := aError;
while (aError = DBIERR_NONE) and (aRecRead < aRecCount) do begin
inc(Offset, aRecLen);
aError := Cursor.GetNextRecord(PffByteArray(@aData^[Offset]), ffsltNone);
if (aError = DBIERR_NONE) then
inc(aRecRead);
end; {while}
end; {if}
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.RecordGetForKey(aCursorID : TffCursorID;
aDirectKey : boolean;
aFieldCount : integer;
aPartialLen : integer;
aKeyData : PffByteArray;
aData : PffByteArray;
aFirstCall : Boolean) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeRecRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
Result := Cursor.GetRecordForKey(aDirectKey,
aFieldCount,
aPartialLen,
aKeyData,
aData,
aFirstCall);
if Result = DBIERR_NONE then
Cursor.NotifyExtenders(ffeaAfterRecRead, ffeaNoAction);
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.RecordGetNext(aCursorID : TffCursorID;
aLockType : TffLockType;
aData : PffByteArray) : TffResult;
var
Cursor : TffSrBaseCursor;
ServerLockType : TffSrLockType;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeRecRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
ServerLockType := FFMapLock(aLockType, false);
Result := Cursor.GetNextRecord(aData, ServerLockType);
if Result = DBIERR_NONE then
Cursor.NotifyExtenders(ffeaAfterRecRead, ffeaNoAction);
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.RecordGetNextSeq(aCursorID : TffCursorID;
var aRefNr : TffInt64;
aData : PffByteArray) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
if Result = DBIERR_NONE then
Cursor.Table.GetNextRecordSeq(Cursor.Database.TransactionInfo, aRefNr, aData);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.RecordGetPrior(aCursorID : TffCursorID;
aLockType : TffLockType;
aData : PffByteArray) : TffResult;
var
Cursor : TffSrBaseCursor;
ServerLockType : TffSrLockType;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeRecRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
ServerLockType := FFMapLock(aLockType, false);
Result := Cursor.GetPriorRecord(aData, ServerLockType);
if Result = DBIERR_NONE then
Cursor.NotifyExtenders(ffeaAfterRecRead, ffeaNoAction);
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.RecordInsert(aCursorID : TffCursorID;
aLockType : TffLockType;
aData : PffByteArray) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
ServerLockType : TffSrLockType;
StartedTrans : boolean;
TransID : TffTransID;
begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
StartedTrans := False;
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.EnsureWritable(False, True); {!!.02}
{ Need to start an implicit transaction? }
if (Result = DBIERR_NOACTIVETRAN) or {!!.03}
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := (Result = DBIERR_NONE);
end;
// else {Deleted !!.11}
// Cursor.Table.UseInternalRollback := True; {Deleted !!.11}
// try {Deleted !!.11}
if (Result = DBIERR_NONE) then begin
ServerLockType := FFMapLock(aLockType, false);
Result := Cursor.InsertRecord(aData, ServerLockType);
end;
// finally {Deleted !!.11}
{Begin !!.05}
// Cursor.Table.UseInternalRollback := False; {Deleted !!.11}
// end;
if StartedTrans then begin
if (Result = DBIERR_NONE) then
Result := seTransactionCommit(Cursor.Database)
else
seTransactionRollback(Cursor.Database);
end;
{End !!.05}
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.RecordInsertBatch(aCursorID : TffCursorID;
aRecCount : Longint;
aRecLen : Longint;
aData : PffByteArray;
aErrors : PffLongintArray) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
TransID : TffTransID;
Offset : Longint;
IRRes : TffResult;
RecInx : integer;
StartedTrans : boolean;
begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
StartedTrans := false;
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.EnsureWritable(False, False); {!!.02}
if (Result = DBIERR_NOACTIVETRAN) or
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := true;
end;
if (Result = DBIERR_NONE) then begin
try
Offset := 0;
for RecInx := 0 to pred(aRecCount) do begin
IRRes := RecordInsert( aCursorID, ffltWriteLock,
PffByteArray(@aData^[Offset]));
aErrors^[RecInx] := IRRes;
inc(Offset, aRecLen);
end;
finally
if StartedTrans then
Result := seTransactionCommit(Cursor.Database);
end;{try..finally}
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.RecordIsLocked(aCursorID : TffCursorID; aLockType : TffLockType;
var aIsLocked : boolean) : TffResult;
var
Cursor : TffSrBaseCursor;
ServerLockType : TffSrLockType;
begin
Result := DBIERR_NONE;
aIsLocked := false;
if (aLockType = ffltNoLock) then
Exit;
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
ServerLockType := FFMapLock(aLockType, true);
aIsLocked := Cursor.IsRecordLocked(ServerLockType);
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.RecordModify(aCursorID : TffCursorID;
aData : PffByteArray;
aRelLock : boolean) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
TransID : TffTransID;
StartedTrans : boolean;
begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
StartedTrans := false;
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.EnsureWritable(True, False); {!!.02}
{ Need to start an implicit transaction? }
if (Result = DBIERR_NOACTIVETRAN) or {!!.03}
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := Result = DBIERR_NONE;
{Begin !!.03}
end;
// else {Deleted !!.11}
// Cursor.Table.UseInternalRollback := True; {Deleted !!.11}
{End !!.03}
// try {Deleted !!.11}
if (Result = DBIERR_NONE) then begin
Result := Cursor.ModifyRecord(aData, aRelLock);
end;
{Begin !!.05}
// finally {Deleted !!.11}
// Cursor.Table.UseInternalRollback := False; {!!.03}{Deleted !!.11}
// end;{try..finally} {Deleted !!.11}
if StartedTrans then begin
if (Result = DBIERR_NONE) then
Result := seTransactionCommit(Cursor.Database)
else
seTransactionRollback(Cursor.Database);
end; { if }
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
{End !!.05}
finally
Cursor.Deactivate;
end;
end;
{--------}
function TffServerEngine.RecordRelLock(aCursorID : TffCursorID; aAllLocks : boolean) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
{ Assumption: Transaction is active. }
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Cursor.RelRecordLock(aAllLocks);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.seDatabaseOpenPrim(Session : TffSrSession;
Folder : TffSrFolder;
anAlias : TffName;
aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
aTimeout : Longint;
aCheckSpace : Boolean) {!!.11}
: TffSrDatabase;
var
aMonitor : TffBaseEngineMonitor;
anExtender : TffBaseEngineExtender;
anIndex : Longint;
MonitorList : TffList;
begin
Result := TffSrDatabase.Create(Self,
Session,
Folder,
anAlias,
aOpenMode,
aShareMode,
aTimeout,
aCheckSpace); {!!.11}
{ Assumption: Calling routine has gained write access to the database list. }
DatabaseList.BeginWrite;
try
DatabaseList.AddDatabase(Result);
finally
DatabaseList.EndWrite;
end;
{ If there are any monitors interested in databases then see if they
are interested in this database. }
MonitorList := GetInterestedMonitors(TffSrDatabase);
if assigned(MonitorList) then begin
for anIndex := 0 to pred(MonitorList.Count) do begin
aMonitor := TffBaseEngineMonitor
(TffIntListItem(MonitorList[anIndex]).KeyAsInt);
try
anExtender := aMonitor.Interested(Result);
if assigned(anExtender) then
Result.dbAddExtender(anExtender);
except
on E:Exception do
seForce('Monitor [%s] exception, seDatabaseOpenPrim: %s', {!!.06 - Start}
[aMonitor.ClassName,E.message],
bseGetReadOnly); {!!.06 - End}
end;
end;
MonitorList.Free;
end;
end;
{--------}
function TffServerEngine.SQLAlloc(aClientID : TffClientID;
aDatabaseID : TffDatabaseID;
aTimeout : Longint;
var aStmtID : TffSqlStmtID) : TffResult;
var
Client : TffSrClient;
DB : TffSrDatabase;
begin
try
Result := CheckClientIDAndGet(aClientID, Client);
if Result = DBIERR_NONE then
try
Result := seCheckDatabaseIDAndGet(aDatabaseID, DB);
if Result = DBIERR_NONE then begin
FFSetRetry(5000);
Result := DB.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
if assigned(seSQLEngine) then
Result := seSQLEngine.Alloc(Self, aClientID, aDatabaseID,
aTimeout, aStmtID)
else
FFRaiseException(EffServerException, ffStrResServer,
fferrNoSQLEngine, [seGetServerName]);
end;
end;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.SQLExec(aStmtID : TffSqlStmtID;
aOpenMode : TffOpenMode;
var aCursorID : TffCursorID;
aStream : TStream) : TffResult;
begin
Result := DBIERR_NONE;
try
{ Note: Timeout set in SQLAlloc. }
if assigned(seSQLEngine) then
Result := seSQLEngine.Exec(aStmtID, aOpenMode, aCursorID, aStream)
else
FFRaiseException(EffServerException, ffStrResServer,
fferrNoSQLEngine, [seGetServerName]);
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.SQLExecDirect(aClientID : TffClientID;
aDatabaseID : TffDatabaseID;
aQueryText : PChar;
aTimeout : Longint;
aOpenMode : TffOpenMode;
var aCursorID : TffCursorID;
aStream : TStream) : TffResult;
var
Client : TffSrClient;
DB : TffSrDatabase;
begin
try
Result := CheckClientIDAndGet(aClientID, Client);
if Result = DBIERR_NONE then
try
Result := seCheckDatabaseIDAndGet(aDatabaseID, DB);
if Result = DBIERR_NONE then begin
FFSetRetry(aTimeout);
Result := DB.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
if assigned(seSQLEngine) then
Result := seSQLEngine.ExecDirect(Self, aClientID, aDatabaseID,
aQueryText, aOpenMode, aTimeout,
aCursorID, aStream)
else
FFRaiseException(EffServerException, ffStrResServer,
fferrNoSQLEngine, [seGetServerName]);
end;
end;
finally
Client.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.SQLFree(aStmtID : TffSqlStmtID) : TffResult;
begin
Result := DBIERR_NONE;
try
FFSetRetry(5000);
if assigned(seSQLEngine) then
Result := seSQLEngine.FreeStmt(aStmtID)
else
FFRaiseException(EffServerException, ffStrResServer,
fferrNoSQLEngine, [seGetServerName]);
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.SQLPrepare(aStmtID : TffSqlStmtID;
aQueryText : PChar;
aStream : TStream) : TffResult;
begin
Result := DBIERR_NONE;
try
FFSetRetry(5000);
if assigned(seSQLEngine) then
Result := seSQLEngine.Prepare(aStmtID, aQueryText, aStream)
else
FFRaiseException(EffServerException, ffStrResServer,
fferrNoSQLEngine, [seGetServerName]);
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.SQLSetParams(aStmtID : TffSqlStmtID;
aNumParams : word;
aParamDescs : Pointer;
aDataBuffer : PffByteArray;
aDataLen : integer;
aStream : TStream) : TffResult;
begin
Result := DBIERR_NONE;
try
FFSetRetry(5000);
if assigned(seSQLEngine) then
Result := seSQLEngine.SetParams(aStmtID, aNumParams, aParamDescs,
aDataBuffer, aStream)
else
FFRaiseException(EffServerException, ffStrResServer,
fferrNoSQLEngine, [seGetServerName]);
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.TableAddIndex(const aDatabaseID : TffDatabaseID;
const aCursorID : TffCursorID;
const aTableName : TffTableName;
const aIndexDesc : TffIndexDescriptor) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
DB : TffSrDatabase;
StartedTrans : boolean;
tmpCursorID : TffCursorID;
tmpTablename : string;
TransID : TffTransID;
FI : PffFileInfo;
FileHeader : PffBlockHeaderFile;
aRelMethod : TffReleaseMethod;
begin
{choice of two here: if the cursor ID is set, use it. Otherwise
use the databaseID/tablename}
if (aCursorID <> 0) then begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
StartedTrans := false;
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeAddInx, ffeaTabAddInxFail);
if Result = DBIERR_NONE then begin
tmpTableName := Cursor.Table.BaseName;
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
if (Result = DBIERR_NONE) then begin
StartedTrans := true;
Result := Cursor.AddIndexToTable(aIndexDesc);
{Begin !!.13}
if (Result = DBIERR_NONE) then begin
{update the file header}
TableList.BeginRead;
try
FI := TableList.GetTableFromName(aTableName).Files[0];
finally
TableList.EndRead;
end;
FileHeader :=
PffBlockHeaderFile(BufferManager.GetBlock(FI, 0, DB.dbTI,
True,
aRelMethod));
inc(FileHeader^.bhfIndexCount);
aRelMethod(PffBlock(FileHeader));
seTransactionCommit(Cursor.Database)
end
{End !!.13}
else begin
Cursor.NotifyExtenders(ffeaTabAddInxFail, ffeaNoAction);
seTransactionRollback(Cursor.Database)
end;
end;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end
else {use databaseID/tablename} begin
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result = DBIERR_NONE) then
try
StartedTrans := False;
try
FFSetRetry(DB.Timeout);
Result := TableOpen(aDatabaseID, aTableName,
false, '', 0, omReadWrite, smExclusive, DB.Timeout,
tmpCursorID, nil);
if (Result = DBIERR_NONE) then
try
Result := seCheckCursorIDAndGet(tmpCursorID, Cursor);
if (Result = DBIERR_NONE) then begin
Result := Cursor.NotifyExtenders(ffeaBeforeAddInx, ffeaTabAddInxFail);
if Result = DBIERR_NONE then begin
Result := seTransactionStart(DB, false, ffcl_TrImplicit,
TransID);
if (Result = DBIERR_NONE) then begin
StartedTrans := true;
Result := Cursor.AddIndexToTable(aIndexDesc);
if (Result = DBIERR_NONE) then begin
{update the file header}
TableList.BeginRead;
try
FI := TableList.GetTableFromName(aTableName).Files[0];
finally
TableList.EndRead;
end;
FileHeader :=
PffBlockHeaderFile(BufferManager.GetBlock(FI, 0, DB.dbTI,
True,
aRelMethod));
inc(FileHeader^.bhfIndexCount);
aRelMethod(PffBlock(FileHeader));
seTransactionCommit(Cursor.Database)
end else begin
Cursor.NotifyExtenders(ffeaTabAddInxFail, ffeaNoAction);
seTransactionRollback(Cursor.Database)
end;
end;
end;
end;
finally
CursorClose(tmpCursorID);
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(DB);
end;
end;{try..except}
finally
DB.Deactivate;
end;
end;
end;
{--------}
function TffServerEngine.TableBuild(aDatabaseID : TffDatabaseID;
aOverWrite : boolean;
const aTableName : TffTableName;
aForServer : boolean;
aDictionary : TffDataDictionary) : TffResult;
var
DB : TffSrDatabase;
begin
if IsReadOnly then begin {!!.01 - Start}
Result := DBIERR_READONLYDB;
Exit;
end; {!!.01 - End}
try
{the database ID must exist}
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result <> DBIERR_NONE) then
exit;
try
FFSetRetry(DB.Timeout);
Result := DB.NotifyExtenders(ffeaBeforeTabInsert, ffeaTabInsertFail);
if Result = DBIERR_NONE then begin
{the database must be open in readwrite mode}
if (DB.OpenMode = omReadOnly) then begin
Result := DBIERR_READONLYDB;
Exit;
end;
Result := seTableBuildPrim(DB, aOverwrite, aTableName, aForServer,
aDictionary);
if Result <> DBIERR_NONE then
DB.NotifyExtenders(ffeaTabInsertFail, ffeaNoAction);
end;
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.seTableBuildPrim(aDB : TffSrDatabase;
aOverwrite : boolean;
const aTableName : TffTableName;
aForServer : boolean;
aDict : TffDataDictionary) : TffResult;
var
Table : TffSrBaseTable;
TableDataFile : TffFileNameExt;
TransID : TffTransID;
begin
{ Obtain write access to the table list. Our purpose is to make sure
the table is not opened. We have to obtain write access, instead of read
access, just in case we need to call TableList.RemoveIfUnused. }
TableList.BeginWrite;
try
{ Is the table open? }
Table := GetTableInstance(aDB.Folder, aTableName);
if (Table <> nil) then begin
{ Yes. See if it can be closed. }
TableList.RemoveIfUnused(Table);
if GetTableInstance(aDB.Folder, aTableName) <> nil then begin
Result := DBIERR_TABLEOPEN;
Exit;
end;
end;
{the table name must be a valid file name without extension}
if not FFVerifyFileName(aTableName) then begin
Result := DBIERR_INVALIDTABLENAME;
Exit;
end;
{the table's data file connot exist within the database}
TableDataFile := FFMakeFileNameExt(aTableName, ffc_ExtForData);
if FFFileExists(FFMakeFullFileName(aDB.Folder.Path, TableDataFile)) then begin
if aOverWrite then begin
{we want to overwrite this table - we have to delete it first}
{table exists, is not open - we can delete the table and all files}
seDeleteTable(aDB, aTableName);
end
else begin
{table exists, and we're not going to overwrite it}
Result := DBIERR_TABLEEXISTS;
Exit;
end;
end;
{ Create the table. }
Table := TffSrTable.Create(Self, aTableName, aDB.Folder, seBufMgr,
omReadWrite);
try
{ Start a transaction. Note that if one is already active for this
database object, this will be a nested transaction. }
Result := seTransactionStart(aDB, false, ffcl_TrImplicit, TransID);
if Result <> DBIERR_NONE then
Exit;
try
{ Create files making up the table. }
Table.BuildFiles(aDB.TransactionInfo, aForServer, aDict, [], nil);
{ Commit the transaction. }
seTransactionCommit(aDB);
{ If we are in a nested transaction then the table will not have
been written out to disk. Make sure the changes are written to
disk. }
if aDB.Transaction <> nil then
Table.CommitChanges(aDB.TransactionInfo);
except
on E:Exception do begin
seTransactionRollback(aDB);
raise;
end;
end;{try..except}
finally
{ Destroy the table object. This will close all the files. }
Table.Free;
end;{try..finally}
finally
TableList.EndWrite;
end;
end;
{--------}
function TffServerEngine.seTableDeletePrim(DB : TffSrDatabase;
const aTableName : TffTableName) : TffResult;
var
Table : TffSrBaseTable;
begin
Result := DBIERR_NONE;
{ If no tablename specified then exit otherwise a lower level routine
(FFFindClose) will go into an infinite loop. }
if aTableName = '' then begin
Result := DBIERR_INVALIDTABLENAME;
exit;
end;
{ Obtain write access to the table list. This is our way of making sure
nobody opens the table in between our determining the table is NOT open
and deleting the table. }
TableList.BeginWrite;
try
{ Is the table open? }
Table := GetTableInstance(DB.Folder, aTableName);
if (Table <> nil) then begin
{ Yes. Can it be closed? }
TableList.RemoveIfUnused(Table);
if GetTableInstance(DB.Folder, aTableName) <> nil then begin
{ No. Return an error. }
Result := DBIERR_TABLEOPEN;
Exit;
end;
end;
seDeleteTable(DB, aTableName)
finally
TableList.EndWrite;
end;
end;
{--------}
function TffServerEngine.TableDelete(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName) : TffResult;
var
DB : TffSrDatabase;
begin
if IsReadOnly then begin {!!.01 - Start}
Result := DBIERR_TABLEREADONLY;
Exit;
end; {!!.01 - End}
try
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if Result = DBIERR_NONE then
try
FFSetRetry(DB.Timeout);
Result := DB.NotifyExtenders(ffeaBeforeTabDelete, ffeaTabDeleteFail);
if (Result = DBIERR_NONE) then begin
Result := seTableDeletePrim(DB, aTableName);
if Result <> DBIERR_NONE then
DB.NotifyExtenders(ffeaTabDeleteFail, ffeaNoAction);
end;
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.TableDropIndex(aDatabaseID : TffDatabaseID;
aCursorID : TffCursorID;
const aTableName : TffTableName;
const aIndexName : TffDictItemName;
aIndexID : Longint) : TffResult;
{Restructured !!.10}
var
aTable : TffSrBaseTable; {!!.02}
Cursor : TffSrBaseCursor;
DB : TffSrDatabase;
StartedTrans : boolean;
TransID : TffTransID;
begin
{ Assumption: Table has been opened for Exclusive use. This is verified
in Cursor.DropIndexFromTable. }
{choice of two here: if the cursor ID is set use that, otherwise
use the databaseID/tablename}
if (aCursorID <> 0) then begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
StartedTrans := false;
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeTabDelete, ffeaTabDeleteFail);
if Result = DBIERR_NONE then begin
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
if (Result = DBIERR_NONE) then begin
StartedTrans := true;
Result := Cursor.DropIndexFromTable(aIndexName, aIndexID);
if (Result = DBIERR_NONE) then begin
seTransactionCommit(Cursor.Database);
end else begin
Cursor.NotifyExtenders(ffeaTabDeleteFail, ffeaNoAction);
seTransactionRollback(Cursor.Database);
end; { if }
end; { if }
end; { if }
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end
else {use databaseID/tablename} begin
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if Result = DBIERR_NONE then
try
StartedTrans := false;
try
FFSetRetry(DB.Timeout);
Result := TableOpen(aDatabaseID, aTableName,
false, '', 0, omReadWrite, smExclusive, DB.Timeout,
aCursorID, nil);
if (Result = DBIERR_NONE) then
try
Result := seCheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then begin
Result := Cursor.NotifyExtenders(ffeaBeforeTabDelete, ffeaTabDeleteFail);
if Result = DBIERR_NONE then begin
Result := seTransactionStart(DB, false, ffcl_TrImplicit,
TransID);
if (Result = DBIERR_NONE) then begin
StartedTrans := true;
{Begin !!.02}
try
Result := Cursor.DropIndexFromTable(aIndexName, aIndexID);
if (Result = DBIERR_NONE) then
seTransactionCommit(Cursor.Database)
else begin
Cursor.NotifyExtenders(ffeaTabDeleteFail, ffeaNoAction);
seTransactionRollback(Cursor.Database)
end;
except
Cursor.NotifyExtenders(ffeaTabDeleteFail, ffeaNoAction);
seTransactionRollback(Cursor.Database);
StartedTrans := False;
raise;
end;
{End !!.02}
end;
end;
end; { if }
finally
aTable := Cursor.Table; {!!.02}
CursorClose(aCursorID);
TableList.RemoveIfUnused(aTable); {!!.02}
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(DB);
end;
end;{try..except}
finally
DB.Deactivate;
end;
end;
end;
{--------}
function TffServerEngine.TableEmpty(aDatabaseID : TffDatabaseID;
aCursorID : TffCursorID;
const aTableName : TffTableName) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
DB : TffSrDatabase;
Dict : TffDataDictionary;
Trans : TffSrTransaction;
TransID : TffTransID;
begin
if IsReadOnly then begin {!!.01 - Start}
Result := DBIERR_TABLEREADONLY;
Exit;
end; {!!.01 - End}
{ Choice of two here: if the cursor ID is set use that, otherwise
use the databaseID/tablename. }
if (aCursorID <> 0) then begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if Result = DBIERR_NONE then
try
Trans := nil;
try
FFSetRetry(Cursor.Timeout);
DB := Cursor.Database;
Result := Cursor.NotifyExtenders(ffeaBeforeTabDelete, ffeaTabDeleteFail);
{ Verify the cursor is writable & start an implicit transaction if
necessary. }
if (Result = DBIERR_NONE) then begin
Result := Cursor.EnsureWritable(False, False); {!!.02}
if Result = DBIERR_NOACTIVETRAN then
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
Trans := Cursor.Database.Transaction;
end;
if (Result = DBIERR_NONE) then begin
Result := Cursor.Empty;
{ If this was an implicit transaction then commit/rollback. }
if (Result = DBIERR_NONE) and Trans.IsImplicit then
seTransactionCommit(DB)
else begin
Cursor.NotifyExtenders(ffeaTabDeleteFail, ffeaNoAction);
seTransactionRollback(DB);
end; { if }
end; { if }
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if assigned(Trans) and Trans.IsImplicit then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end
else {use databaseID/tablename} begin
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if Result = DBIERR_NONE then
try
Trans := nil;
try
FFSetRetry(DB.Timeout);
Result := DB.NotifyExtenders(ffeaBeforeTabDelete, ffeaTabDeleteFail);
if Result = DBIERR_NONE then begin
Dict := TffDataDictionary.Create(4096);
try
Result := seGetDictionary(DB, aTableName, Dict);
if (Result = DBIERR_NONE) then
Result := seTableBuildPrim(DB, true, aTableName, false, Dict);
if Result <> DBIERR_NONE then
DB.NotifyExtenders(ffeaTabDeleteFail, ffeaNoAction);
finally
Dict.Free;
end;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if assigned(Trans) and Trans.IsImplicit then
seTransactionRollback(DB);
end;
end;{try..except}
finally
DB.Deactivate;
end;
end;
end;
{--------}
function TffServerEngine.TableGetAutoInc(aCursorID : TffCursorID;
var aValue : TffWord32) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if (Result = DBIERR_NONE) then
Cursor.ReadAutoIncValue(aValue);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.TableGetDictionary(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
aForServer : boolean;
aStream : TStream) : TffResult;
var
DB : TffSrDatabase;
Dict : TffDataDictionary;
begin
try
{the database ID must exist}
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result <> DBIERR_NONE) then
exit;
try
FFSetRetry(DB.Timeout);
Result := DB.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
{ We must obtain write access on the engine's table list. Why?
Because another thread may be looking for the table at the same
time. If the table has not been opened, we don't want that thread
to open the table while we are opening the table. }
Dict := TffServerDataDict.Create(4096);
TableList.BeginWrite;
try
Result := seGetDictionary(DB, aTableName, Dict);
if Result = DBIERR_NONE then
Dict.WriteToStream(aStream);
finally
TableList.EndWrite;
Dict.Free;
end;
end;
finally
DB.Deactivate;
end;
except
on E : Exception do
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;{try..except}
end;
{--------}
function TffServerEngine.TableGetRecCount(aCursorID : TffCursorID;
var aRecCount : Longint) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if Result = DBIERR_NONE then
Result := Cursor.GetRecordCount(aRecCount);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.TableIsLocked(aCursorID : TffCursorID;
aLockType : TffLockType;
var aIsLocked : boolean) : TffResult;
var
Cursor : TffSrBaseCursor;
ServerLockType : TffSrLockType;
begin
Result := DBIERR_NONE;
aIsLocked := false;
if (aLockType = ffltNoLock) then
Exit;
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
ServerLockType := FFMapLock(aLockType, true);
aIsLocked := Cursor.Table.HasLock(Cursor.CursorID, ServerLockType);
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.TableLockAcquire(aCursorID : TffCursorID;
aLockType : TffLockType) : TffResult;
var
Cursor : TffSrBaseCursor;
ServerLockType : TffSrLockType;
begin
Result := DBIERR_NONE;
if (aLockType = ffltNoLock) then
Exit;
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Result := Cursor.NotifyExtenders(ffeaBeforeTableLock, ffeaTableLockFail);
if Result = DBIERR_NONE then
try
ServerLockType := FFMapLock(aLockType, True);
Cursor.Table.AcqClientLock(aCursorID, ServerLockType, False);
Cursor.NotifyExtenders(ffeaAfterTableLock, ffeaNoAction);
except
Cursor.NotifyExtenders(ffeaTableLockFail, ffeaNoAction);
raise;
end;
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.TableLockRelease(aCursorID : TffCursorID; aAllLocks : Boolean) : TffResult;
var
Cursor : TffSrBaseCursor;
begin
try
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
FFSetRetry(Cursor.Timeout);
Cursor.Table.RelClientLock(aCursorID, aAllLocks);
finally
Cursor.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.TableOpen(const aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
const aForServer : Boolean;
const aIndexName : TffName;
aIndexID : Longint;
const aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
const aTimeout : Longint;
var aCursorID : TffCursorID;
aStream : TStream)
: TffResult;
var
Cursor : TffSrBaseCursor; {!!.06}
DB : TffSrDatabase;
IndexID : Longint;
OpenMode : TffOpenMode;
begin
try
{ The database must exist. }
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result <> DBIERR_NONE) then
exit;
try
FFSetRetry(DB.Timeout);
Result := DB.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
{ Change the open mode to ReadOnly if the Server is ReadOnly. }
if Result = DBIERR_NONE then begin
if seConfig.GeneralInfo^.giReadOnly then
OpenMode := omReadOnly
else
OpenMode := aOpenMode;
{ The database and table open and share modes must 'match'. }
if (DB.OpenMode = omReadOnly) and (OpenMode <> omReadOnly) then begin
Result := DBIERR_READONLYDB;
Exit;
end;
if (DB.ShareMode = smExclusive) then
aShareMode := smExclusive;
{ Create a cursor for the table and return it, add it to the
server's cursor list. }
Cursor := CursorClass.Create(Self, DB, aTimeout); {!!.06}
try
Cursor.Open(aTableName, aIndexName, aIndexID, OpenMode, aShareMode,
aForServer, False, []);
CursorList.BeginWrite;
try
CursorList.AddCursor(Cursor);
finally
CursorList.EndWrite;
end;
{ Get the cursor ID. }
aCursorID := Cursor.CursorID;
{ Write the information out to the stream - caller's responsibility to
create and destroy the stream - also to rewind it. }
if (aStream <> nil) then begin
{ First, the cursor ID. }
aStream.Write(aCursorID, sizeof(aCursorID));
{ Next, the data dictionary. }
Cursor.Dictionary.WriteToStream(aStream);
{ Finally the IndexID for the cursor. }
IndexID := Cursor.IndexID;
aStream.Write(IndexID, sizeof(IndexID));
end;
except
Cursor.Free;
raise;
end;
end; { if }
finally
DB.Deactivate;
end;
except
on E : Exception do
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;{try..except}
end;
{--------}
function TffServerEngine.seTableRenamePrim(DB : TffSrDatabase;
const aOldName, aNewName : TffName) : TffResult;
var
Dict : TffDataDictionary;
Table : TffSrBaseTable;
begin
Dict := TffDataDictionary.Create(4096);
TableList.BeginWrite;
try
{ Is the table open? }
Table := GetTableInstance(DB.Folder, aOldName);
if (Table <> nil) then begin
{ Yes. Can it be closed? }
TableList.RemoveIfUnused(Table);
if GetTableInstance(DB.Folder, aOldName) <> nil then begin
{ No. Return an error. }
Result := DBIERR_TABLEOPEN;
Exit;
end;
end;
Result := seGetDictionary(DB, aOldName, Dict);
{ Retrieved the dictionary? }
if Result = DBIERR_NONE then begin
{ Yes. Delete the files specified by the dictionary. }
FFTblHlpRename(DB.Folder.Path, aOldName, aNewName, Dict);
Result := DBIERR_NONE;
end
finally
TableList.EndWrite;
Dict.Free;
end;
end;
{--------}
function TffServerEngine.TableRename(aDatabaseID : TffDatabaseID;
const aOldName, aNewName : TffName) : TffResult;
var
DB : TffSrDatabase;
begin
try
{ The table name must be a valid file name without extension. }
if not FFVerifyFileName(aNewName) then begin
Result := DBIERR_INVALIDTABLENAME;
Exit;
end;
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result = DBIERR_NONE) then
try
FFSetRetry(DB.Timeout);
Result := DB.NotifyExtenders(ffeaBeforeTabUpdate, ffeaTabUpdateFail);
if Result = DBIERR_NONE then begin
Result := seTableRenamePrim(DB, aOldName, aNewName);
if Result <> DBIERR_NONE then
DB.NotifyExtenders(ffeaTabUpdateFail, ffeaNoAction);
end;
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.TableSetAutoInc(aCursorID : TffCursorID;
aValue : TffWord32) : TffResult;
{Restructured !!.10}
var
Cursor : TffSrBaseCursor;
StartedTrans: Boolean;
TransID: TffTransID;
begin
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if (Result = DBIERR_NONE) then
try
StartedTrans := false;
try
FFSetRetry(Cursor.Timeout);
StartedTrans := False;
Result := Cursor.NotifyExtenders(ffeaBeforeTabUpdate, ffeaTabUpdateFail);
if Result = DBIERR_NONE then begin
Result := Cursor.EnsureWritable(False, False); {!!.02}
if (Result = DBIERR_NOACTIVETRAN) or
Cursor.NeedNestedTransaction then begin {!!.03}
Result := seTransactionStart(Cursor.Database, false,
ffcl_TrImplicit, TransID);
StartedTrans := (Result = DBIERR_NONE);
end;
if (Result = DBIERR_NONE) then begin
try
Cursor.SetAutoIncValue(aValue);
except
Cursor.NotifyExtenders(ffeaTabUpdateFail, ffeaNoAction);
raise;
end;
if StartedTrans then
Result := seTransactionCommit(Cursor.Database);
end;
end; { if }
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
if StartedTrans then
seTransactionRollback(Cursor.Database);
end;
end;{try..except}
finally
Cursor.Deactivate;
end;
end;
{--------}
{Begin !!.11}
function TffServerEngine.TableVersion(aDatabaseID : TffDatabaseID;
const aTableName : TffTableName;
var aVersion : Longint) : TffResult;
var
DB : TffSrDatabase;
FI : TffFileInfo;
FileHandle : THandle;
Table : TffSrBaseTable;
TableDataFile : TffFullFileName;
PTableDataFile : PAnsiChar;
Header : TffBlockHeaderFile;
begin
PTableDataFile := nil;
try
{the database ID must exist}
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result <> DBIERR_NONE) then
exit;
try
FFSetRetry(DB.Timeout);
Result := DB.NotifyExtenders(ffeaBeforeTabRead, ffeaNoAction);
if Result = DBIERR_NONE then begin
{ If the table is already open then return the version number from the
internal file data structure. Otherwise, open the main file for the
table & retrieve the version number from its header block. }
seTableList.BeginWrite;
try
{ Try & find the open table in the engine's table list. If it exists already
then reference the existing table. }
Table := GetTableInstance(DB.Folder, aTableName);
{ Is the table open? }
if assigned(Table) then
{ Yes. Return version # from in-memory information. }
aVersion := Table.Files[0].fiFFVersion
else if seTableExistsPrim(DB, aTableName) then begin
{ Table exists. Open the file directly & retrieve the version number
from its header block. }
TableDataFile := FFMakeFullFileName
(DB.Folder.Path,
FFMakeFileNameExt(aTableName, ffc_ExtForData));
FFGetMem(PTableDataFile, Length(TableDataFile) + 1);
StrPCopy(PTableDataFile, TableDataFile);
FileHandle := FFOpenFilePrim(PTableDataFile, omReadOnly,
smShareRead, False, False);
try
FI.fiHandle := FileHandle;
FI.fiName := FFShStrAlloc(TableDataFile);
FFReadFilePrim(@FI, SizeOf(TffBlockHeaderFile), Header);
aVersion := Header.bhfFFVersion;
finally
FFCloseFilePrim(@FI);
FFShStrFree(FI.fiName);
end;
end
else
{ The file does not exist. Raise an error. }
FFRaiseException(EffException, ffstrResServer, fferrUnknownTable,
[aTableName, DB.Alias]);
finally
if PTableDataFile <> nil then
FFFreeMem(PTableDataFile, StrLen(PTableDataFile) + 1);
seTableList.EndWrite;
end;
end; { if }
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{End !!.11}
{--------}
function TffServerEngine.seConvertSingleField(aSourceBuf,
aTargetBuf: PffByteArray;
aSourceCursorID,
aTargetCursorID: Longint;
aSourceFldNr,
aTargetFldNr: Integer;
aBLOBBuffer: Pointer;
aBLOBBufLen: Longint): TffResult;
var
SourceValue: Pointer;
TargetValue: Pointer;
SourceType: TffFieldType;
TargetType: TffFieldType;
SourceLength: Longint;
TargetLength: Longint;
SourceCursor,
TargetCursor: TffSrBaseCursor;
begin
Result := DBIERR_NONE;
try
seCheckCursorIDAndGet(aSourceCursorID, SourceCursor);
seCheckCursorIDAndGet(aTargetCursorID, TargetCursor);
SourceValue := nil;
TargetValue := nil;
with SourceCursor.Table.Dictionary do begin
if Assigned(aSourceBuf) then begin
{ If input field is a null, then output is automatically a null
regardless of datatype. }
if IsRecordFieldNull(aSourceFldNr, aSourceBuf) then begin
TargetCursor.Table.Dictionary.SetRecordField(aTargetFldNr, aTargetBuf, nil);
Exit;
end;
{Begin !!.10}
{ also count input field as null if it's a stringtype, the field
conains the empty string, and output field is a blob. }
if (TargetCursor.Table.Dictionary.FieldType[aTargetFldNr] in [fftBLOB..ffcLastBlobType]) and
(((FieldType[aSourceFldNr] in [fftNullString, fftNullAnsiStr]) and
(Byte(aSourceBuf^[FieldOffset[aSourceFldNr]])=0)) or
((FieldType[aSourceFldNr] in [fftShortString, fftShortAnsiStr]) and
(Byte(aSourceBuf^[FieldOffset[aSourceFldNr]+1])=0)) or
((FieldType[aSourceFldNr] in [fftWideString]) and
(WideChar(aSourceBuf^[FieldOffset[aSourceFldNr]])=''))) then begin
TargetCursor.Table.Dictionary.SetRecordField(aTargetFldNr, aTargetBuf, nil);
Exit;
end;
{End !!.10}
SourceValue := Addr(aSourceBuf^[FieldOffset[aSourceFldNr]]);
end;
SourceType := FieldType[aSourceFldNr];
SourceLength := FieldLength[aSourceFldNr];
end;
with TargetCursor.Table.Dictionary do begin
if Assigned(aTargetBuf) then
TargetValue := Addr(aTargetBuf^[FieldOffset[aTargetFldNr]]);
TargetType := FieldType[aTargetFldNr];
TargetLength := FieldLength[aTargetFldNr];
end;
Result := FFConvertSingleField(SourceValue, TargetValue,
SourceType, TargetType,
SourceLength, TargetLength);
if Assigned(aTargetBuf) and (Result = DBIERR_NONE) then begin
{ Field is not null }
with TargetCursor.Table.Dictionary do
FFClearBit(@aTargetBuf^[LogicalRecordLength], aTargetFldNr);
{ Handle BLOB targets }
if TargetType in [fftBLOB..ffcLastBLOBType] then begin
Result := BLOBCreate(TargetCursor.CursorID, TffInt64(TargetValue^));
if Result = DBIERR_NONE then
if SourceType in [fftBLOB..ffcLastBLOBType] then
Result := seBLOBCopy(SourceCursor,
TargetCursor,
TffInt64(SourceValue^),
TffInt64(TargetValue^),
aBLOBBuffer,
aBLOBBufLen)
else
{Begin !!.10}
if SourceType in [fftShortString, fftShortAnsiStr] then begin
{ skip lengthbyte }
SourceValue := Pointer(Succ(Integer(SourceValue)));
Result := TargetCursor.BLOBWrite(TffInt64(TargetValue^),
0,
SourceLength-1,
SourceValue^);
end
else
// if SourceType in [fftShortString, fftShortAnsiStr] begin
{End !!.10}
Result := TargetCursor.BLOBWrite(TffInt64(TargetValue^),
0,
SourceLength,
SourceValue^);
end;
end;
except
{Begin !!.13}
on E : EOverFlow do
Result := DBIERR_INVALIDFLDXFORM;
{$IFOPT R+}
on E : ERangeError do
Result := DBIERR_INVALIDFLDXFORM;
{$ENDIF}
{End !!.13}
on E : Exception do begin
if Result = DBIERR_NONE then
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;
end;
{--------}
{ Include code for asynchronous requests. }
{$i ffsrridx.inc}
{$i ffsrpack.inc}
{$i ffsrrest.inc}
{$i ffsrrcnt.inc} {!!.10}
{--------}
function TffServerEngine.TransactionCommit(const aDatabaseID : TffDatabaseID) : TffResult;
var
DB : TffSrDatabase;
begin
try
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result = DBIERR_NONE) then
try
FFSetRetry(DB.Timeout);
if DB.Transaction = nil then
Result := DBIERR_NOACTIVETRAN
else if DB.Transaction.IsCorrupt then begin
DB.NotifyExtenders(ffeaBeforeRollback, ffeaNoAction);
seTransactionRollback(DB);
Result := DBIERR_FF_CorruptTrans;
DB.NotifyExtenders(ffeaAfterRollback, ffeaNoAction);
end
else begin
Result := DB.NotifyExtenders(ffeaBeforeCommit, ffeaCommitFail);{!!.06}
if Result = DBIERR_NONE then begin {!!.06}
seTransactionCommit(DB); {!!.06}
DB.NotifyExtenders(ffeaAfterCommit, ffeaNoAction); {!!.06}
end; {!!.06}
end;
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.TransactionCommitSubset(const aDatabaseID : TffDatabaseID) : TffResult;
var
DB : TffSrDatabase;
begin
try
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result = DBIERR_NONE) then
try
FFSetRetry(DB.Timeout);
Result := seTransactionCommitSubset(DB);
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{Begin !!.01
{--------}
function TffServerEngine.TransactionCommitSQL(const aDatabaseID : TffDatabaseID;
const notifyExtenders : Boolean) : TffResult;
var
aDB : TffSrDatabase;
begin
aDB := TffSrDatabase(aDatabaseID);
if aDB.Transaction.IsCorrupt then begin
if notifyExtenders then
aDB.NotifyExtenders(ffeaBeforeRollback, ffeaNoAction);
seTransactionRollback(aDB);
Result := DBIERR_FF_CorruptTrans;
if notifyExtenders then
aDB.NotifyExtenders(ffeaAfterRollback, ffeaNoAction);
end
else begin
if notifyExtenders then
aDB.NotifyExtenders(ffeaBeforeCommit, ffeaNoAction);
Result := seTransactionCommit(aDB);
if notifyExtenders then
aDB.NotifyExtenders(ffeaAfterCommit, ffeaNoAction);
end;
end;
{End !!.01}
{--------}
function TffServerEngine.seTransactionCommitSubset(const aDB : TffSrDatabase) : TffResult;
{ Rewritten !!.03}
var
aContainer : TffTransContainer;
aInx : Longint;
aTable : TffSrTable;
aTableList : TffPointerList;
Nested : Boolean;
begin
Result := DBIERR_NONE;
if aDB.Transaction.IsCorrupt then begin
aDB.NotifyExtenders(ffeaBeforeRollback, ffeaNoAction);
seTransactionRollback(aDB);
Result := DBIERR_FF_CorruptTrans;
aDB.NotifyExtenders(ffeaAfterRollback, ffeaNoAction);
end
else begin
aTableList := TffPointerList.Create;
aContainer := TffTransContainer(aDB.Transaction.TransLockContainer);
Nested := aDB.Transaction.Nested;
try
{ Determine which tables were affected by the transaction. We will
commit the changes to their BLOB mgr's in-memory deleted chain. }
if assigned(aContainer) and (not Nested) then
for aInx := 0 to pred(aContainer.ContentCount) do
if aContainer.ContentLockType[aInx] = ffsltExclusive then begin
aTable := TffSrTable(aContainer.ContentTable[aInx]);
aTableList.Append(Pointer(aTable));
end;
aDB.NotifyExtenders(ffeaBeforeCommit, ffeaNoAction);
seBufMgr.CommitTransactionSubset(aDB.Transaction);
{ Nested transaction? }
if (not Nested) then begin
{ No. Release transaction locks. For each table involved, commit the
changes to the BLOB resource manager's in-memory deleted chain. }
aDB.Folder.LockMgr.ReleaseTransactionLocks(aDB.Transaction, True);
for aInx := 0 to pred(aTableList.Count) do begin
aTable := TffSrTable(aTableList.List[aInx]);
aTable.btCommitBLOBMgr;
end;
end;
aDB.NotifyExtenders(ffeaAfterCommit, ffeaNoAction);
finally
aTableList.Free;
end;
end;
end;
{--------}
function TffServerEngine.TransactionRollback(const aDatabaseID : TffDatabaseID) : TffResult;
var
DB : TffSrDatabase;
begin
try
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result = DBIERR_NONE) then
try
if not assigned(DB.Transaction) then begin
Result := DBIERR_NOACTIVETRAN;
exit;
end;
FFSetRetry(DB.Timeout);
DB.NotifyExtenders(ffeaBeforeRollback, ffeaNoAction);
seTransactionRollback(DB);
DB.NotifyExtenders(ffeaAfterRollback, ffeaNoAction);
finally
DB.Deactivate;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{Begin !!.01}
{--------}
function TffServerEngine.TransactionRollbackSQL(const aDatabaseID : TffDatabaseID;
const notifyExtenders : Boolean) : TffResult;
var
aDB : TffSrDatabase;
begin
aDB := TffSrDatabase(aDatabaseID);
if notifyExtenders then
aDB.NotifyExtenders(ffeaBeforeRollback, ffeaNoAction);
Result := seTransactionRollback(aDB);
if notifyExtenders then
aDB.NotifyExtenders(ffeaAfterRollback, ffeaNoAction);
end;
{End !!.01}
{--------}
function TffServerEngine.bseGetAutoSaveCfg : Boolean;
begin
Result := seConfig.GeneralInfo^.giNoAutoSaveCfg;
end;
{--------}
function TffServerEngine.bseGetReadOnly : boolean;
begin
Result := seConfig.GeneralInfo^.giReadOnly;
end;
{--------}
procedure TffServerEngine.bseSetAutoSaveCfg(aValue : Boolean); {!!.01 - Start}
begin
seConfig.GeneralInfo^.giNoAutoSaveCfg := aValue;
end;
{--------}
procedure TffServerEngine.bseSetReadOnly(aValue : Boolean);
begin
seConfig.GeneralInfo^.giReadOnly := aValue;
end;
{--------} {!!.01 - End}
function TffServerEngine.TransactionStart(const aDatabaseID : TffDatabaseID;
const aFailSafe : Boolean) : TffResult;
var
DB : TffSrDatabase;
TransID : TffTransID;
begin
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result = DBIERR_NONE) then
try
FFSetRetry(DB.Timeout);
Result := seTransactionStart(DB, aFailSafe, ffcl_TrExplicit,
TransID);
if Result = DBIERR_NONE then
DB.NotifyExtenders(ffeaAfterStartTrans, ffeaNoAction);
finally
DB.Deactivate;
end;
end;
{Begin !!.01}
{--------}
function TffServerEngine.TransactionStartSQL(const aDatabaseID : TffDatabaseID;
const notifyExtenders : boolean) : TffResult;
var
aTransID : TffTransID;
begin
Result := seTransactionStart(TffSrDatabase(aDatabaseID), false, true, aTransID);
{Begin !!.06}
if (Result = DBIERR_NONE) then begin
TffSrDatabase(aDatabaseID).Transaction.IsReadOnly := True;
if notifyExtenders then
TffSrDatabase(aDatabaseID).NotifyExtenders(ffeaAfterStartTrans, ffeaNoAction);
end;
{End !!.06}
end;
{End !!.01}
{Begin !!.10}
{--------}
function TffServerEngine.TransactionStartWith(const aDatabaseID : TffDatabaseID;
const aFailSafe : Boolean;
const aCursorIDs : TffPointerList) : TffResult;
var
RetryUntil : DWORD;
DB : TffSrDatabase;
TransID : TffTransID;
Limit,
anIndex : Longint;
aCursorID : TffCursorID;
Cursor : TffSrBaseCursor;
Lock : TffPadlock;
GetCursorResult : TffResult; {!!.13}
begin
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result = DBIERR_NONE) then
try
FFSetRetry(DB.Timeout);
Result := seTransactionStart(DB, aFailSafe, ffcl_TrExplicit,
TransID);
if Result = DBIERR_NONE then
try
Lock := DB.Folder.LockMgr.StartWithLock;
{ Retry this operation until it is successful or we reach the database
timeout limit. }
RetryUntil := FFGetRetry;
repeat
if Result <> DBIERR_NONE then
Sleep(ffc_StartTranWithDelay);
Limit := 0;
Lock.Lock;
try
for anIndex := 0 to pred(aCursorIDs.Count) do begin
aCursorID := TffCursorID(aCursorIDs[anIndex]);
Result := CheckCursorIDAndGet(aCursorID, Cursor);
if Result = DBIERR_NONE then
try
Result := Cursor.AcqExclContentLock;
if Result <> DBIERR_NONE then begin
Limit := pred(anIndex);
Break;
end;
finally
Cursor.Deactivate;
end
else
Break;
end; { for }
if Result <> DBIERR_NONE then
for anIndex := 0 to Limit do begin
aCursorID := TffCursorID(aCursorIDs[anIndex]);
GetCursorResult := CheckCursorIDAndGet(aCursorID, Cursor); {!!.13}
if GetCursorResult = DBIERR_NONE then begin {!!.13}
Cursor.RelContentLock(ffclmWrite);
end;
end; { for }
finally
Lock.Unlock;
end;
until (Result = DBIERR_NONE) or
(RetryUntil <= (GetTickCount - 10));
if Result = DBIERR_NONE then
DB.NotifyExtenders(ffeaAfterStartTrans, ffeaNoAction)
else begin
seTransactionRollback(DB);
if Result = fferrLockRejected then
Result := DBIERR_LOCKED;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog,
bseGetReadOnly);
seTransactionRollback(DB);
end;
end;
finally
DB.Deactivate;
end;
end;
{End !!.10}
{--------}
function TffServerEngine.seTransactionStart(const aDB : TffSrDatabase;
const aFailSafe, aImplicit : boolean;
var aTransactionID : TffTransID) : TffResult;
var
aTrans : TffSrTransaction;
begin
try
Result := aDB.Folder.TransactionMgr.StartTransaction
(aDB.DatabaseID, aFailSafe, aImplicit,
false, aDB.Folder.Path, aTrans);
aDB.Transaction := aTrans;
aTransactionID := aTrans.TransactionID;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{===Script processing================================================}
function TffServerEngine.CalcPriorityIndex(const PriorityStr : TffShStr) : integer;
const
PriorityValues : array [0..6] of string[12] = (
'LOWEST',
'BELOW NORMAL',
'NORMAL',
'ABOVE NORMAL',
'HIGHEST',
'BELOWNORMAL',
'ABOVENORMAL');
var
Inx : integer;
begin
for Inx := low(PriorityValues) to high(PriorityValues) do
if (PriorityStr = PriorityValues[Inx]) then begin
Result := Inx - 2;
if Result = 3 then
Result := -1
else if Result = 4 then
Result := 1;
Exit;
end;
Result := 0;
end;
{--------}
function TffServerEngine.CalcKeyIndex(const KeyStr : TffShStr) : integer;
const
KeyValues : array [0..21] of string[13] = (
'SERVERNAME',
'MAXRAM',
'USESINGLEUSER',
'USEIPXSPX',
'USETCPIP',
'USELOGIN',
'AUTOUPSERVER',
'AUTOMINIMIZE',
'IPXSPXLFB',
'TCPIPLFB',
'ALLOWENCRYPT',
'READONLY',
'LASTMSGINTVAL',
'ALIVEINTERVAL',
'ALIVERETRIES',
'PRIORITY',
'DELETESCRIPT',
'TCPINTERFACE',
'NOAUTOSAVECFG',
'TEMPSTORESIZE',
'COLLECTENABLD',
'COLLECTFREQ');
var
Inx : integer;
begin
for Inx := low(KeyValues) to high(KeyValues) do
if (KeyStr = KeyValues[Inx]) then begin
Result := Inx;
Exit;
end;
Result := -1;
end;
{--------}
procedure TffServerEngine.GetServerNames(aList: TStrings;
aTimeout : Longint);
begin
aList.Clear;
aList.Add('Direct');
end;
{--------}
function TffServerEngine.seDatabaseGetAliasPathPrim
(aAlias : TffName; var aPath :TffPath) : TffResult;
var
aList : TList;
Count : Integer;
AliasDes : PffAliasDescriptor;
begin
{ Assumption: Thread-safeness enforced at a higher level. }
{ Retrieve the alias list, and return the path for the matching entry }
aPath := '';
aList := TList.Create;
try
Result := seDatabaseAliasListPrim(aList);
if Result = DBIERR_NONE then
for Count := 0 to Pred(aList.Count) do begin
AliasDes := PffAliasDescriptor(aList.Items[Count]);
if FFAnsiCompareText(AliasDes^.adAlias, aAlias) = 0 then begin {!!.03, !!.10}
aPath := AliasDes^.adPath;
Break;
end;
end;
finally
aList.Free;
end;
end;
{--------}
function TffServerEngine.DatabaseGetAliasPath(aAlias : TffName;
var aPath : TffPath;
aClientID : TFFClientID)
: TffResult;
var
Client : TffSrClient;
begin
try
Result := CheckClientIDandGet(aClientID, Client);
if Result = DBIERR_NONE then begin
FFSetRetry(Client.Timeout);
try
seConfig.AliasList.BeginRead;
try
Result := Client.NotifyExtenders(ffeaBeforeDBRead, ffeaNoAction);
if Result = DBIERR_NONE then
Result := seDatabaseGetAliasPathPrim(aAlias, aPath);
finally
seConfig.AliasList.EndRead;
end;
finally
Client.Deactivate;
end;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.DatabaseGetFreeSpace(const aDatabaseID : TffDatabaseID;
var aFreeSpace : Longint)
: TffResult;
{!!.11 - Rewritten}
var
DB : TffSrDatabase;
begin
try
Result := CheckDatabaseIDAndGet(aDatabaseID, DB);
if (Result = DBIERR_NONE) then begin
try
aFreeSpace := FFGetDiskFreeSpace(DB.dbFolder.Path);
finally
DB.Deactivate;
end;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;
end;
{--------}
function TffServerEngine.DatabaseModifyAlias(const aClientID : TffClientID;
const aAlias : TffName;
const aNewName : TffName;
const aNewPath : TffPath;
aCheckSpace : Boolean) {!!.11}
: TffResult;
var
Client : TffSrClient;
Name : TffName;
Path : TffPath;
begin
try
Result := CheckClientIDandGet(aClientID, Client);
if Result = DBIERR_NONE then begin
FFSetRetry(Client.Timeout);
try
seConfig.AliasList.BeginWrite;
try
Result := Client.NotifyExtenders(ffeaBeforeDBDelete,
ffeaDBDeleteFail);
if Result = DBIERR_NONE then begin
Name := aAlias;
Result := seDatabaseGetAliasPathPrim(aAlias, Path);
if Result = DBIERR_NONE then begin
{ Does the alias have a new name? }
if aNewName <> '' then
Name := aNewName;
{ Does the alias have a new path? }
if aNewPath <> '' then
Path := aNewPath;
Result := seDatabaseDeleteAliasPrim(aAlias);
if (Result = DBIERR_NONE) then begin
Result := Client.NotifyExtenders(ffeaBeforeDBInsert,
ffeaDBInsertFail);
if Result = DBIERR_NONE then begin
Result := seDatabaseAddAliasPrim(Name,
Path,
aCheckSpace); {!!.11}
if Result = DBIERR_NONE then
WriteAliasData
else
Client.NotifyExtenders(ffeaDBInsertFail,
ffeaNoAction);
end;
end else
Client.NotifyExtenders(ffeaDBDeleteFail, ffeaNoAction);
end else { if got existing alias path }
Client.NotifyExtenders(ffeaDBDeleteFail, ffeaNoAction);
end; { if no clients complained about rights }
finally
seConfig.AliasList.EndWrite;
end;
finally
Client.Deactivate;
end;
end;
except
on E : Exception do begin
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
end;{try..except}
end;
{--------}
function TffServerEngine.GetServerDateTime(var aDateTime: TDateTime): TffResult;
begin
Result := DBIERR_NONE;
aDateTime := Now;
end;
{--------} {begin !!.10}
function TffServerEngine.GetServerSystemTime(var aSystemTime : TSystemTime)
: TffResult;
begin
Result := DBIERR_NONE;
GetSystemTime(aSystemTime);
end;
{--------}
function TffServerEngine.GetServerGUID(var aGUID : TGUID) : TffResult;
begin
Result := DBIERR_NONE;
CoCreateGuid(aGuid);
end;
{--------}
function TffServerEngine.GetServerID(var aUniqueID : TGUID) : TffResult;
begin
Result := DBIERR_NONE;
aUniqueID := seUniqueID;
end;
{--------}
function TffServerEngine.GetServerStatistics(var aStats : TffServerStatistics)
: TffResult;
begin
aStats.ssName := Configuration.ServerName;
aStats.ssVersion := ffVersionNumber;
aStats.ssState := FFMapStateToString(State);
aStats.ssClientCount := ClientList.ClientCount;
aStats.ssSessionCount := SessionList.SessionCount;
aStats.ssOpenDatabasesCount := DatabaseList.DatabaseCount;
aStats.ssOpenTablesCount := TableList.TableCount;
aStats.ssOpenCursorsCount := CursorList.CursorCount;
aStats.ssRamUsed := BufferManager.RAMUsed;
aStats.ssMaxRam := BufferManager.MaxRAM;
aStats.ssUpTimeSecs := (GetTickCount - seStartTime) div 1000;
aStats.ssCmdHandlerCount := CmdHandlerCount;
Result := DBIERR_NONE;
end;
{--------}
function TffServerEngine.GetCommandHandlerStatistics(const aCmdHandlerIdx : Integer;
var aStats : TffCommandHandlerStatistics)
: TffResult;
begin
if (aCmdHandlerIdx < 0) or
(aCmdHandlerIdx > Pred(CmdHandlerCount)) then
Result := DBIERR_OBJNOTFOUND
else begin
aStats.csTransportCount := CmdHandler[aCmdHandlerIdx].TransportCount;
Result := DBIERR_NONE;
end;
end;
{--------}
function TffServerEngine.GetTransportStatistics(const aCmdHandlerIdx : Integer;
const aTransportIdx : Integer;
var aStats : TffTransportStatistics)
: TffResult;
var
Trans : TffBaseTransport;
begin
if (aCmdHandlerIdx < 0) or
(aCmdHandlerIdx > Pred(CmdHandlerCount)) then
Result := DBIERR_OBJNOTFOUND
else begin
if (aTransportIdx < 0) or
(aTransportIdx > Pred(CmdHandler[aCmdHandlerIdx].TransportCount)) then
Result := DBIERR_OBJNOTFOUND
else begin
Trans := CmdHandler[aCmdHandlerIdx].Transports[aTransportIdx];
aStats.tsName := Trans.GetName;
aStats.tsState := FFMapStateToString(Trans.State);
aStats.tsAddress := Trans.ServerName;
aStats.tsClientCount := Trans.ConnectionCount;
aStats.tsMessageCount := Trans.MsgCount;
aStats.tsMessagesPerSec := Trans.MsgCount / ((GetTickCount - seStartTime) div 1000);
Result := DBIERR_NONE;
end;
end;
end;
{--------} {end !!.10}
function TffServerEngine.ValBoolean(const BoolStr : TffShStr;
var BoolValue : boolean) : boolean;
var
UpperBoolStr : TffShStr;
begin
{only values allowed are 0, 1, YES, NO, TRUE, FALSE}
UpperBoolStr := FFShStrUpper(BoolStr);
Result := true;
BoolValue := false;
if (UpperBoolStr = '0') or
(UpperBoolStr = 'NO') or
(UpperBoolStr = 'FALSE') then
Exit;
BoolValue := true;
if (UpperBoolStr = '1') or
(UpperBoolStr = 'YES') or
(UpperBoolStr = 'TRUE') then
Exit;
Result := false;
end;
{--------}
procedure TffServerEngine.ProcessScriptCommand(const KeyStr,
ValueStr : TffShStr;
var DeleteScript : Boolean);
var
KeyInx : Integer;
WorkInt : Longint;
ec : Integer;
WorkBool : Boolean;
UpperStr : TffShStr;
begin
DeleteScript := False;
{uppercase the key}
UpperStr := FFShStrUpper(KeyStr);
{is it one of the strings we allow?}
KeyInx := CalcKeyIndex(UpperStr);
{if it is, process the command}
if (KeyInx >= 0) then begin
case KeyInx of
0 : {server name}
begin
Configuration.GeneralInfo^.giServerName := ValueStr;
end;
1 : {Max RAM}
begin
Val(ValueStr, WorkInt, ec);
if (ec = 0) and (WorkInt >= 1) then
Configuration.GeneralInfo^.giMaxRAM := WorkInt;
end;
2 : {Use Single User Protocol}
begin {!!.01 - Start}
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giSingleUser := WorkBool;
end; {!!.01 - End}
3 : {Use IPX/SPX Protocol}
begin {!!.01 - Start}
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giIPXSPX := WorkBool;
end; {!!.01 - End}
4 : {Use TCP/IP Protocol}
begin {!!.01 - Start}
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giTCPIP := WorkBool;
end; {!!.01 - End}
5 : {Login security?}
begin
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giIsSecure := WorkBool;
end;
6 : {Auto Up?}
begin
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giAutoUp := WorkBool;
end;
7 : {Auto Minimize?}
begin
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giAutoMini := WorkBool;
end;
8 : {Enable IPX/SPX use broadcasts?}
begin
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giIPXSPXLFB := WorkBool;
end;
9 : {Enable TCP/IP use broadcasts?}
begin
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giTCPIPLFB := WorkBool;
end;
10 : {Allow encrypted tables to be created?}
begin
{$IFDEF SecureServer}
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giAllowEncrypt := WorkBool;
{$ENDIF}
end;
11 : {ReadOnly?}
begin
if ValBoolean(ValueStr, WorkBool) then begin
Configuration.GeneralInfo^.giReadOnly := WorkBool;
seSetLoggingState;
end;
end;
12 : {Last message interval}
begin
Val(ValueStr, WorkInt, ec);
if (ec = 0) and (WorkInt >= 1000) and (WorkInt <= 86400000) then
Configuration.GeneralInfo^.giLastMsgInterval := WorkInt;
end;
13 : {keep alive interval}
begin
Val(ValueStr, WorkInt, ec);
if (ec = 0) and (WorkInt >= 1000) and (WorkInt <= 86400000) then
Configuration.GeneralInfo^.giKAInterval := WorkInt;
end;
14 : {keep alive retries}
begin
Val(ValueStr, WorkInt, ec);
if (ec = 0) and (WorkInt >= 1) and (WorkInt <= 100) then
Configuration.GeneralInfo^.giKARetries := WorkInt;
end;
15 : {Priority}
begin
UpperStr := FFShStrUpper(ValueStr);
Configuration.GeneralInfo^.giPriority :=
CalcPriorityIndex(UpperStr);
end;
16 : {Delete script}
begin {!!.01 - Start}
if ValBoolean(ValueStr, WorkBool) then
DeleteScript := WorkBool;
end; {!!.01 - End}
17 : {TCP/IP Interface}
begin
Val(ValueStr, WorkInt, ec);
Configuration.GeneralInfo^.giTCPInterface := WorkInt;
end;
18 : {NoAutoSaveCfg}
begin
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giNoAutoSaveCfg := WorkBool;
end;
19 : {giTempStoreSize}
begin
Val(ValueStr, WorkInt, ec);
{Temp storage must be between 1 meg and 2 gigs.}
if (ec = 0) and (WorkInt > 0) and (WorkInt < 2049) then
Configuration.GeneralInfo^.giTempStoreSize := WorkInt;
end;
20 : {giCollectEnabled}
begin
if ValBoolean(ValueStr, WorkBool) then
Configuration.GeneralInfo^.giCollectEnabled := WorkBool;
end;
21 : {giCollectFreq}
begin
Val(ValueStr, WorkInt, ec);
{Garbage collection frequency should be between 30 seconds
and 60 minutes.}
if (ec = 0) and (WorkInt > 30000) and (WorkInt < 3600000) then
Configuration.GeneralInfo^.giCollectFreq := WorkInt;
end;
end;{case}
end
{if it isn't it must be an alias definition}
else begin
if FFDirectoryExists(ValueStr) then
{ Assumption: This routine happens on sever startup therefore we
do not need to ensure thread-safeness. }
seDatabaseAddAliasPrim(KeyStr, ValueStr, False); {!!.11}
end;
end;
{--------}
procedure TffServerEngine.ProcessAliasScript;
var
CurPath : TffPath;
ScriptFile : TffFullFileName;
ScriptItems : TStrings;
Alias : TffName;
Path : TffPath;
i, iPos, iLen : Integer;
DeleteScript : Boolean;
begin
{ Get the application's directory. }
CurPath := FFExtractPath(FFGetExeName);
{ Create the script filename. }
ScriptFile := FFMakeFullFileName(CurPath, ffc_AliasScript);
{ Does the alias script file (FFAlias.sc$) exist in the directory? }
if FFFileExists( ScriptFile ) then begin
{ Yes. Process it. }
ScriptItems := TStringList.Create;
try
ScriptItems.LoadFromFile( ScriptFile );
{ For each item in the file, try to parse it. }
for i := 0 to pred( ScriptItems.Count ) do begin
{ Only process lines with some length. }
iLen := Length( ScriptItems[i] );
if iLen > 2 then begin
{ Find the semicolon. }
iPos := Pos( ';', ScriptItems[i] );
{ Only process lines with length before and after the semicolon. }
if ( iPos > 1 ) and ( iPos < iLen )then begin
{ Get the alias. }
Alias := Copy( ScriptItems[i], 1, pred( iPos ) );
{ Get the path. }
Path := Copy( ScriptItems[i], succ( iPos ), iLen - iPos );
{ Add the alias. }
ProcessScriptCommand(Alias, Path, DeleteScript);
end;
end;
end;
finally
ScriptItems.Free;
end;
end;
end;
{--------}
procedure TffServerEngine.ProcessFullScript(const ScriptFileName : TffFullFileName);
var
AfterStr : TffShStr;
AppliesToSelf : Boolean;
{ If True then script command applies to this server. Becomes True when
encounters a section header bearing the same server name. Becomes False
when encounters a section header bearing a different server name. }
DeleteScript : Boolean;
Inx : Integer;
Len : Integer;
Line : TffShStr;
PosEquals : Integer;
ScriptItems : TStrings;
UServerName : TffShStr;
begin
AppliesToSelf := True;
{ Default to True since the script may contain leading items that apply
to all server engines. }
DeleteScript := False;
UServerName := Uppercase(Self.Name);
{ Does the script file exist? }
if FFFileExists(ScriptFileName) then begin
{ Yes. Process it. }
ScriptItems := TStringList.Create;
try
ScriptItems.LoadFromFile(ScriptFileName);
{ For each item in the file, try to parse it. }
for Inx := 0 to pred(ScriptItems.Count) do begin
{ Only process lines with some length. }
Line := Trim(ScriptItems[Inx]);
Len := length(Line);
if (Len > 2) then begin
{ Is this a section header? }
if (Pos('[', Line) = 1) and
(Pos(']', Line) = Len) then begin
{ Yes. Does the section apply to us? }
AppliesToSelf := (UpperCase(Copy(Line, 2, Len - 2)) = UServerName);
end
else
{ Not a section header. Does this item apply to this server
engine? }
if AppliesToSelf then begin
{ Yes. Find the equals sign. }
PosEquals := Pos('=', Line);
{ Only process lines with length before and after the = char. }
if (PosEquals > 1) and (PosEquals < Len) then begin
{ Get the before and after strings. }
AfterStr := Copy(Line, succ(PosEquals), Len - PosEquals);
SetLength(Line, pred(PosEquals));
{ Process the script command. }
ProcessScriptCommand(Line, AfterStr, DeleteScript);
if (DeleteScript) then
DeleteFile(ScriptFileName);
end;
end; { if AppliesToSelf }
end;
end;
finally
ScriptItems.Free;
end;
end;
end;
{--------}
procedure TffServerEngine.ReadAliasData;
var
aClientID : TffClientID;
Alias : TffName;
Client : TffSrClient;
Cursor : TffSrBaseCursor; {!!.06}
DB : TffSrDatabase;
DBIResult : TffResult;
Dict : TffDataDictionary;
Folder : TffSrFolder;
Hash : TffWord32;
IsNull : Boolean;
MyRec : PffByteArray;
Path : TffPath;
SearchPath : TffPath;
CheckDisk : Boolean; {!!.11}
begin
Folder := nil;
DB := nil;
Client := nil;
Cursor := nil;
try
{create ourselves a client}
DBIResult := ClientAdd(aClientID, '', ffc_AdminUserID, 1000, Hash);
if (DBIResult <> DBIERR_NONE) then
Exit;
{open a database to the server engine directory}
Client := TffSrClient(aClientID);
Folder := TffSrFolder.Create(ConfigDir, True, seBufMgr);
DB := seDatabaseOpenPrim(Client.clSessionList.CurrentSession,
Folder,
'',
omReadWrite,
smExclusive,
1000,
False); {!!.11}
if (DBIResult = DBIERR_NONE) then
FFSetRetry(DB.Timeout)
else
Exit;
{ Read the records. }
Configuration.AliasList.BeginWrite;
try
Configuration.AliasList.Empty;
finally
Configuration.AliasList.EndWrite;
end;
{ If the table exists then read it. }
SearchPath := Folder.Path;
if (SearchPath[length(SearchPath)] <> '\') then
FFShStrAddChar(SearchPath, '\');
if (FFFileExists(SearchPath +
FFForceExtension(ffc_AliasTableName,
ffc_ExtForData))) then begin
Cursor := CursorClass.Create(Self, DB, 1000); {!!.06}
FFSetRetry(Cursor.Timeout); {!!.01}
Cursor.Open(ffc_AliasTableName,
'',
0,
omReadOnly,
smExclusive,
True,
False, []);
Cursor.CloseTable := True;
Dict := Cursor.Dictionary;
FFGetMem(MyRec, Dict.RecordLength);
try
FFSetRetry(Cursor.Timeout);
Cursor.SetToBegin;
FFSetRetry(Cursor.Timeout); {!!.01}
DBIResult := Cursor.GetNextRecord(MyRec, ffsltNone);
while (DBIResult = DBIERR_NONE) do begin
Dict.GetRecordField(0, MyRec, IsNull, @Alias);
Dict.GetRecordField(1, MyRec, IsNull, @Path);
if (Dict.FieldCount > 2) then {!!.11}
Dict.GetRecordField(2, MyRec, IsNull, @CheckDisk) {!!.11}
else {!!.11}
CheckDisk := False; {!!.11}
{ Assumption: This is one of the first things happening when the
server starts so no thread-safeness need be enforced. }
Configuration.AddAlias(Alias, Path, CheckDisk); {!!.11}
FFSetRetry(Cursor.Timeout); {!!.01}
DBIResult := Cursor.GetNextRecord(MyRec, ffsltNone);
end;
finally
FFFreeMem(MyRec, Dict.RecordLength);
end;{try..finally}
end;
finally
{ Close the cursor. }
if assigned(Cursor) then
Cursor.Free;
DB.Free;
Folder.Free;
{ Remove the client. }
seClientRemovePrim(Client);
end;
end;
{--------}
function TffServerEngine.WriteAliasData : TffResult;
label
Cleanup,
InnerCleanup;
var
aClientID : TffClientID;
AliasItem : TffAliasItem;
Buffer : TffShStr;
Dict : TffDataDictionary;
Folder : TffSrFolder;
Hash : TffWord32;
i : integer;
MyRec : PffByteArray;
State : integer;
TransID : TffTransID;
Client : TffSrClient;
DB : TffSrDatabase;
Cursor : TffSrBaseCursor; {!!.06}
begin
Result := DBIERR_NONE;
with Configuration.GeneralInfo^ do
if giReadOnly or giNoAutoSaveCfg then
Exit;
State := 0;
DB := nil;
Client := nil;
Dict := nil;
Folder := nil;
Cursor := nil;
try
{ Strategy: Create a temporary table and write the data to that
table. If that works, rename the existing table and replace it with
the temporary table. If that succeeds, get rid of the old table.
If a failure occurs at any point, the old table must be put back
in its original place. }
{create ourselves a client}
Result := ClientAdd(aClientID, '', ffc_AdminUserID, 1000, Hash);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 100; { client added }
{open a database (no alias) to the server engine directory}
Client := TffSrClient(aClientID);
Folder := TffSrFolder.Create(ConfigDir, False, seBufMgr);
DB := seDatabaseOpenPrim(Client.clSessionList.CurrentSession,
Folder,
'',
omReadWrite,
smExclusive,
1000,
False); {!!.11}
if (Result = DBIERR_NONE) then
FFSetRetry(DB.Timeout)
else
goto Cleanup;
State := 200; { database opened }
{Make sure prior instances of the saved and temporary tables are deleted. }
seTableDeletePrim(DB, ffc_SavedAliasTableName);
seTableDeletePrim(DB, ffc_TempAliasTableName);
{Prepare a data dictionary.}
Dict := TffServerDataDict.Create(4096);
State := 300; { dictionary created }
{Create the new alias table as a temporary file. }
with Dict do begin
AddField('Alias', '', fftShortString, pred(sizeof(TffName)), 0, True, nil);
AddField('Path', '', fftShortString, pred(sizeof(TffPath)), 0, True, nil);
AddField('CheckDisk', '', fftBoolean, SizeOf(Boolean), 0, True, nil); {!!.11}
end;
Dict.IsEncrypted := Configuration.GeneralInfo^.giAllowEncrypt;
Result := seTableBuildPrim(DB,
True,
ffc_TempAliasTableName,
True,
Dict);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 400; { temporary table created }
{start a transaction before opening the alias table}
Result := seTransactionStart(DB, False, ffcl_TrImplicit, TransID);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 500; { transaction started for opening alias table }
Configuration.AliasList.BeginRead;
try
FFGetMem(MyRec, Dict.RecordLength);
State := 600;
Cursor := CursorClass.Create(Self, DB, 1000); {!!.06}
FFSetRetry(Cursor.Timeout); {!!.01}
Cursor.Open(ffc_TempAliasTableName,
'',
0,
omReadWrite,
smExclusive,
True,
False,
[]);
Cursor.CloseTable := True;
{Insert new records.}
for i := 0 to pred(Configuration.AliasList.Count) do begin
Cursor.Dictionary.InitRecord(MyRec);
AliasItem := Configuration.AliasList[i];
Buffer := AliasItem.Alias;
Cursor.Dictionary.SetRecordField(0, MyRec, @Buffer);
Buffer := AliasItem.Path;
Cursor.Dictionary.SetRecordField(1, MyRec, @Buffer);
Cursor.Dictionary.SetRecordField(2, MyRec, @AliasItem.CheckSpace); {!!.11}
FFSetRetry(Cursor.Timeout); {!!.01}
Result := Cursor.InsertRecord(MyRec, ffsltExclusive);
if (Result <> DBIERR_NONE) then
goto InnerCleanup;
end;
State := 750;
{ Commit the transaction. }
FFSetRetry(Cursor.Timeout); {!!.01}
Result := seTransactionCommit(DB);
if Result = DBIERR_NONE then
State := 800; { transaction committed }
InnerCleanup:
finally
Configuration.AliasList.EndRead;
{ Rollback the transaction. }
if (State >= 500) and (State < 750) then
seTransactionRollback(DB);
if State >= 600 then
FFFreeMem(MyRec, Dict.RecordLength);
{close the cursor}
if assigned(Cursor) then
Cursor.Free;
end;{try..finally}
{ If the record insertions did not complete then jump to cleanup. }
if State < 800 then
goto Cleanup;
{ Rename the existing table. }
Result := seTableRenamePrim(DB, ffc_AliasTableName, ffc_SavedAliasTableName);
if (Result <> DBIERR_NOSUCHTABLE) and (Result <> DBIERR_NONE) then
goto Cleanup;
State := 1000; { renamed system table to saved table }
{ Replace the original table with the temporary table. }
Result := seTableRenamePrim(DB, ffc_TempAliasTableName, ffc_AliasTableName);
if Result <> DBIERR_NONE then
goto Cleanup;
State := 1100; { renamed temp table to system table }
{ The new alias table is now in place. Get rid of the saved, original
table. Ignore errors. }
if not IsTableNameOpen(DB.Folder, ffC_SavedAliasTableName) then
seDeleteTable(DB, ffC_SavedAliasTableName)
else
Result := DBIERR_TABLEOPEN;
{ The code jumps to this point if an error is detected in a ServerEngine
method. }
Cleanup:
except
{If an exception occurs, get the error code and fall through to the
cleanup code below. The error code will be returned to the calling
object. }
on E : Exception do
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
{ Put System table back into its rightful place if a failure occurred
after it was renamed to the saved table. }
if (State >= 1000) and (State < 1100) then
seTableRenamePrim(DB, ffc_SavedAliasTableName, ffc_AliasTableName);
{delete temporary table if it did not replace system table}
if (State >= 400) and (State < 1100) then
if not IsTableNameOpen(DB.Folder, ffc_TempAliasTableName) then
seDeleteTable(DB, ffc_TempAliasTableName)
else
Result := DBIERR_TABLEOPEN;
Dict.Free;
DB.Free;
Folder.Free;
{remove the client}
if State >= 100 then
seClientRemovePrim(Client);
end;
{=====================================================================}
{== Read/Write User data from table ==================================}
procedure TffServerEngine.ReadUserData;
var
aClientID : TffClientID;
BufFirst : TffName;
BufHash : TffWord32;
BufLast : TffName;
BufRights : TffUserRights;
BufUserID : TffName;
Client : TffSrClient;
Cursor : TffSrBaseCursor; {!!.06}
DBIResult : TffResult;
DB : TffSrDatabase;
Dict : TffDataDictionary;
Folder : TffSrFolder;
IsNull : boolean;
MyRec : PffByteArray;
SearchPath : TffPath;
begin
Client := nil;
Folder := nil;
DB := nil;
Cursor := nil;
try
{create ourselves a client}
DBIResult := ClientAdd(aClientID,
'',
ffc_AdminUserID,
1000,
BufHash);
if (DBIResult <> DBIERR_NONE) then
Exit;
{open a database (no User) to the server engine directory}
Client := TffSrClient(aClientID);
Folder := TffSrFolder.Create(ConfigDir, True, seBufMgr);
DB := seDatabaseOpenPrim(Client.clSessionList.CurrentSession,
Folder,
'',
omReadWrite,
smExclusive,
1000,
False); {!!.11}
if (DBIResult = DBIERR_NONE) then
FFSetRetry(DB.Timeout)
else
Exit;
Configuration.UserList.Empty;
{ If the table exists then read it. }
SearchPath := Folder.Path;
if (SearchPath[length(SearchPath)] <> '\') then
FFShStrAddChar(SearchPath, '\');
if FFFileExists(SearchPath + FFForceExtension(ffc_UserTableName, ffc_ExtForData)) then begin
Cursor := CursorClass.Create(Self, DB, 1000); {!!.06}
FFSetRetry(Cursor.Timeout); {!!.01}
Cursor.Open(ffc_UserTableName, '', 0, omReadOnly, smExclusive,
true, False, []);
Cursor.CloseTable := True;
Dict := Cursor.Dictionary;
FFGetMem(MyRec, Dict.RecordLength);
try
FFSetRetry(Cursor.Timeout);
Cursor.SetToBegin;
FFSetRetry(Cursor.Timeout); {!!.01}
DBIResult := Cursor.GetNextRecord(MyRec, ffsltNone);
while (DBIResult = DBIERR_NONE) do begin
Dict.GetRecordField(0, MyRec, IsNull, @BufUserID);
Dict.GetRecordField(1, MyRec, IsNull, @BufLast);
Dict.GetRecordField(2, MyRec, IsNull, @BufFirst);
Dict.GetRecordField(3, MyRec, IsNull, @BufHash);
Dict.GetRecordField(4, MyRec, IsNull, @BufRights);
Configuration.AddUser(BufUserID, BufLast, BufFirst, BufHash, BufRights);
FFSetRetry(Cursor.Timeout); {!!.01}
DBIResult := Cursor.GetNextRecord(MyRec, ffsltNone);
end;
finally
FFFreeMem(MyRec, Dict.RecordLength);
end;{try..finally}
end;
finally
{ Close the cursor. }
if assigned(Cursor) then
Cursor.Free;
DB.Free;
Folder.Free;
{ Remove the client. }
seClientRemovePrim(Client);
end;
end;
{--------}
function TffServerEngine.WriteUserData : TffResult;
label
Cleanup,
InnerCleanup;
var
aClientID : TffClientID;
BufHash : TffWord32;
BufRights : TffUserRights;
BufStr : TffShStr;
Dict : TffDataDictionary;
Folder : TffSrFolder;
Hash : TffWord32;
i : integer;
MyRec : PffByteArray;
State : integer;
TransID : TffTransID;
UserItem : TffUserItem;
Client : TffSrClient;
DB : TffSrDatabase;
Cursor : TffSrBaseCursor; {!!.06}
begin
Result := DBIERR_NONE;
with Configuration.GeneralInfo^ do
if giReadOnly or giNoAutoSaveCfg then
Exit;
Client := nil;
DB := nil;
Dict := nil;
Folder := nil;
Cursor := nil;
State := 0;
try
{ Strategy: Create a temporary table and write the data to that
table. If that works, rename the existing table and replace it with
the temporary table. If that succeeds, get rid of the old table.
If a failure occurs at any point, the old table must be put back
in its original place. }
{create ourselves a client}
Result := ClientAdd(aClientID, '', ffc_AdminUserID, 1000, Hash);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 100; { client added }
{open a database (no alias) to the server engine directory}
Client := TffSrClient(aClientID);
Folder := TffSrFolder.Create(ConfigDir, False, seBufMgr);
DB := seDatabaseOpenPrim(Client.clSessionList.CurrentSession,
Folder,
'',
omReadWrite,
smExclusive,
1000,
False); {!!.11}
if (Result = DBIERR_NONE) then
FFSetRetry(DB.Timeout)
else
goto Cleanup;
State := 200; { database opened }
{Make sure prior instances of the saved and temporary tables are deleted. }
seTableDeletePrim(DB, ffc_SavedUserTableName);
seTableDeletePrim(DB, ffc_TempUserTableName);
{create a dictionary}
Dict := TffServerDataDict.Create(4096);
State := 300; { dictionary created }
with Dict do begin
AddField('User', '', fftShortString, pred(sizeof(TffName)), 0, true, nil);
AddField('LastName', '', fftShortString, pred(sizeof(TffName)), 0, true, nil);
AddField('FirstName', '', fftShortString, pred(sizeof(TffName)), 0, true, nil);
AddField('PwdHash', '', fftWord32, 0, 0, true, nil);
AddField('Rights', '', fftByteArray, sizeof(TffUserRights), 0, true, nil);
end;
Dict.IsEncrypted := Configuration.GeneralInfo^.giAllowEncrypt;
{Create the new table as a temporary file. }
Result := seTableBuildPrim(DB, true, ffc_TempUserTableName, True, Dict);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 400; { temporary table created }
{start a transaction before opening the table}
Result := seTransactionStart(DB, false, ffcl_TrImplicit, TransID);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 500; { transaction started for opening table }
try
FFGetMem(MyRec, Dict.RecordLength);
State := 600;
{Insert new records.}
Cursor := CursorClass.Create(Self, DB, 1000); {!!.06}
FFSetRetry(Cursor.Timeout); {!!.01}
Cursor.Open(ffc_TempUserTableName, '', 0, omReadWrite, smExclusive,
True, False, []);
Cursor.CloseTable := True;
for i := 0 to pred(Configuration.UserList.Count) do begin
Cursor.Dictionary.InitRecord(MyRec);
UserItem := Configuration.UserList[i];
BufStr := UserItem.UserID;
Cursor.Dictionary.SetRecordField(0, MyRec, @BufStr);
BufStr := UserItem.LastName;
Cursor.Dictionary.SetRecordField(1, MyRec, @BufStr);
BufStr := UserItem.FirstName;
Cursor.Dictionary.SetRecordField(2, MyRec, @BufStr);
BufHash := UserItem.PasswordHash;
Cursor.Dictionary.SetRecordField(3, MyRec, @BufHash);
BufRights := UserItem.Rights;
Cursor.Dictionary.SetRecordField(4, MyRec, @BufRights);
FFSetRetry(Cursor.Timeout); {!!.01}
Cursor.InsertRecord(MyRec, ffsltExclusive);
if (Result <> DBIERR_NONE) then
goto InnerCleanup;
end;
State := 750;
{ Commit the transaction. }
FFSetRetry(Cursor.Timeout); {!!.01}
Result := seTransactionCommit(DB);
if Result = DBIERR_NONE then
State := 800; { transaction committed }
InnerCleanup:
finally
{ Rollback the transaction. }
if (State >= 500) and (State < 750) then
seTransactionRollback(DB);
if State >= 600 then
FFFreeMem(MyRec, Dict.RecordLength);
{close the cursor}
if assigned(Cursor) then
Cursor.Free;
end;{try..finally}
{ If the record insertions did not complete then jump to cleanup. }
if State < 800 then
goto Cleanup;
{ Rename the existing table. }
Result := seTableRenamePrim(DB, ffc_UserTableName, ffc_SavedUserTableName);
if (Result <> DBIERR_NOSUCHTABLE) and (Result <> DBIERR_NONE) then
goto Cleanup;
State := 1000; { renamed system table to saved table }
{ Replace the original table with the temporary table. }
Result := seTableRenamePrim(DB, ffc_TempUserTableName, ffc_UserTableName);
if Result <> DBIERR_NONE then
goto Cleanup;
State := 1100; { renamed temp table to system table }
{ The new table is now in place. Get rid of the saved, original
table. Ignore errors. }
if not IsTableNameOpen(DB.Folder, ffc_SavedUserTableName) then
seDeleteTable(DB, ffc_SavedUserTableName)
else
Result := DBIERR_TABLEOPEN;
{ The code jumps to this point if an error is detected in a ServerEngine
method. }
Cleanup:
except
{If an exception occurs, get the error code and fall through to the
cleanup code below. The error code will be returned to the calling
object. }
on E : Exception do
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
{ Put System table back into its rightful place if a failure occurred
after it was renamed to the saved table. }
if (State >= 1000) and (State < 1100) then
seTableRenamePrim(DB, ffc_SavedUserTableName, ffc_UserTableName);
{delete temporary table if it did not replace system table}
if (State >= 400) and (State < 1100) then
if not IsTableNameOpen(DB.Folder, ffc_TempUserTableName) then
seDeleteTable(DB, ffc_TempUserTableName)
else
Result := DBIERR_TABLEOPEN;
Dict.Free;
DB.Free;
Folder.Free;
{remove the client}
if State >= 100 then
seClientRemovePrim(Client);
end;
{=====================================================================}
{== Read/write general info from tables ==============================}
const
ffc_GeneralClientID = -1;
{--------}
procedure TffServerEngine.ReadGeneralInfo;
var
aClientID : TffClientID;
Client : TffSrClient;
Cursor : TffSrBaseCursor; {!!.06}
DB : TffSrDatabase;
DBIResult : TffResult;
Dict : TffDataDictionary;
Folder : TffSrFolder;
Hash : TffWord32;
IsNull : boolean;
MyRec : PffByteArray;
SearchPath : TffPath;
begin
Client := nil;
DB := nil;
Folder := nil;
Cursor := nil;
try
{create ourselves a client}
DBIResult := ClientAdd(aClientID, '', ffc_AdminUserID, 1000, Hash);
if (DBIResult <> DBIERR_NONE) then
Exit;
{open a database (no User) to the server engine directory}
Client := TffSrClient(aClientID);
Folder := TffSrFolder.Create(ConfigDir, True, seBufMgr);
DB := seDatabaseOpenPrim(Client.clSessionList.CurrentSession,
Folder,
'',
omReadWrite,
smExclusive,
1000,
False); {!!.11}
if (DBIResult = DBIERR_NONE) then
FFSetRetry(DB.Timeout)
else
Exit;
{ If the table exists then read it. }
SearchPath := Folder.Path;
if (SearchPath[length(SearchPath)] <> '\') then
FFShStrAddChar(SearchPath, '\');
if FFFileExists(SearchPath + FFForceExtension(ffc_GenInfoTableName, ffc_ExtForData)) then begin
{ Open a cursor to read the records. }
Cursor := CursorClass.Create(Self, DB, 1000); {!!.06}
FFSetRetry(Cursor.Timeout); {!!.01}
Cursor.Open(ffc_GenInfoTableName, '', 0, omReadOnly, smExclusive,
true, False, []);
Cursor.CloseTable := True;
Dict := Cursor.Dictionary;
FFGetMem(MyRec, Dict.RecordLength);
try
FFSetRetry(Cursor.Timeout);
Cursor.SetToBegin;
FFSetRetry(Cursor.Timeout);
DBIResult := Cursor.GetNextRecord(MyRec, ffsltNone);
if DBIResult = DBIERR_NONE then
with Configuration.GeneralInfo^ do begin
Dict.GetRecordField(0, MyRec, IsNull, @giServerName);
Dict.GetRecordField(1, MyRec, IsNull, @giMaxRAM);
Dict.GetRecordField(2, MyRec, IsNull, @giIsSecure);
Dict.GetRecordField(3, MyRec, IsNull, @giAutoUp);
Dict.GetRecordField(4, MyRec, IsNull, @giAutoMini);
Dict.GetRecordField(5, MyRec, IsNull, @giDebugLog);
Dict.GetRecordField(6, MyRec, IsNull, @giSingleUser);
Dict.GetRecordField(7, MyRec, IsNull, @giIPXSPX);
Dict.GetRecordField(8, MyRec, IsNull, @giIPXSPXLFB);
Dict.GetRecordField(9, MyRec, IsNull, @giTCPIP);
Dict.GetRecordField(10, MyRec, IsNull, @giTCPIPLFB);
Dict.GetRecordField(11, MyRec, IsNull, @giTCPPort);
Dict.GetRecordField(12, MyRec, IsNull, @giUDPPortSr);
Dict.GetRecordField(13, MyRec, IsNull, @giUDPPortCl);
Dict.GetRecordField(14, MyRec, IsNull, @giIPXSocketSr);
Dict.GetRecordField(15, MyRec, IsNull, @giIPXSocketCl);
Dict.GetRecordField(16, MyRec, IsNull, @giSPXSocket);
Dict.GetRecordField(17, MyRec, IsNull, @giAllowEncrypt);
Dict.GetRecordField(18, MyRec, IsNull, @giReadOnly);
Dict.GetRecordField(19, MyRec, IsNull, @giLastMsgInterval);
Dict.GetRecordField(20, MyRec, IsNull, @giKAInterval);
Dict.GetRecordField(21, MyRec, IsNull, @giKARetries);
Dict.GetRecordField(22, MyRec, IsNull, @giPriority);
Dict.GetRecordField(23, MyRec, IsNull, @giTCPInterface);
Dict.GetRecordField(24, MyRec, IsNull, @giNoAutoSaveCfg);
Dict.GetRecordField(25, MyRec, IsNull, @giTempStoreSize);
Dict.GetRecordField(26, MyRec, IsNull, @giCollectEnabled);
Dict.GetRecordField(27, MyRec, IsNull, @giCollectFreq);
end; { with }
finally
FFFreeMem(MyRec, Dict.RecordLength);
end;{try..finally}
end;
finally
{ Close the cursor. }
if assigned(Cursor) then
Cursor.Free;
DB.Free;
Folder.Free;
{ Remove the client. }
seClientRemovePrim(Client);
{ Update the logging state. }
seSetLoggingState;
end;
end;
{--------}
function TffServerEngine.WriteGeneralInfo(aOverrideRO : Boolean)
: TffResult;
label
Cleanup,
InnerCleanup;
var
aClientID : TffClientID;
MyRec : PffByteArray;
Folder : TffSrFolder;
Hash : TffWord32;
TransID : TffTransID;
Dict : TffServerDataDict;
Client : TffSrClient;
DB : TffSrDatabase;
Cursor : TffSrBaseCursor; {!!.06}
State : integer;
begin
Result := DBIERR_NONE;
{aOverrideRO is used to override the giReadOnly setting. If we didn't
have this option, there would be no way of saving the change when
setting giReadOnly from False to True}
with Configuration.GeneralInfo^ do
if ((giReadOnly or giNoAutoSaveCfg) and
(not aOverrideRO)) then
Exit;
State := 0;
Client := nil;
DB := nil;
Cursor := nil;
Dict := nil;
Folder := nil;
try
{ Strategy: Create a temporary table and write the data to that
table. If that works, rename the existing table and replace it with
the temporary table. If that succeeds, get rid of the old table.
If a failure occurs at any point, the old table must be put back
in its original place. }
{create ourselves a client}
Result := ClientAdd(aClientID, '', ffc_AdminUserID, 1000, Hash);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 100; {client added}
{open a database (no alias) to the server engine directory}
Client := TffSrClient(aClientID);
Folder := TffSrFolder.Create(ConfigDir, False, seBufMgr);
DB := seDatabaseOpenPrim(Client.clSessionList.CurrentSession,
Folder,
'',
omReadWrite,
smExclusive,
1000,
False); {!!.11}
if (Result = DBIERR_NONE) then
FFSetRetry(DB.Timeout)
else
goto Cleanup;
State := 200; {database opened}
{Make sure prior instances of the saved and temporary tables are deleted. }
seTableDeletePrim(DB, ffc_SavedGenInfoTableName);
seTableDeletePrim(DB, ffc_TempGenInfoTableName);
{build a new data dictionary (don't bother with an index)}
Dict := TffServerDataDict.Create(4096);
State := 300; {dict created}
with Dict do begin
AddField('ServerName', '', fftShortString, pred(sizeof(TffNetName)), 0, true, nil);
AddField('MaxRAM', '', fftWord32, 0, 0, True, nil);
AddField('IsSecure', '', fftBoolean, 0, 0, True, nil);
AddField('AutoUp', '', fftBoolean, 0, 0, True, nil);
AddField('AutoMini', '', fftBoolean, 0, 0, True, nil);
AddField('DebugLog', '', fftBoolean, 0, 0, True, nil);
AddField('UseSingleUser', '', fftBoolean, 0, 0, True, nil);
AddField('UseIPXSPX', '', fftBoolean, 0, 0, True, nil);
AddField('IPXSPXLFB', '', fftBoolean, 0, 0, True, nil);
AddField('UseTCPIP', '', fftBoolean, 0, 0, True, nil);
AddField('TCPIPLFB', '', fftBoolean, 0, 0, True, nil);
AddField('TCPPort', '', fftInt32, 0, 0, True, nil);
AddField('UDPPortSr', '', fftInt32, 0, 0, True, nil);
AddField('UDPPortCl', '', fftInt32, 0, 0, True, nil);
AddField('IPXSocketSr', '', fftInt32, 0, 0, True, nil);
AddField('IPXSocketCl', '', fftInt32, 0, 0, True, nil);
AddField('SPXSocket', '', fftInt32, 0, 0, True, nil);
AddField('UseEncrypt', '', fftBoolean, 0, 0, True, nil);
AddField('ReadOnly', '', fftBoolean, 0, 0, True, nil);
AddField('LstMsgIntvl', '', fftInt32, 0, 0, True, nil);
AddField('KAInterval', '', fftInt32, 0, 0, True, nil);
AddField('KARetries', '', fftInt32, 0, 0, True, nil);
AddField('Priority', '', fftInt32, 0, 0, True, nil);
AddField('TCPInterface', '', fftInt32, 0, 0, True, nil);
AddField('NoAutoSaveCfg', '', fftBoolean, 0, 0, True, nil);
Addfield('TempStoreSize', '', fftInt16, 0, 0, True, nil);
AddField('CollectEnabld', '', fftBoolean, 0, 0, True, nil);
AddField('CollectFreq', '', fftInt32, 0, 0, True, nil);
end;
Dict.IsEncrypted := Configuration.GeneralInfo^.giAllowEncrypt;
{build a new alias table}
Result := seTableBuildPrim(DB, True, ffc_TempGenInfoTableName, True,
Dict);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 400; {temporary table created}
{start a transaction before opening the table}
Result := seTransactionStart(DB, False, ffcl_TrImplicit, TransID);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 500; { transaction started for opening table }
try
{First, delete all existing records.}
FFGetMem(MyRec, Dict.RecordLength);
State := 600; {memory allocated for MyRec}
{Insert new record.}
Cursor := CursorClass.Create(Self, DB, 1000); {!!.06}
FFSetRetry(Cursor.Timeout); {!!.01}
Cursor.Open(ffc_TempGenInfoTableName, '', 0, omReadWrite, smExclusive,
True, False, []);
Cursor.CloseTable := True;
Cursor.Dictionary.InitRecord(MyRec);
with Configuration.GeneralInfo^, Cursor.Dictionary do begin
SetRecordField(0, MyRec, @giServerName);
SetRecordField(1, MyRec, @giMaxRAM);
SetRecordField(2, MyRec, @giIsSecure);
SetRecordField(3, MyRec, @giAutoUp);
SetRecordField(4, MyRec, @giAutoMini);
SetRecordField(5, MyRec, @giDebugLog);
SetRecordField(6, MyRec, @giSingleUser);
SetRecordField(7, MyRec, @giIPXSPX);
SetRecordField(8, MyRec, @giIPXSPXLFB);
SetRecordField(9, MyRec, @giTCPIP);
SetRecordField(10, MyRec, @giTCPIPLFB);
SetRecordField(11, MyRec, @giTCPPort);
SetRecordField(12, MyRec, @giUDPPortSr);
SetRecordField(13, MyRec, @giUDPPortCl);
SetRecordField(14, MyRec, @giIPXSocketSr);
SetRecordField(15, MyRec, @giIPXSocketCl);
SetRecordField(16, MyRec, @giSPXSocket);
SetRecordField(17, MyRec, @giAllowEncrypt);
SetRecordField(18, MyRec, @giReadOnly);
SetRecordField(19, MyRec, @giLastMsgInterval);
SetRecordField(20, MyRec, @giKAInterval);
SetRecordField(21, MyRec, @giKARetries);
SetRecordField(22, MyRec, @giPriority);
SetRecordField(23, MyRec, @giTCPInterface);
SetRecordField(24, MyRec, @giNoAutoSaveCfg);
SetRecordField(25, MyRec, @giTempStoreSize);
SetRecordField(26, MyRec, @giCollectEnabled);
SetRecordField(27, MyRec, @giCollectFreq);
end;
FFSetRetry(Cursor.Timeout); {!!.01}
Result := Cursor.InsertRecord(MyRec, ffsltExclusive);
if Result <> DBIERR_NONE then
goto InnerCleanup;
State := 750;
{ Commit the transaction. }
FFSetRetry(Cursor.Timeout); {!!.01}
Result := seTransactionCommit(DB);
if Result = DBIERR_NONE then
State := 800; { transaction committed }
InnerCleanup:
finally
{rollback the transaction}
if (State >= 500) and (State < 750) then
seTransactionRollback(DB);
{free memory for MyRec}
if State >= 600 then
FFFreeMem(MyRec, Dict.RecordLength);
{close the cursor}
if assigned(Cursor) then
Cursor.Free;
end; {try..finally}
{if the record wasn't inserted, goto cleanup}
if State < 800 then
goto Cleanup;
{ Rename the existing table. }
Result := seTableRenamePrim(DB, ffc_GenInfoTableName, ffc_SavedGenInfoTableName);
if (Result <> DBIERR_NOSUCHTABLE) and (Result <> DBIERR_NONE) then
goto Cleanup;
State := 1000; {table renamed}
{ Replace the original table with the temporary table. }
Result := seTableRenamePrim(DB, ffc_TempGenInfoTableName, ffc_GenInfoTableName);
if Result <> DBIERR_NONE then
goto Cleanup;
State := 1100; {renamed existing table}
{ The new table is now in place. Get rid of the saved, original
table. Ignore errors. }
if not IsTableNameOpen(DB.Folder, ffc_SavedGenInfoTableName) then
seDeleteTable(DB, ffc_SavedGenInfoTableName)
else
Result := DBIERR_TABLEOPEN;
Cleanup:
except
{If an error occurs at any point, we raise an exception. The
exception handling just falls through to the cleanup code below.}
on E: Exception do
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
{ Put System table back into its rightful place if a failure occurred
after it was renamed to the saved table. }
if (State >= 1000) and (State < 1100) then
seTableRenamePrim(DB, ffc_SavedGenInfoTableName, ffc_GenInfoTableName);
{delete the temporary table if it didn't replace the system table}
if (State >= 400) and (State < 1100) then
if not IsTableNameOpen(DB.Folder, ffc_TempGenInfoTableName) then
seDeleteTable(DB, ffc_TempGenInfoTableName)
else
Result := DBIERR_TABLEOPEN;
Dict.Free;
DB.Free;
Folder.Free;
{remove the client}
if State >= 100 then
seClientRemovePrim(Client);
end;
{=====================================================================}
{== Read/write key proc info from/to tables ==========================}
const
ffc_KeyProcClientID = -1;
{--------}
procedure TffServerEngine.ReadKeyProcData;
var
aClientID : TffClientID;
BufBuild : TffName;
BufCompare : TffName;
BufDLL : TffFullFileName;
BufIndexID : Longint;
BufPath : TffPath;
BufTable : TffTableName;
Client : TffSrClient;
Cursor : TffSrBaseCursor; {!!.06}
DB : TffSrDatabase;
DBIResult : TffResult;
Dict : TffDataDictionary;
Folder : TffSrFolder;
Hash : TffWord32;
IsNull : boolean;
MyRec : PffByteArray;
SearchPath : TffPath;
begin
Client := nil;
Folder := nil;
Cursor := nil;
DB := nil;
try
{create ourselves a client}
DBIResult := ClientAdd(aClientID, '', ffc_AdminUserID, 1000, Hash);
if (DBIResult <> DBIERR_NONE) then
Exit;
{open a database (no User) to the server engine directory}
Client := TffSrClient(aClientID);
Folder := TffSrFolder.Create(ConfigDir, True, seBufMgr);
DB := seDatabaseOpenPrim(Client.clSessionList.CurrentSession,
Folder,
'',
omReadWrite,
smExclusive,
1000,
False); {!!.11}
if (DBIResult = DBIERR_NONE) then
FFSetRetry(DB.Timeout)
else
Exit;
{read the records}
Configuration.KeyProcList.Empty;
{ If the table exists then read it. }
SearchPath := Folder.Path;
if (SearchPath[length(SearchPath)] <> '\') then
FFShStrAddChar(SearchPath, '\');
if FFFileExists(SearchPath + FFForceExtension(ffc_IndexTableName, ffc_ExtForData)) then begin
Cursor := CursorClass.Create(Self, DB, 1000); {!!.06}
FFSetRetry(Cursor.Timeout); {!!.01}
Cursor.Open(ffc_IndexTableName, '', 0, omReadOnly, smExclusive,
True, False, []);
Cursor.CloseTable := True;
Dict := Cursor.Dictionary;
FFGetMem(MyRec, Dict.RecordLength);
try
FFSetRetry(Cursor.Timeout);
Cursor.SetToBegin;
FFSetRetry(Cursor.Timeout); {!!.01}
DBIResult := Cursor.GetNextRecord(MyRec, ffsltNone);
while (DBIResult = DBIERR_NONE) do begin
Dict.GetRecordField(0, MyRec, IsNull, @BufPath);
Dict.GetRecordField(1, MyRec, IsNull, @BufTable);
Dict.GetRecordField(2, MyRec, IsNull, @BufIndexID);
Dict.GetRecordField(3, MyRec, IsNull, @BufDLL);
Dict.GetRecordField(4, MyRec, IsNull, @BufBuild);
Dict.GetRecordField(5, MyRec, IsNull, @BufCompare);
Configuration.AddKeyProc(BufPath, BufTable, BufIndexID,
BufDLL, BufBuild, BufCompare);
FFSetRetry(Cursor.Timeout); {!!.01}
DBIResult := Cursor.GetNextRecord(MyRec, ffsltNone);
end;
finally
FFFreeMem(MyRec, Dict.RecordLength);
end; {try..finally}
end;
finally
{ Close the cursor. }
if assigned(Cursor) then
Cursor.Free;
DB.Free;
Folder.Free;
{ Remove the client. }
seClientRemovePrim(Client);
end;
end;
{--------}
function TffServerEngine.WriteKeyProcData : TffResult;
label
Cleanup,
InnerCleanup;
var
aClientID : TffClientID;
BufInt : Longint;
BufStr : TffShStr;
Dict : TffDataDictionary;
Folder : TffSrFolder;
Hash : TffWord32;
i : integer;
KeyProcItem : TffKeyProcItem;
MyRec : PffByteArray;
State : integer;
TransID : TffTransID;
Client : TffSrClient;
DB : TffSrDatabase;
Cursor : TffSrBaseCursor; {!!.06}
begin
Result := DBIERR_NONE;
with Configuration.GeneralInfo^ do
if giReadOnly or giNoAutoSaveCfg then
Exit;
Client := nil;
DB := nil;
Dict := nil;
Folder := nil;
Cursor := nil;
State := 0;
try
{ Strategy: Create a temporary table and write the data to that
table. If that works, rename the existing table and replace it with
the temporary table. If that succeeds, get rid of the old table.
If a failure occurs at any point, the old table must be put back
in its original place. }
{create ourselves a client}
Result := ClientAdd(aClientID, '', ffc_AdminUserID, 1000, Hash);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 100; { client added }
{open a database (no alias) to the server engine directory}
Client := TffSrClient(aClientID);
Folder := TffSrFolder.Create(ConfigDir, False, seBufMgr);
DB := seDatabaseOpenPrim(Client.clSessionList.CurrentSession,
Folder,
'',
omReadWrite,
smExclusive,
1000,
False); {!!.11}
if (Result = DBIERR_NONE) then
FFSetRetry(DB.Timeout)
else
goto Cleanup;
State := 200; { database opened }
{Make sure prior instances of the saved and temporary tables are deleted. }
seTableDeletePrim(DB, ffc_SavedIndexTableName);
seTableDeletePrim(DB, ffc_TempIndexTableName);
{Prepare a data dictionary.}
Dict := TffServerDataDict.Create(4096);
State := 300; { dictionary created }
{Create the new table as a temporary file. }
with Dict do begin
AddField('Path', '', fftShortString, pred(sizeof(TffPath)), 0, true, nil);
AddField('Table', '', fftShortString, pred(sizeof(TffTableName)), 0, true, nil);
AddField('IndexID', '', fftInt32, 0, 0, true, nil);
AddField('DLL', '', fftShortString, pred(sizeof(TffFullFileName)), 0, true, nil);
AddField('BuildKey', '', fftShortString, pred(sizeof(TffName)), 0, true, nil);
AddField('CompareKey', '', fftShortString, pred(sizeof(TffName)), 0, true, nil);
end;
Dict.IsEncrypted := Configuration.GeneralInfo^.giAllowEncrypt;
Result := seTableBuildPrim(DB, true, ffc_TempIndexTableName, True, Dict);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 400; { temporary table created }
{ Start a transaction before opening the table. }
Result := seTransactionStart(DB, false, ffcl_TrImplicit, TransID);
if (Result <> DBIERR_NONE) then
goto Cleanup;
State := 500; { transaction started for opening table }
try
FFGetMem(MyRec, Dict.RecordLength);
State := 600;
Cursor := CursorClass.Create(Self, DB, 1000); {!!.06}
FFSetRetry(Cursor.Timeout); {!!.01}
Cursor.Open(ffc_TempIndexTableName, '', 0, omReadWrite, smExclusive,
True, False, []);
Cursor.CloseTable := True;
{Insert new records.}
for i := 0 to pred(Configuration.KeyProcList.Count) do begin
Cursor.Dictionary.InitRecord(MyRec);
KeyProcItem := Configuration.KeyProcList[i];
BufStr := KeyProcItem.Path;
Cursor.Dictionary.SetRecordField(0, MyRec, @BufStr);
BufStr := KeyProcItem.Table;
Cursor.Dictionary.SetRecordField(1, MyRec, @BufStr);
BufInt := KeyProcItem.IndexID;
Cursor.Dictionary.SetRecordField(2, MyRec, @BufInt);
BufStr := KeyProcItem.DLLName;
Cursor.Dictionary.SetRecordField(3, MyRec, @BufStr);
BufStr := KeyProcItem.BuildKeyName;
Cursor.Dictionary.SetRecordField(4, MyRec, @BufStr);
BufStr := KeyProcItem.CompareKeyName;
Cursor.Dictionary.SetRecordField(5, MyRec, @BufStr);
FFSetRetry(Cursor.Timeout); {!!.01}
Result := Cursor.InsertRecord(MyRec, ffsltExclusive);
if (Result <> DBIERR_NONE) then
goto InnerCleanup;
end;
State := 750;
{ Commit the transaction. }
FFSetRetry(Cursor.Timeout); {!!.01}
Result := seTransactionCommit(DB);
if Result = DBIERR_NONE then
State := 800; { transaction committed }
InnerCleanup:
finally
{ Rollback the transaction. }
if (State >= 500) and (State < 750) then
seTransactionRollback(DB);
if State >= 600 then
FFFreeMem(MyRec, Dict.RecordLength);
{close the cursor}
if assigned(Cursor) then
Cursor.Free;
end;{try..finally}
{ If the record insertions did not complete then jump to cleanup. }
if State < 800 then
goto Cleanup;
{ Rename the existing table. }
Result := seTableRenamePrim(DB, ffc_IndexTableName, ffc_SavedIndexTableName);
if (Result <> DBIERR_NOSUCHTABLE) and (Result <> DBIERR_NONE) then
goto Cleanup;
State := 1000; { renamed system table to saved table }
{ Replace the original table with the temporary table. }
Result := seTableRenamePrim(DB, ffc_TempIndexTableName, ffc_IndexTableName);
if Result <> DBIERR_NONE then
goto Cleanup;
State := 1100; { renamed temp table to system table }
{ The new table is now in place. Get rid of the saved, original
table. Ignore errors. }
if not IsTableNameOpen(DB.Folder, ffc_SavedIndexTableName) then
seDeleteTable(DB, ffc_SavedIndexTableName)
else
Result := DBIERR_TABLEOPEN;
{ The code jumps to this point if an error is detected in a ServerEngine
method. }
Cleanup:
except
{If an exception occurs, get the error code and fall through to the
cleanup code below. The error code will be returned to the calling
object. }
on E : Exception do
Result := ConvertServerExceptionEx(E, FEventLog, bseGetReadOnly);
end;
{ Put System table back into its rightful place if a failure occurred
after it was renamed to the saved table. }
if (State >= 1000) and (State < 1100) then
seTableRenamePrim(DB, ffc_SavedIndexTableName, ffc_IndexTableName);
{ Delete temporary table if it did not replace system table. }
if (State >= 400) and (State < 1100) then
if not IsTableNameOpen(DB.Folder, ffc_TempIndexTableName) then
seDeleteTable(DB, ffc_TempIndexTableName)
else
Result := DBIERR_TABLEOPEN;
Dict.Free;
DB.Free;
Folder.Free;
{remove the client}
if State >= 100 then
seClientRemovePrim(Client);
end;
{--------}
procedure TffServerEngine.CreateAdminUser(SaveToDisk : Boolean);
var
Hash : TffWord32;
begin
Hash := FFCalcShStrELFHash('flashfiler');
Configuration.AddUser(ffc_AdminUserID,
'Administrator',
'',
Hash,
ffc_AdminRights);
if SaveToDisk then
WriteUserData;
end;
{====================================================================}
{===Initialization===================================================}
procedure InitializeUnit;
var
i : integer;
Temp : string[5];
begin
{a simple encryption to thwart casual hackers: 'ojneb' will appear
in the EXE, not 'admin'}
Temp := 'ojneb';
ffc_AdminUserID[0] := #5;
for i := 1 to 5 do
ffc_AdminUserID[i] := char(ord(Temp[6-i]) - 1);
end;
{====================================================================}
initialization
InitializeUnit;
end.