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

4526 lines
164 KiB
ObjectPascal

{*********************************************************}
(* Implementation of all driver functions *)
(* Direct port of the original PHYSDB.CPP source file *)
{*********************************************************}
(* ***** 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 ffcrdefn.inc}
unit ffcrmain;
{ The import unit can be built by copying the interface section of this
unit and globally replacing "stdcall;" with "external 'PDBFF.DLL';" }
{
This file contains the interface definition used by all Brahma physical
database DLL's.
Brahma supports three basic types of DLLs, physical database (PhysDb.hpp),
physical dictionary (PhysDict.hpp), and physical directory (PhysDir.hpp).
These DLLs provide some similar functions, but differ as follows:
PhysDb: - Supports single physical database tables (assumed to be
stored in single files), and provides both retrieving of
database info and reading of database records.
- May be able to retrieve structural and index info of a
single table, but has no support for links between multiple
tables.
- Performs reading of database records (sequentially or using
an index).
PhysDict: - Supports retrieving of database info from multiple database
tables, but has no support for reading of database records.
- May be able to retrieve structural, index and link
information of multiple tables.
- Is knowledgeable of PhysDb database table types, and informs
the Database Manager of these types for reading of database
records.
PhysDir: - Supports a directory of multiple database files, but does not
perform retrieving of database info or reading of database
records itself.
- Is knowledgeable of PhysDb and PhysDict DLLs, and informs
the Database Manager which DLL to use for servicing each
entry in its directory.
Since each physical database, dictionary and directory is implemented as a
DLL, other database types can be defined and linked dynamically to the
Database Manager in the future.
Note: As mentioned above, physical database DLLs are responsible for
individual database tables only, and handling the links between multiple-
table databases is the responsibility of the Database Manager. The
physical database DLL must support multiple open database tables at a
time however.
Friendly advice: No global static data should be used in the
implementation of a physical database DLL. This makes it easier to
support multiple open files per report, and multiple open sets of files
for multiple reports, by letting the Database Manager save state
information instead.
The general rule is that whenever any state information is required
by the DLL it is dynamically allocated and a reference to it passed back
to the Database Manager. The Database Manager is then responsible for
storing this reference, passing it to the DLL whenever it is needed, and
calling the DLL to free the associated information.
Error Messages: When any DLL function cannot complete successfully, it
has a choice of returning an error code (PhysDbError type) or an error
message (code PhysDbErrMsgReturned, and returning a message in ErrMsg
parameter). The recommended behavior of the DLL is:
- Return an error string if no error code matches the situation
well, or if very specific information is available that would help
the user (e.g. "Please execute DOS share program", "Table is
corrupted at record 15", etc.). If an error string is returned it
will be displayed by the Database Manager.
- Return an error code in all other cases. The Database Manager
will display a standard error message of its own in these cases,
which will be consistent for all physical database types
(e.g. "Not enough memory", "File could not be found", etc.).
}
{$DEFINE IDAPI_INTERNAL_LIMITS}
interface
uses
ffllbase,
fflllog, {!!.12}
ffcrptyp,
ffcrtype,
ffclreng,
ffstdate, {!!.02}
SysUtils;
{ --------------------- Database Abilities ------------------------ }
{ Return physical database version number. }
function PhysDbVersionNumber(
var MajorVersionNumber : Word;
var MinorVersionNumber : Word;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ Return whether this physical database can recognize data files of
its own type. For example, a Paradox physical database DLL can recognize
a data file passed to it by its file name extension and internal header
information, whereas an ASCII physical database DLL cannot uniquely
identify a data file as being of its type.
If this returns true, the Database Manager may pass arbitrary file names to
the function OpenDataFileIfRecognizedVer12, and assumes it only opens data
files belonging to it. If this is false the function OpenDataFileIfRecognizedVer12
is only called when the user has confirmed that a file is of this data
type (via a dialog of FetchDatabaseName names) and can assume that the
type is correct. }
function CanRecognizeDataFile(var CanRecognize : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ Return whether this physical database can retrieve info describing
an open data file (whether flat or recurring records, number of
fields in the file, the width & type of each data field, etc.).
This can be done by the physical database either by "inhaling" the
data file information (without user interaction), or by displaying
Windows dialogs to prompt the user for this information.
If this returns true, the Database Manager calls FetchDataFileInfo
to retrieve this info, if false the Database Manager uses default Windows
dialogs of its own to prompt the user to provide this information. }
function CanFetchDataFileInfo(var CanFetchFileInfo : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ Return whether this physical database can fetch index information
for data files of its type. There are four possible cases:
1. indexesNeverExist: e.g. for ASCII files.
2. indexesExistButNotKnown: e.g. if not implemented yet.
3. someIndexesKnown: e.g. for dBase, default indexes known, but
others may exist.
4. allIndexesKnown: e.g. for Paradox, all indexes known by system.
In cases 3 and 4 the function FetchDataFileIndexInfo is called to
retrieve information on all known indexes. In cases 2 and 3 the
Database Manager uses default Windows dialogs to allow the user to
select file names containing indexes. }
function CanFetchDataFileIndexInfo(
var CanFetchIndexInfo : TPhysDbIndexInfoCases;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ Return whether this physical database can build indexes on data files
if required. There are three possible cases:
1. cannotBuildIndex
2. canBuildNonMaintainedIndex
3. canBuildMaintainedIndex }
function CanBuildIndex(var CanBuildIndex : TPhysDbBuildIndexCases;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ Return whether this physical database can (efficiently) retrieve the
number of records in an open data file. This information is required
by the Database Manager to estimate the % completion of reading of a
data file.
If this returns true, the Database Manager calls NRecurringRecordsToRead
to retrieve the record count, if false it does not.
Note: It is not recommended to read the entire data file to determine
the number of records, since performance will be seriously slowed.
Therefore if the physical database system does not easily provide this
info, this function should return false indicating that the ability is
not provided. }
function CanFetchNRecurringRecords(var CanFetchNrecords : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function is to tell whether this DLL has SQL functionality,
three parameters are passed back, isSQLTypeDLL, canBuildAndExecSQLQuery,
and canExecSQLQuery. }
function SQLCompatible(
var IsSQLTypeDLL : TcrBoolean;
var CanBuildAndExecSQLQuery : TcrBoolean;
var CanExecSQLQuery : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ Return if physical database supports reading of main file using an index.
This is a speed-up option, since Brahma will not need to sort the report. }
function CanReadSortedOrder(var CanReadSorted : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ Return if physical database supports selecting records using a range.
This is a speed-up option, since Brahma will only be given records
matching the record selection criteria. }
function CanReadRangeOfValues(var CanReadRange : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ Each physical database type may or may not support multi-user access.
If a database does support multi-user access, it may allow a choice of
either file locking or record locking, or it may always use one method.
This function returns whether record locking is available for this
physical database type. }
function CanUseRecordLocking(var RecordLockingPossible : TcrBoolean;
var RecordLockingPreferred : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function returns whether file locking is available for this
physical database type. }
function CanUseFileLocking(var FileLockingPossible : TcrBoolean;
var FileLockingPreferred : TcrBoolean;
ErrMsg: PAnsiChar) : TPhysDbError; stdcall;
{ ---------------- Initialization and Termination ----------------- }
{ Any database system initialization is performed at this point.
Note: No global static structures should be allocated, as discussed in
the program header above. }
function InitPhysicalDatabase(ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ Termination of the database system is performed at this point. }
function TermPhysicalDatabase(ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ OpenSession and TermSession are called to initialize and terminate on
a per task basis. The Database Manager determines when a new task
attempts to use a DLL, and calls OpenSession at that time. }
function OpenSession(ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
function TermSession(ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ ------------------------- Database Name -------------------------- }
{ Return the text name of this physical database format. This is used
in Database Manager dialogs to describe the database type of a data
file, and to store with a database dictionary to describe which physical
database DLL to use for a file. }
function FetchDatabaseName(var Name : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ Free the text name of this physical database format. }
function FreeDatabaseName(var Name : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ ---------------------- Log On and Log Off ----------------------- }
{ These functions allow the CRPE user to pass log on and log off
information to a PhysDB DLL.
Note: These functions are only required if the database supports
password-protected database files (e.g. Paradox). Otherwise
they do not need to be implemented. }
function LogOnServer(ServerInfo : PPhysDbServerInfo;
var ServerHandle : PPhysDbServerHandle;
Password : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
function LogOffServer(var ServerHandle : PPhysDbServerHandle;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ ------------------- Parse and Rebuild SQL Info -------------------- }
{ These functions are helpers to parse SQL connect info passed
down from a PhysDir type DLL.
Note: These functions are only useful for MS Access tables. These
functions do not need to be implemented in any other case. In
general, for SQL databases the PhysDs.hpp (PDS*.DLL) interface
should be used.
SST: These routines must be exported even if they are not used or Crystal
will not load the driver DLL. }
function ParseLogOnInfo(ConnectBuf : PAnsiChar;
BufSize : Word;
ServerInfo : PPhysDbServerInfo;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
function RebuildConnectBuf(ServerInfo : PPhysDbServerInfo;
ConnectBuf : PAnsiChar;
BufSize : Word;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ -------------------- Open and Close Files ----------------------- }
{ This function is passed a file name, including its path and extension,
and determines whether it is a data file of its physical database type,
and if so opens the data file and returns a file handle.
This function is called the first time a data file is attempted to be
opened, and before fetching the file info (from FetchDataFileInfo) and
index info (from FetchDataFileIndexInfo) structures that describe the
file. This function may also be called to open a data file for
sequential reading (without an index) using ReadNextRecurringRecord.
The Database Manager may pass arbitrary file names to this function,
and assumes it only opens data files belonging to it. If it is false
this function is only called when the user has confirmed that a file
is of this data type (via a dialog of FetchDatabaseName names) and this
function opens the file as if the database type is correct.
The new parameter logOnInfo can contain a password to use in opening
password-protected files.
Note: The parameter sessionInfo is only of use for MS Access DLLs
that track user session info. The parameter dirInfo is also only
currently useful for MS Access DLLs.
The parameter silentMode is used to tell DLL whether to pop up any
dialog or message itself or just return an error code.
The parameter aliasName allows the DLL to pass back its own alias
name to be used for the file, it can ignore this parameter if it wants
to use the default alias.
The parameter calledFromDirDLL indicates whether the user has
chosen a directory or database type file. If the user chose a
directory type file, the directory file says to call this database
DLL with an internal file name. }
function OpenDataFileIfRecognizedVer113(
FileName : PAnsiChar;
OpenDefaultIndex : TcrBoolean;
var Recognized : TcrBoolean;
var FileHandle : PPhysDbFileHandle;
CalledFromDirDLL : TcrBoolean;
var AliasName : PAnsiChar;
SilentMode : TcrBoolean;
DirInfo : PPhysDbFileDirectoryInfo;
DictInfo : PPhysDbFileDictionaryInfo;
SessionInfo : PPhysDbSessionInfo;
LogOnInfo : PPhysDbLogOnInfo;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function is passed both a data file name and index file name,
including paths and extensions, and determines whether they are of
its physical database type. If so it opens the data file using the
specified index, and returns a file handle.
This function is called when the user has selected an index file
to attempt to open, or to open a data file for reading, using either
ReadNextRecurringRecord (in the order of the chosen index file) or
LookupMatchingRecurringRecord to search directly for a record (using the
chosen index file).
This function will only be called if CanFetchDataFileIndexInfo has
returned indexesExistButNotKnown or someIndexesKnown.
The new parameter logOnInfo can contain a password to use in opening
password-protected files.
Note: The parameter sessionInfo is only of use for MS Access DLLs
that track user session info. The parameter dirInfo is also only
currently useful for MS Access DLLs.
The parameter silentMode is used to tell DLL whether to pop up any
dialog or message itself or just return an error code.
The parameter aliasName allows the DLL to pass back its own alias
name to be used for the file, it can ignore this parameter if it wants
to use the default alias. }
function OpenDataAndIndexFileIfRecogV113(
FileName : PAnsiChar;
IndexName : PAnsiChar;
var Recognized : TcrBoolean;
var FileHandle : PPhysDbFileHandle;
var AliasName : PAnsiChar;
SilentMode : TcrBoolean;
DirInfo : PPhysDbFileDirectoryInfo;
DictInfo : PPhysDbFileDictionaryInfo;
SessionInfo : PPhysDbSessionInfo;
LogOnInfo : PPhysDbLogOnInfo;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function is passed a file name, including its path and extension,
and the file info (from FetchDataFileInfo) and index info (from
FetchDataFileIndexInfo) structures, with usedInRead field set to
indicate the index file chosen. This function opens the data file
using the chosen index and returns a file handle.
This function is called to open a data file for reading, using either
ReadNextRecurringRecord (in the order of the chosen index file) or
LookupMatchingRecurringRecord to search directly for a record (using the
chosen index file).
This function can assume that the data file is of its physical
database type, since it was opened and recognized previously in order
to fetch the file info and index info passed as parameters.
The new parameter logOnInfo can contain a password to use in opening
password-protected files.
Note: The parameter sessionInfo is only of use for MS Access DLLs
that track user session info. The parameter dirInfo is also only
currently useful for MS Access DLLs.
The parameter silentMode is used to tell DLL whether to pop up any
dialog or message itself or just return an error code. }
function OpenDataFileAndIndexChoiceVer113(
FileName : PAnsiChar;
InfoPtr : PPhysDbFileInfo;
IndexesPtr : PPhysDbIndexesInfo;
var FileHandle : PPhysDbFileHandle;
SilentMode : TcrBoolean;
DirInfo : PPhysDbFileDirectoryInfo;
DictInfo : PPhysDbFileDictionaryInfo;
SessionInfo : PPhysDbSessionInfo;
LogOnInfo : PPhysDbLogOnInfo;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function closes a data file opened with OpenDataFileIfRecognized,
OpenDataAndIndexFileIfRecognized or OpenDataFileAndIndexChoice, and
deletes any allocated memory structures. }
function CloseDataFile(var FileHandle : PPhysDbFileHandle;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ ---------------------- Fetch Data File Info --------------------- }
{ This function is passed a file handle of an open data file, and returns
info describing its file structure (whether flat or recurring records,
number of fields in the file, the width & type of each data field,
etc.)
This function may retrieve this information by:
1. "Inhaling" the data file information (without user interaction),
if it has facilities to query the data file definition directly.
2. Displaying Windows dialogs to prompt the user for this information,
and then returning these values as the file structure.
This function is only called if CanFetchDataFileInfo has previously
returned true. If it has not, the Database Manager uses default Windows
dialogs to allow the user to describe the data file structure (with
obvious risks of error).
The parameter infoDefaultsExist is only meaningful in case 2 above.
(In case 1 the function should always retrieve the most current data
file definition from the system.) In case 2 if this parameter is true
the user has executed this function on this table before, and if
false this is the first time. If true, the previous values are passed as
defaults in the info structure, and the function can display them as
defaults in its Windows dialogs.
Note: This function is not responsible for filling in certain information
in PhysDbFileInfo:
- nBytesInReadRecord
- nFieldsInReadRecord
- nBytesInIndexRecord
- nFieldsInIndexRecord
and certain information in PhysDbFieldInfo:
- usedInReadRecord
- offsetInReadRecord
- usedInIndexRecord
- offsetInIndexRecord
This information is only meaningful in the InitDataFile functions
below. It can be set to zero or ignored by FetchDataFileInfo. }
function FetchDataFileInfo(
FileHandle : PPhysDbFileHandle;
InfoDefaultsExist : TcrBoolean;
var InfoPtr : PPhysDbFileInfo;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function frees the file info structure allocated by FetchDataFileInfo. }
function FreeDataFileInfo(
var InfoPtr : PPhysDbFileInfo;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ ------------------- Fetch Data File Index Info ------------------ }
{ This function is passed a file handle of an open data file, and returns
an index info structure (such as the number of known indexes, which
fields are used in each index definition, etc.). The fields in an
index definition are identified by their (0-origin) index in the
PhysDbFileInfoPtr->fieldInfo array of fields returned by FetchDataFileInfo.
The file info structure is passed as a parameter to this function to
look up these field numbers.
This function is expected to "inhale" the index information (without user
interaction) by querying the data file definition directly.
This function is only called if CanFetchDataFileIndexInfo has previously
returned someIndexesKnown or allIndexesKnown. If indexesExistButNotKnown
or someIndexesKnown the Database Manager uses default Windows dialogs
to allow the user to select file names containing indexes.
Note: This function is not responsible for filling in certain information
in PhysDbIndexInfo:
- usedInRead
This information is only meaningful in the function OpenDataFileAndIndexChoice
above. It can be set to zero or ignored by FetchDataFileIndexInfo. }
function FetchDataFileIndexInfo(
FileHandle : PPhysDbFileHandle;
InfoPtr : PPhysDbFileInfo;
var IndexesPtr : PPhysDbIndexesInfo;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function frees the index info structure created by
FetchDataFileIndexInfo. }
function FreeDataFileIndexInfo(var IndexesPtr : PPhysDbIndexesInfo;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ ---- Initialization and Termination of Reading from Data File ---- }
{ Note: This function is only useful for non-SQL databases that prefer a
SQL-type interface. In general, for SQL databases the PhysDs.hpp
(PDS*.DLL) interface should be used.
SST: This routine must be exported even if it is not used or Crystal
will not load the driver DLL. }
function BuildAndExecSQLQuery(
FileHandleList : PPhysDbFileHandleArray;
FileInfoList : PPhysDbFileInfoArray;
LinkNonSQLFlags : PcrBooleanArray;
IndexesInfoList : PPhysDbIndexesInfoArray;
RangeInfoList : PPhysDbRangeInfoArray;
NFiles : Word;
LinkInfoList : PPhysDbFileLinkInfoArray;
NFileLinks : Word;
SqlDrivingFile : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function is passed a file handle of an open data file, and the
file info structure (from FetchDataFileInfo) describing this file,
before starting to read from the file. This function should perform
any file initialization, and determine the sets of fields to be read
for each record.
During the Database Manager print cycle, the physical database functions
are called as follows for each data file to be read:
if (OpenDataFileIfRecognizedVer12 ()) // or OpenDataFileAndIndexChoice ()
if (InitDataFileForReading ()) // or InitDataFileAndIndexForReading ()
... // perform reading
TermDataFileForReading ()
CloseDataFile ()
Important: This function must not interfere with other data files
being read at the same time by this physical database implementation.
Therefore no global (static) data should be used by this function,
and all state information needed during reading should be kept local
to its own file handle. This function should also not perform any
global initialization of the database system that will affect other
open data files, (this can be done during InitPhysicalDatabase instead).
Translated and Non-Translated Fields: The Database Manager specifies
two sets of fields to be read from each data record, using the
additional information in the file info structure:
- nBytesInReadRecord
- nFieldsInReadRecord
- nBytesInIndexRecord
- nFieldsInIndexRecord
and in each field info structure:
- usedInReadRecord
- offsetInReadRecord
- usedInIndexRecord
- offsetInIndexRecord
The two sets of fields are required for different purposes. The
fields indicated by usedInReadRecord are used in the printed report
and must be translated to generic Brahma data types before returning.
The fields flagged by usedInIndexRecord are used in constructing an
index value for looking up records in another file, and should
not be translated from their native format.
The function now allows the main file of the report to be opened using
an index, to speed up sorting and selection of records.
The function now also allows an array of range values.
If indexesPtr is NULL, OpenDataFileIfRecognizedVer12 was called to
open the data file. If indexesPtr is non-NULL, OpenDataFileAndIndexChoice
was called to open the file, and indexPtr contains the index choice.
This function returns in canDoLimitRange whether it is able to perform
the range check on this particular field type. }
function InitDataFileForReadingVer17(
FileHandle : PPhysDbFileHandle;
InfoPtr : PPhysDbFileInfo;
IndexesPtr : PPhysDbIndexesInfo;
RangeInfoList : PPhysDbRangeInfoArray;
NRanges : TcrInt16u;
var CanDoRangeLimit : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function serves the same purpose as InitDataFileForReading, but
is called when initializing reading from a file with an index,
whereas InitDataFileForReading is called when reading from a file
without. The index info structure (from FetchDataFileIndexInfo) is
passed to this function to identify the chosen index. }
function InitDataFileAndIndexForReadV115(
FileHandle : PPhysDbFileHandle;
InfoPtr : PPhysDbFileInfo;
IndexesPtr : PPhysDbIndexesInfo;
LookupOptPtr : PPhysDbLookupOptInfo;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function frees the read state information allocated by
InitDataFile functions. }
function TermDataFileForReading(
FileHandle : PPhysDbFileHandle;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ --------------- Number of Records in Data File ------------------- }
{ This function is passed a file handle of an open data file, and
returns the number of recurring records in the file. }
function NRecurringRecordsToRead(
FileHandle : PPhysDbFileHandle;
var NRecordsToRead : LongInt;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ ----------------------- Read Functions --------------------------- }
{ The following comments apply to all three of the functions for
data file reading: ReadFlatRecord, ReadNextRecurringRecord, and
LookupMatchingRecurringRecord.
Translated and Non-Translated Fields: The Database Manager requires
two sets of fields to be returned from each data record, as explained in
InitDataFile functions above. The two buffers readRecordBuf and
indexRecordBuf are passed to these functions for the two sets of field
values. As well indexNullFlags is an array of flags indicating whether
a field has special database "null value" and its indexRecordBuf entry
should be ignored.
The two sets of fields are required for different purposes. The fields
returned in readRecordBuf are used in the printed report and must be
translated to generic Brahma data types before returning. The fields
returned in indexRecordBuf are used in constructing an index value for
looking up records in another file, and should not be translated from
their native format. }
{ --------------------- Read Flat File Record ---------------------- }
{ This function is passed a file handle of an open flat data file, and
reads the first data record. }
function ReadFlatRecordVer15(
FileHandle : PPhysDbFileHandle;
ReadRecordBuf : PByteArray;
ReadNullFlags : PcrBooleanArray;
IndexRecordBuf : PByteArray;
IndexNullFlags : PcrBooleanArray;
var RecordRead : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ -------- Read Next Recurring Record (Sequential Access) ---------- }
{ This function is passed a file handle of an open data file, and
reads the next data record (from its current file position) sequentially.
It sets recordRead to true if it is successful, and to false if it is
at end of file. }
function ReadNextRecurringRecordVer15(
FileHandle : PPhysDbFileHandle;
ReadRecordBuf : PByteArray;
ReadNullFlags : PcrBooleanArray;
IndexRecordBuf : PByteArray;
IndexNullFlags : PcrBooleanArray;
var RecordRead : TcrBoolean;
var NRecordsSkipped : LongInt;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ ------- Lookup Matching Recurring Record (Random Access) --------- }
{ This function is passed a file handle of an open data file, a lookup
value and whether to start searching from first record, and looks up
a record matching the lookup value. This function is only called if
OpenDataFileAndIndexChoice has been called to open the data file.
The lookup value passed in the parameters lookupValueRecordBuf and
lookupValueNullFlags agrees in type and ordering with the fields of the
index chosen in the file open call. The lookup value fields are not
translated from the native field format, so no translation needs to occur
back to their native format when doing record lookup.
If the parameter startTopOfFile is true this function should begin its
search from the beginning of the data file, if it is false it should
search from its current file position.
This function sets recordRead to true if it is successful, and to false
if it is at end of file. }
function LookupMatchingRecurringRecVer15(
FileHandle : PPhysDbFileHandle;
LookupValueRecordBuf : PAnsiChar;
LookupValueNullFlags : PcrBooleanArray;
LookupValueType : Word;
StartTopOfFile : TcrBoolean;
ReadRecordBuf : PByteArray;
ReadNullFlags : PcrBooleanArray;
IndexRecordBuf : PByteArray;
IndexNullFlags : PcrBooleanArray;
var RecordRead : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ ------------------------- Memo Fields ---------------------------- }
{ There are two types of memo fields: transientMemoField and
persistentMemoField. A transient memo field is one that must
be read at the same time as the recurring data record, and a
persistent memo field is one that can be read at any later point.
For example, dBase supports persistent memo fields by storing a
memo field number in the data record that uniquely identifies the
field value in the memo file. This field number can be stored
by the physical database in the recurring record, and then read from
the memo file at any later point.
Persistent memo fields are preferred by Brahma, since the (potentially
very large) variable length text values do not need to be saved with
the data record (including buffering in memory, sorting, etc.)
The following functions are used to support memo fields. The
functions FetchMemoField and FreeMemoField are only called for fields
identified as transientMemoField's by this physical database.
The functions FetchPersistentMemoField and FreePersistentMemoField
are only called for fields identified as persistentMemoField's by
this physical database.
Memo field identifiers are stored in data records returned to Brahma
by the above Read functions, and these identifiers are used to
retrieve the memo field value. }
function FetchMemoField(MemoFieldRecordBuf : PAnsiChar;
var MemoField : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
function FreeMemoField(var MemoField : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
function FetchPersistentMemoField(FileHandle : PPhysDbFileHandle;
MemoFieldRecordBuf : PAnsiChar;
var MemoField : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
function FreePersistentMemoField(FileHandle : PPhysDbFileHandle;
var MemoField : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ --------------------- Multi-User Access -------------------------- }
{ This function is called to tell the physical database functions to use
record locking when reading from the database file(s). }
function UseRecordLocking(
FileHandle : PPhysDbFileHandle;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{ This function is called to tell the physical database functions to use
file locking when reading from the database file(s). }
function UseFileLocking(
FileHandle : PPhysDbFileHandle;
ErrMsg : PAnsiChar) : TPhysDbError; stdcall;
{===DEBUG LOGGING===}
procedure StartLog;
procedure EndLog;
procedure AddToLog(const S : string);
procedure AddToLogFmt(const S : string; args : array of const); {!!.12}
procedure AddBlockToLog(const S : string; Buf : pointer; BufLen : TffMemSize);
procedure AddResultToLog(aResult : TPhysDbError);
implementation
uses
Dialogs,
Forms,
Classes,
Windows,
ffclbde,
ffsrbde,
ffclconv,
ffcrltyp,
ffcrutil,
ffllunc,
ffdb,
fflleng,
ffdbbase;
type
TTaskListItem = record
TaskHandle : THandle;
AlreadyInitialized : Boolean;
end;
PTaskListItem = ^TTaskListItem;
TTaskList = class(TList)
function AddTask(TaskHandle: THandle) : TPhysDbError;
function DeleteTask(var TaskFound: Boolean;
var AlreadyInitialized: Boolean;
ErrMsg: PAnsiChar) : TPhysDbError;
function FindTask(TaskHandle: THandle) : integer;
function NewTask(var TaskFound: Boolean;
var TaskIndex: integer;
ErrMsg: PAnsiChar) : TPhysDbError;
end;
var
TaskList : TTaskList;
IsTaskSuccess : Boolean;
DebugBuff : array[0..1023] of AnsiChar;
Log : TffEventLog; {!!.12}
{$IFDEF IDAPI_INTERNAL_LIMITS}
const
MAX_DBS_PER_SESSION = 32;
nOpenDatabase: Word = 0;
{$ENDIF}
function ServerEngine : TffBaseServerEngine;
{return the default sessions server engine}
begin
Result := FFSession.ServerEngine;
end;
{ ----------------------- TTaskList methods ------------------------- }
function TTaskList.AddTask(TaskHandle: THandle) : TPhysDbError;
var
Item : PTaskListItem;
begin
try
FFGetMem(Item, sizeof(TTaskListItem));
Item^.TaskHandle := TaskHandle;
Item^.AlreadyInitialized := False;
Add(Item);
Result := errPhysDbNoError;
except
Result := errPhysDbNotEnoughMemory;
end;
end;
function TTaskList.DeleteTask(var TaskFound: Boolean;
var AlreadyInitialized: Boolean;
ErrMsg: PAnsiChar) : TPhysDbError;
var
Item : PTaskListItem;
TaskHandle : THandle;
TaskIndex : integer;
begin
TaskFound := False;
AlreadyInitialized := False;
TaskHandle := HInstance; {!!GetCurrentTask }
TaskIndex := FindTask(TaskHandle);
if TaskIndex <> -1 then begin
TaskFound := True;
Item := PTaskListItem(TaskList.Items[TaskIndex]);
AlreadyInitialized := Item^.AlreadyInitialized;
FFFreeMem(Item, sizeof(TTaskListItem));
Delete(TaskIndex);
end;
Result := errPhysDbNoError;
end;
function TTaskList.FindTask(TaskHandle: THandle) : integer;
var
i : integer;
begin
Result := -1;
for i := 0 to pred(Count) do
if PTaskListItem(Items[i])^.TaskHandle = TaskHandle then begin
Result := i;
Break;
end;
end;
function TTaskList.NewTask(var TaskFound : Boolean;
var TaskIndex : integer;
ErrMsg : PAnsiChar) : TPhysDbError;
var
TaskHandle : THandle;
begin
TaskFound := False;
{ See if current task is already in the list of tasks }
TaskHandle := HInstance; {GetCurrentTask;}
TaskIndex := FindTask(TaskHandle);
if TaskIndex <> -1 then begin
TaskFound := True;
Result := errPhysDbNoError;
Exit;
end;
{ If not, then add it }
TaskIndex := Count;
Result := AddTask(TaskHandle);
end;
{ ----------------------- Helper Routines ------------------------- }
function IDAPIError(ErrCode: TffResult; var ErrMsg: PAnsiChar) : TPhysDbError;
begin
with EffDatabaseError.CreateViaCode(ErrCode, False) do
try
StrPCopy(ErrMsg, ErrorString);
finally
Free;
end;
AddToLogFmt(' IDAPI Error: [%s]', [ErrMsg]);
Result := errPhysDbErrMsgReturned;
end;
function Convert2BrahmaType(FileHandle : PPhysDbFileHandle;
NativeType : TcrInt16u;
var NativeWidth : TcrInt16u;
var BrahmaType : TFieldValueType;
var BrahmaWidth : TcrInt16u;
ErrMsg : PAnsiChar) : TPhysDbError;
var
BookmarkSize : Integer;
FFError : TffResult;
begin
Result := errPhysDbNoError;
case NativeType of
fldZSTRING:
begin
BrahmaType := ftStringField;
if NativeWidth = 1 then begin { Handle Char types }
BrahmaWidth := 2;
end
else begin
BrahmaWidth := NativeWidth;
(* Dec(NativeWidth);*)
end;
end;
fldDATE:
begin
BrahmaType := ftDateField;
BrahmaWidth := SizeOf(TcrDate);
NativeWidth := SizeOf(TcrDate);
end;
fldBLOB, fldstBINARY, fldstGRAPHIC, fldstTYPEDBINARY:
begin
BrahmaType := ftBlobField;
{ Get bookmark size }
FFError := ServerEngine.CursorGetBookmarkSize(FileHandle^.CursorID, BookmarkSize);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
BrahmaWidth := SizeOf(TcrInt16u) + BookmarkSize;
NativeWidth := SizeOf(TcrInt16u) + BookmarkSize;
end;
fldstMEMO, fldstFMTMEMO:
{ Memo field, or variable length char string. save only the FieldNo
in this field. }
begin
BrahmaType := ftPersistentMemoField;
{ Get bookmark size }
FFError := ServerEngine.CursorGetBookmarkSize(FileHandle^.CursorID, BookmarkSize);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
BrahmaWidth := SizeOf(TcrInt16u) + BookmarkSize + 100;
NativeWidth := SizeOf(TcrInt16u) + BookmarkSize + 100;
end;
fldBOOL:
begin
BrahmaType := ftBooleanField;
BrahmaWidth := SizeOf(TcrBoolean);
end;
fldTIME:
begin
BrahmaType := ftTimeField;
BrahmaWidth := SizeOf(TcrTime);
NativeWidth := SizeOf(TDbiTime);
end;
fldTIMESTAMP:
begin
BrahmaType := ftStringField;
BrahmaWidth := SIZEOF_DATETIME_FIELD_STRING;
NativeWidth := SizeOf(TDbiTimeStamp);
end;
fldINT16, fldUINT16:
begin
BrahmaType := ftInt16sField;
BrahmaWidth := SizeOf(TcrInt16s);
end;
fldINT32, fldUINT32:
begin
BrahmaType := ftInt32sField;
BrahmaWidth := SizeOf(TcrInt32s);
end;
fldFLOAT:
begin
BrahmaType := ftNumberField;
BrahmaWidth := SizeOf(TcrNumber);
end;
fldstMONEY:
begin
BrahmaType := ftCurrencyField;
BrahmaWidth := SizeOf(TcrNumber);
end;
else
begin
BrahmaType := ftUnknownField;
BrahmaWidth := 1;
NativeWidth := 1;
end;
end;
end;
function DoubleToNumber(const D: Double) : TcrNumber;
begin
Result := D * NUMBER_SCALING_FACTOR;
end;
function NumberToDouble(const N : TcrNumber) : Double;
begin
Result := (N / NUMBER_SCALING_FACTOR);
end;
procedure ConvertTimestampToDateTimeString(
aDate : TDbiDate;
aTime : TDbiTime;
aBrahmaValue : PAnsiChar);
var
Year : TcrInt16u;
Fraction : TcrInt16s;
Hour : TcrInt16u;
Minute : TcrInt16u;
Second : TcrInt16u;
Millisec : TcrInt16u;
Month : TcrInt16u;
Day : TcrInt16u;
I : TcrInt16u;
ZeroOrd : Integer;
begin
Year := 0;
Fraction := 0;
FFBDEDateDecode(aDate, Day, Month, Year);
FFBDETimeDecode(aTime, Hour, Minute, MilliSec);
Second := Millisec div 1000;
ZeroOrd := Ord('0');
{ Translate year to string }
for I := 3 downto 0 do begin
aBrahmaValue[I] := Chr((Year mod 10) + ZeroOrd);
Year := Year div 10;
end;
aBrahmaValue[4] := '/';
aBrahmaValue[5] := Chr((Month div 10) + ZeroOrd);
aBrahmaValue[6] := Chr((Month mod 10) + ZeroOrd);
aBrahmaValue[7] := '/';
aBrahmaValue[8] := Chr((Day div 10) + ZeroOrd);
aBrahmaValue[9] := Chr((Day mod 10) + ZeroOrd);
aBrahmaValue[10] := ' ';
aBrahmaValue[11] := Chr((Hour div 10) + ZeroOrd);
aBrahmaValue[12] := Chr((Hour mod 10) + ZeroOrd);
aBrahmaValue[13] := ':';
aBrahmaValue[14] := Chr((Minute div 10) + ZeroOrd);
aBrahmaValue[15] := Chr((Minute mod 10) + ZeroOrd);
aBrahmaValue[16] := ':';
aBrahmaValue[17] := Chr((Second div 10) + ZeroOrd);
aBrahmaValue[18] := Chr((Second mod 10) + ZeroOrd);
aBrahmaValue[19] := '.';
aBrahmaValue[20] := Chr((Fraction div 10) + ZeroOrd);
aBrahmaValue[21] := Chr((Fraction mod 10) + ZeroOrd);
aBrahmaValue[22] := #0;
end;
{ --------------------- Database Abilities ------------------------ }
{ This is the version number for the driver DLL, not the physical database.
Crystal Reports uses this number to decide which list of function names
to expect to be exported from the DLL.
Crystal Reports OEM Tech Support advised me that this should be
identical to the version number coded into the PDBXBSE driver. As such,
the exported function names should be identical to PDBXBSE as well. }
function PhysDbVersionNumber(
var MajorVersionNumber : Word;
var MinorVersionNumber : Word;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('PhysDbVersionNumber');
MajorVersionNumber := 1;
MinorVersionNumber := 17;
Result := errPhysDbNoError;
AddToLogFmt(' MajMin: [%d.%d]', [MajorVersionNumber, MinorVersionNumber]);
AddResultToLog(Result);
end;
function CanRecognizeDataFile(
var CanRecognize : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('CanRecognizeDataFile');
CanRecognize := true;
Result := errPhysDbNoError;
AddToLogFmt(' Can?: [%s]', [BoolToStr(CanRecognize)]);
AddResultToLog(Result);
end;
function CanFetchDataFileInfo(
var CanFetchFileInfo : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('CanFetchDataFileInfo');
CanFetchFileInfo := true;
Result := errPhysDbNoError;
AddToLogFmt(' Can?: [%s]', [BoolToStr(CanFetchFileInfo)]);
AddResultToLog(Result);
end;
function CanFetchDataFileIndexInfo(
var CanFetchIndexInfo : TPhysDbIndexInfoCases;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('CanFetchDataFileIndexInfo');
CanFetchIndexInfo := iiAllIndexesKnown;
Result := errPhysDbNoError;
AddToLogFmt(' Can?: [%d]', [Ord(CanFetchIndexInfo)]);
AddResultToLog(Result);
end;
function CanBuildIndex(
var CanBuildIndex : TPhysDbBuildIndexCases;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('CanBuildIndex');
CanBuildIndex := biCannotBuildIndex;
Result := errPhysDbNoError;
AddToLogFmt(' Can?: [%d]', [ord(CanBuildIndex)]);
AddResultToLog(Result);
end;
function CanFetchNRecurringRecords(
var CanFetchNrecords : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('CanFetchNRecurringRecords');
CanFetchNRecords := true;
Result := errPhysDbNoError;
AddToLogFmt(' Can?: [%s]', [BoolToStr(CanFetchNRecords)]);
AddResultToLog(Result);
end;
function SQLCompatible(
var IsSQLTypeDLL : TcrBoolean;
var CanBuildAndExecSQLQuery : TcrBoolean;
var CanExecSQLQuery : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('SQLCompatible');
IsSQLTypeDLL := false;
CanBuildAndExecSQLQuery := false;
CanExecSQLQuery := false; {true - allow passing down rangeinfolist }
Result := errPhysDbNoError;
AddToLogFmt(' IsSQLTypeDLL?: [%s]', [BoolToStr(IsSQLTypeDLL)]);
AddToLogFmt(' CanBuildAndExecSQLQuery?: [%s]', [BoolToStr(CanBuildAndExecSQLQuery)]);
AddToLogFmt(' CanExecSQLQuery?: [%s]', [BoolToStr(CanExecSQLQuery)]);
AddResultToLog(Result);
end;
function CanReadSortedOrder(
var CanReadSorted : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('CanReadSortedOrder');
CanReadSorted := True;
Result := errPhysDbNoError;
AddToLogFmt(' Can?: [%s]', [BoolToStr(CanReadSorted)]);
AddResultToLog(Result);
end;
function CanReadRangeOfValues(
var CanReadRange : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('CanReadRangeOfValues');
CanReadRange := False;
Result := errPhysDbNoError;
AddToLogFmt(' Can?: [%s]', [BoolToStr(CanReadRange)]);
AddResultToLog(Result);
end;
function CanUseRecordLocking(
var RecordLockingPossible : TcrBoolean;
var RecordLockingPreferred : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('CanUseRecordLocking');
RecordLockingPossible := false;
RecordLockingPreferred := false;
Result := errPhysDbNoError;
AddToLogFmt(' Record Locking Possible?: [%s]', [BoolToStr(RecordLockingPossible)]);
AddToLogFmt(' Record Locking Preferred?: [%s]', [BoolToStr(RecordLockingPreferred)]);
AddResultToLog(Result);
end;
function CanUseFileLocking(
var FileLockingPossible : TcrBoolean;
var FileLockingPreferred : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('CanUseFileLocking');
FileLockingPossible := false;
FilelockingPreferred := false;
Result := errPhysDbNoError;
AddToLogFmt(' File Locking Possible?: [%s]', [BoolToStr(FileLockingPossible)]);
AddToLogFmt(' File Locking Preferred?: [%s]', [BoolToStr(FileLockingPreferred)]);
AddResultToLog(Result);
end;
{ ----------- Database Initialization and Termination ------------- }
function InitPhysicalDatabase(ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('InitPhysicalDatabase');
{ No special processing to initilize the database.
But we can't return PhysDbNotImplemented or Crystal will choke. }
IsTaskSuccess := True;
Result := errPhysDbNoError;
AddResultToLog(Result);
end;
function TermPhysicalDatabase(ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('TermPhysicalDatabase');
{ No special processing to deinitialize the database.
But we can't return PhysDbNotImplemented or Crystal will choke. }
Result := errPhysDbNoError;
AddResultToLog(Result);
end;
function OpenSession(ErrMsg : PAnsiChar) : TPhysDbError;
var
TaskFound : Boolean;
TaskIndex : integer;
begin
AddToLog('OpenSession');
Result := errPhysDbNoError;
TaskIndex := -1;
{handling in the except block? }
try
Result := TaskList.NewTask(TaskFound, TaskIndex, ErrMsg);
if (Result = errPhysDbNoError) then
if not TaskFound then
FFSession.Open;
except
on EOutOfMemory do begin
Result := errPhysDbNotEnoughMemory;
StrPCopy(ErrMsg, '');
end;
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
if not Assigned(ErrMsg) then
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function TermSession(ErrMsg : PAnsiChar) : TPhysDbError;
var
TaskFound : Boolean;
AlreadyInitialized : Boolean;
begin
AddToLog('TermSession');
Result := TaskList.DeleteTask(TaskFound, AlreadyInitialized, ErrMsg);
if (Result = errPhysDbNoError) then
if TaskFound then
if not AlreadyInitialized then
FFSession.Close;
AddResultToLog(Result);
end;
{ ------------------------- Database Name -------------------------- }
function FetchDatabaseName(var Name : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('FetchDatabaseName');
try
Name := FFStrNew('FlashFiler 2');
Result := errPhysDbNoError;
except
Result := errPhysDbNotEnoughMemory;
end;
AddToLogFmt(' Name: [%s]', [Name]);
AddResultToLog(Result);
end;
function FreeDatabaseName(var Name : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('FreeDatabaseName');
AddToLogFmt(' Name: [%s]', [Name]);
Result := errPhysDbNoError;
try
FFStrDispose(Name);
Name := nil;
except
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
{ ---------------------- Log On and Log Off ----------------------- }
function LogOnServer(ServerInfo : PPhysDbServerInfo;
var ServerHandle : PPhysDbServerHandle;
Password : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('LogOnServer');
{ Can't return PhysDbNotImplemented or Crystal will choke. }
Result := errPhysDbNoError;
AddToLogFmt(' Server Handle: [%d]', [ServerHandle]);
AddResultToLog(Result);
end;
function LogOffServer(
var ServerHandle : PPhysDbServerHandle;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('LogOffServer');
{ Can't return PhysDbNotImplemented or Crystal will choke. }
Result := errPhysDbNoError;
AddToLogFmt(' Server Handle: [%d]', [ServerHandle]);
AddResultToLog(Result);
end;
{ ------------------- Parse and Rebuild SQL Info -------------------- }
function ParseLogOnInfo(
connectBuf : PAnsiChar;
bufSize : Word;
serverInfo : PPhysDbServerInfo;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('ParseLogOnInfo');
Result := errPhysDbNotImplemented;
AddResultToLog(Result);
end;
function RebuildConnectBuf(
serverInfo : PPhysDbServerInfo;
connectBuf : PAnsiChar;
bufSize : Word;
ErrMsg : PAnsiChar) : TphysDbError;
begin
AddToLog('RebuildConnectBuf');
Result := errPhysDbNotImplemented;
AddResultToLog(Result);
end;
{ -------------------- Open and Close Files ----------------------- }
function InitDataFileHandle(FileName : PAnsiChar;
var FileHandle : PPhysDbFileHandle;
DatabaseHandle : TffDatabaseID;
hCursor : TffcursorID;
ErrMsg : PAnsiChar) : TPhysDbError;
var
vNotXlateDOSString : Boolean;
vNotXlateDOSMemo : Boolean;
begin
Result := errPhysDbNoError;
try
{By default these two flags are FALSE : always convert OEM to ANSI,
check it now}
vNotXlateDOSString :=
(LongInt(FileHandle) and TRANSLATE_DOS_STRINGS) = 0;
vNotXlateDOSMemo :=
(LongInt(FileHandle) and TRANSLATE_DOS_MEMOS) = 0;
FFGetZeroMem(FileHandle, sizeof(TPhysDbFileHandle));
FileHandle^.DatabaseID := DatabaseHandle;
FileHandle^.CursorID := hCursor;
FileHandle^.NotXlateDOSString := vNotXlateDOSString;
FileHandle^.NotXlateDOSMemo := vNotXlateDOSMemo;
FileHandle^.PathAndFileName := FFStrAllocCopy(FileName);
except
on EOutOfMemory do begin
Result := errPhysDbNotEnoughMemory;
ServerEngine.CursorClose(hCursor);
ServerEngine.DatabaseClose(DatabaseHandle);
if Assigned(FileHandle) then
FFFreeMem(FileHandle, sizeof(TPhysDbFileHandle));
StrPCopy(ErrMsg, '');
end;
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
ServerEngine.CursorClose(hCursor);
ServerEngine.DatabaseClose(DatabaseHandle);
if Assigned(FileHandle) then
FFFreeMem(FileHandle, sizeof(TPhysDbFileHandle));
if not Assigned(ErrMsg) then {not assigned? }
StrPCopy(ErrMsg, E.Message);
end;
end;
end;
function OpenDatabase(DBName : PAnsiChar;
var DatabaseHandle : TffDatabaseID;
ErrMsg : PAnsiChar) : TPhysDbError;
var
FFError : TffResult;
DBNameUNC : TffShStr;
begin
if not Assigned(DBName) then begin
Result := errPhysDbProgrammingError;
Exit;
end;
DBNameUNC := FFExpandUNCFilename(FFStrPas(DBName));
if (length(DBNameUNC) > 3) and
(DBNameUNC[length(DBNameUNC)] = '\') then
dec(DBNameUNC[0]);
FFSession.Open;
FFError := ServerEngine.DatabaseOpenNoAlias(FFSession.Client.ClientID,
DBNameUNC,
omReadOnly,
smShared,
DefaultTimeOut, {2000}{-1} {!!.05}
DatabaseHandle);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
Result := errPhysDbNoError;
end;
function OpenDataFile(DatabaseHandle : TffDatabaseID;
FileName : PAnsiChar;
var FileHandle : PPhysDbFileHandle;
IndexFileName : PAnsiChar;
TagName : PAnsiChar;
IndexId : Word;
ErrMsg : PAnsiChar) : TPhysDbError;
var
FFError : TffResult;
hCursor : TffCursorID;
TableName : TffShStr;
IndexName : TffShStr;
Stream : TMemoryStream;
begin
TableName := FFExtractTableName(FFStrPas(FileName));
if (IndexFileName = nil) then
IndexName := ''
else
IndexName := FFStrPas(IndexFilename);
AddToLogFmt(' TableName: [%s]', [TableName]);
AddToLogFmt(' IndexName: [%s]', [IndexName]);
Stream := TMemoryStream.Create;
try
FFError := ServerEngine.TableOpen(DatabaseHandle,
TableName,
False,
IndexName,
IndexId,
omReadOnly,
smShared,
DefaultTimeOut, {2000}{-1} {!!.05}
hCursor,
Stream);
finally
Stream.Free;
end;
if FFError <> DBIERR_NONE then begin
ServerEngine.DatabaseClose(DatabaseHandle);
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
Result := InitDataFileHandle(FileName, FileHandle, DatabaseHandle,
hCursor, ErrMsg);
end;
function OpenDataFileIfRecognizedVer113(
FileName : PAnsiChar;
OpenDefaultIndex : TcrBoolean;
var Recognized : TcrBoolean;
var FileHandle : PPhysDbFileHandle;
CalledFromDirDLL : TcrBoolean;
var AliasName : PAnsiChar;
SilentMode : TcrBoolean;
DirInfo : PPhysDbFileDirectoryInfo;
DictInfo : PPhysDbFileDictionaryInfo;
SessionInfo : PPhysDbSessionInfo;
LogOnInfo : PPhysDbLogOnInfo;
ErrMsg : PAnsiChar) : TPhysDbError;
var
FileNameStr : TffShStr;
DatabaseHandle : TffDatabaseID;
DBNameOem : array[0..255] of AnsiChar;
begin
AddToLog('OpenDataFileIfRecognizedVer113');
AddToLogFmt(' File Name: [%s]', [FileName]);
AddToLogFmt(' OpenDefIndex: [%s]', [BoolToStr(OpenDefaultIndex)]);
Result := errPhysDbNoError;
Recognized := false;
if not IsTaskSuccess then begin
AddToLog(' IsTaskSuccess is false');
AddToLogFmt(' Recognized? [%s]', [BoolToStr(Recognized)]);
AddResultToLog(Result);
Exit;
end;
FileHandle := nil;
DBNameOem[0] := #0;
if (AliasName <> nil) then
AliasName[0] := #0;
try
{ Return error if file does not exist. }
FileNameStr := FFStrPas(FileName);
if not FileExists(FileNameStr) then begin
Result := errPhysDbFileDoesNotExist;
AddToLogFmt(' Recognized? [%s]', [BoolToStr(Recognized)]);
AddResultToLog(Result);
Exit;
end;
{ Check to see if the file name has an FF2 extension. If not, then
we assume that it's not a FF table (this avoids the time-
consuming protocol and FF client initialization stuff).}
if (FFCmpShStrUC(FFExtractExtension(FileNameStr),
ffc_ExtForData, ffcl_Extension) <> 0) then begin
{ No error, but file is not recognized }
Result := errPhysDbNoError;
AddToLogFmt(' Recognized? [%s]', [BoolToStr(Recognized)]);
AddResultToLog(Result);
Exit;
end;
{$IFDEF IDAPI_INTERNAL_LIMITS}
if NOpenDatabase >= MAX_DBS_PER_SESSION then begin
Recognized := false;
AddToLogFmt(' Recognized? [%s]', [BoolToStr(Recognized)]);
AddResultToLog(Result);
Exit;
end;
{$ENDIF}
FFStrPCopy(DBNameOem, FFExtractPath(FFStrPas(FileName)));
Result := OpenDatabase(DBNameOem, DatabaseHandle, ErrMsg);
if Result <> errPhysDbNoError then begin
AddToLogFmt(' Recognized? [%s]', [BoolToStr(Recognized)]);
AddResultToLog(Result);
Exit;
end;
{$IFDEF IDAPI_INTERNAL_LIMITS}
Inc(NOpenDatabase);
{$ENDIF}
{convert filename to oem? }
Recognized := OpenDataFile(DatabaseHandle, FileName, FileHandle, nil, nil, 0, ErrMsg) = errPhysDbNoError;
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
Result := errPhysDbNoError;
except
on EOutOfMemory do begin
Result := errPhysDbNotEnoughMemory;
CloseDataFile(FileHandle, ErrMsg);
StrPCopy(ErrMsg, '');
end;
on E: Exception do begin
CloseDataFile(FileHandle, ErrMsg);
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
AddToLogFmt(' Recognized? [%s]', [BoolToStr(Recognized)]);
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function OpenDataAndIndexFileIfRecogV113(
FileName : PAnsiChar;
IndexName : PAnsiChar;
var Recognized : TcrBoolean;
var FileHandle : PPhysDbFileHandle;
var AliasName : PAnsiChar;
SilentMode : TcrBoolean;
DirInfo : PPhysDbFileDirectoryInfo;
DictInfo : PPhysDbFileDictionaryInfo;
SessionInfo: PPhysDbSessionInfo;
LogOnInfo : PPhysDbLogOnInfo;
ErrMsg : PAnsiChar) : TPhysDbError;
var
FFError : TffResult;
begin
AddToLog('OpenDataAndIndexFileIfRecogV113');
AddToLogFmt(' FName: [%s]', [FileName]);
AddToLogFmt(' InxName: [%s]', [IndexName]);
Result := errPhysDbNoError;
try
Recognized := false;
AliasName := nil;
{ Open the data file first }
Result := OpenDataFileIfRecognizedVer113(FileName, False, Recognized,
FileHandle, False, FileName, SilentMode, DirInfo,
DictInfo, SessionInfo, LogOnInfo, ErrMsg);
if Result <> errPhysDbNoError then Exit;
AddToLogFmt(' FName: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' Cursor ID: [%d]', [FileHandle^.CursorID]);
FFError := ServerEngine.CursorSwitchToIndex(FileHandle^.CursorID,
IndexName,
0,
True);
if (FFError = DBIERR_NOCURRREC) then
FFError := ServerEngine.CursorSwitchToIndex(FileHandle^.CursorID,
IndexName,
0,
False);
if FFError <> DBIERR_NONE then
Result := IDAPIError(FFError, ErrMsg);
except
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function OpenDataFileAndIndexChoiceVer113(
FileName : PAnsiChar;
InfoPtr : PPhysDbFileInfo;
IndexesPtr : PPhysDbIndexesInfo;
var FileHandle : PPhysDbFileHandle;
SilentMode : TcrBoolean;
DirInfo : PPhysDbFileDirectoryInfo;
DictInfo : PPhysDbFileDictionaryInfo;
SessionInfo: PPhysDbSessionInfo;
LogOnInfo : PPhysDbLogOnInfo;
ErrMsg : PAnsiChar) : TPhysDbError;
var
DatabaseHandle: TffDatabaseID;
FileNameOem: array[0..MAX_PATH] of AnsiChar;
DBNameOem: array[0..MAX_PATH] of AnsiChar;
begin
AddToLog('OpenDataFileandIndexChoiceVer113');
Result := errPhysDbNoError;
try
{$IFDEF IDAPI_INTERNAL_LIMITS}
if NOpenDatabase >= MAX_DBS_PER_SESSION then begin
Result := errPhysDbErrorHandledByDBDLL;
Exit;
end;
{$ENDIF}
StrPCopy(DBNameOem, ExtractFilePath(StrPas(FileName)));
Result := OpenDatabase(DBNameOem, DatabaseHandle, ErrMsg);
AddToLogFmt(' DatabaseID: [%d]', [DatabaseHandle]);
if Result = errPhysDbNoError then begin
{$IFDEF IDAPI_INTERNAL_LIMITS}
Inc(NOpenDatabase);
{$ENDIF}
StrCopy(FileNameOem, FileName);
with IndexesPtr^.IndexInfo^[IndexesPtr^.IndexInUse] do
Result := OpenDataFile(DatabaseHandle, FilenameOem, FileHandle,
IndexFilename, TagName, IndexesPtr^.IndexInUse, ErrMsg);
AddToLogFmt(' FName: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
end;
except
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function CloseDataFile(var FileHandle : PPhysDbFileHandle;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('CloseDataFile');
Result := errPhysDbNoError;
try
if Assigned(FileHandle) then begin
AddToLogFmt(' FName: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
with FileHandle^ do begin
ServerEngine.CursorClose(CursorID);
if DatabaseID > 0 then { not sure why DbiCloseCursor is clearing this }
ServerEngine.DatabaseClose(DatabaseID);
{$IFDEF IDAPI_INTERNAL_LIMITS}
if NOpenDatabase > 0 then
Dec(NOpenDatabase);
{$ENDIF}
FFStrDispose(PathAndFileName);
FFStrDispose(IndexFileName);
FFStrDispose(TagName);
end;
FFFreeMem(FileHandle, sizeof(TPhysDbFileHandle));
end;
FileHandle := nil;
except
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
{ ---------------------- Fetch Data File Info --------------------- }
function FetchDataFileInfo(
FileHandle : PPhysDbFileHandle;
InfoDefaultsExist : TcrBoolean;
var InfoPtr : PPhysDbFileInfo;
ErrMsg : PAnsiChar) : TPhysDbError;
var
I : Integer;
FieldOffset : LongInt;
Buffer : TffShStr;
FFFieldType : TffFieldType;
BDEType : Word;
BDESubType : Word;
LogSize : Word;
begin
AddToLog('FetchDataFileInfo');
AddToLogFmt(' File Name: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
Result := errPhysDbNoError;
try
try
{ Allocate the file info structure }
FFGetZeroMem(InfoPtr, sizeof(TPhysDbFileInfo));
with InfoPtr^ do begin
NFields := 0;
FieldInfo := nil;
{ Always set the file to the recurring type, even if file contains only
0 or 1 records, since the file may grow in size. }
FileType := ftRecurringFile;
{ Set tablename to nil so the file name will be used by default }
TableName := nil;
{ Get number of fields in table }
NFields := TFFProxyCursor(FileHandle^.CursorID).Dictionary.FieldCount;
NBytesInPhysRecord := TFFProxyCursor(FileHandle^.CursorID).PhysicalRecordSize;
if NFields > 0 then begin
{ Retrieve field info }
{ Allocate the field info array structure }
FFGetZeroMem(FieldInfo, SizeOf(TPhysDbFieldInfo) * NFields);
{ Build the field info array }
FieldOffset := 0;
for I := 0 to pred(NFields) do begin
with FieldInfo^[I], TFFProxyCursor(FileHandle^.CursorID) do begin
{ Allocate space for the field name }
Name := FFStrNew(Dictionary.FieldName[I]);
{ Determine Brahma data type and width }
NBytesInNativeField := Dictionary.FieldLength[I];
FFFieldType := Dictionary.FieldType[I];
MapFFTypeToBDE(FFFieldType, NBytesInNativeField, BDEType, BDESubType, LogSize);
NativeFieldType := BDEType;
if NativeFieldType = fldBLOB then
NativeFieldType := BDESubType;
if (NativeFieldType = fldFLOAT) and (BDESubType = fldstMONEY) then
NativeFieldType := BDESubType;
Result := Convert2BrahmaType(FileHandle,
NativeFieldType,
NBytesInNativeField,
FieldType,
NBytesInField,
ErrMsg);
if Result <> errPhysDbNoError then Exit;
if FieldType = ftUnknownField then begin
AddToLog('Convert2BrahmaType: Unknown field');
AddToLogFmt(' Field: [%s]', [Dictionary.FieldName[I]]);
AddToLogFmt(' Type : [%d]', [NativeFieldType]);
end;
case FFFieldType of
fftShortString,
fftShortAnsiStr : NativeFieldOffset := Succ(FieldOffset);
else
NativeFieldOffset := FieldOffset;
end;
NDecPlacesInNativeField := Dictionary.FieldUnits[I];
Picture := nil;
Alignment := alLeftAlignedChars;
Sortable := true;
end;
{ Calculate the offset for the next field }
Inc(FieldOffset, FieldInfo^[I].NBytesInNativeField);
end;
end;
{ these are not set by this routine }
NBytesInReadRecord := 0;
NFieldsInReadRecord := 0;
NBytesInIndexRecord := 0;
NFieldsInIndexRecord := 0;
end;
except { InfoPtr error handler }
on EOutOfMemory do begin
Result := errPhysDbNotEnoughMemory;
FreeDataFileInfo(InfoPtr, ErrMsg);
StrPCopy(ErrMsg, '');
end;
on E: Exception do begin
FreeDataFileInfo(InfoPtr, ErrMsg);
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
finally
Buffer := PhysDbErrors[Result]; { this seems necessary for 32-bit, debug mode only }
end;
if (InfoPtr <> nil) then begin
with InfoPtr^ do begin
AddToLogFmt(' InfoPtr.NFields: [%d]', [NFields]);
AddToLogFmt(' InfoPtr.NBytesInPhysRecord: [%d]', [NBytesInPhysRecord]);
for i := 0 to pred(NFields) do begin
AddToLogFmt(' FieldName[%d]: [%s]', [i, FieldInfo^[i].Name]);
end;
AddBlockToLog(' InfoPtr.FieldInfo', FieldInfo, sizeOf(TPhysDbFieldInfo) * NFields);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function FreeDataFileInfo(
var InfoPtr : PPhysDbFileInfo;
ErrMsg : PAnsiChar) : TPhysDbError;
var
i : Integer;
begin
AddToLog('FreeDataFileInfo');
Result := errPhysDbNoError;
try
if Assigned(InfoPtr) then begin
with InfoPtr^ do begin
FFStrDispose(TableName);
if Assigned(FieldInfo) then begin
for I := 0 to pred(NFields) do begin
FFStrDispose(FieldInfo^[I].Name);
FFStrDispose(FieldInfo^[I].Picture);
end;
FFFreeMem(FieldInfo, Sizeof(TPhysDbFieldInfo) * NFields);
end;
end;
FFFreeMem(InfoPtr, sizeof(TPhysDbFileInfo));
end;
InfoPtr := nil;
except
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function FetchDataFileIndexInfo(
FileHandle: PPhysDbFileHandle;
InfoPtr: PPhysDbFileInfo;
var IndexesPtr: PPhysDbIndexesInfo;
ErrMsg: PAnsiChar) : TPhysDbError;
var
I : Integer;
function FetchIndexInfo: TPhysDbError;
var
Index : integer;
IndexDesc : IDXDesc;
FFIndexDesc : TffIndexDescriptor;
FieldN : Integer;
begin
with IndexesPtr^ do begin
for Index := 1 to NIndexes do begin {!!.02}
FFIndexDesc := TFFProxyCursor(FileHandle^.CursorID).Dictionary.IndexDescriptor[Index]^;
GetBDEIndexDescriptor(FFIndexDesc, IndexDesc);
with IndexInfo^[Pred(Index)] do begin {!!.02}
ValuesUnique := IndexDesc.bUnique;
Ascending := not IndexDesc.bDescending;
{ Allocate space for the filename }
if StrLen(IndexDesc.szName) <> 0 then begin
IndexFileName := FFStrAlloc(StrLen(IndexDesc.szName) + 1);
OemToAnsi(IndexDesc.szName, IndexFilename);
end;
{ Allocate space for the tagname }
if StrLen(IndexDesc.szTagName) <> 0 then begin
TagName := FFStrAlloc(StrLen(IndexDesc.szTagName) + 1);
OemToAnsi(IndexDesc.szTagName, TagName);
end;
IndexType := IndexDesc.iKeyExpType;
CaseSensitive := not IndexDesc.bCaseInsensitive;
if IndexDesc.bExpIdx then begin
{ omitted a bunch}
end
else begin
DefaultIndexFileName := not Assigned(IndexFileName);
DefaultTagName := not Assigned(TagName);
IndexExpr := nil;
EstimatedNBytesInexpr := 0;
NFields := IndexDesc.iFldsInKey;
{ Allocate the output list structure }
FFGetZeroMem(FieldNumInFile, SizeOf(TcrInt16u) * NFields);
for FieldN := 0 to pred(NFields) do
FieldNuminFile^[FieldN] := IndexDesc.aiKeyFld[FieldN] - 1; {!!.02}
end;
end;
end;
end;
Result := errPhysDbNoError;
end;
begin
AddToLog('FetchDataFileIndexInfo');
Result := errPhysDbNoError;
{ Allocate the index info structure }
try
FFGetZeroMem(IndexesPtr, SizeOf(TPhysDbIndexesInfo));
with IndexesPtr^ do begin
{ Get number of indexes in the table minus the SEQ Idx} {!!.02}
NIndexes := TFFProxyCursor(FileHandle^.CursorID).Dictionary.IndexCount - 1; {!!.02}
AddToLogFmt(' File Name: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
if NIndexes > 0 then begin
{ Allocate the index info structures }
FFGetZeroMem(IndexInfo, SizeOf(TPhysDbIndexInfo) * NIndexes);
Result := FetchIndexInfo;
if Result <> errPhysDbNoError then SysUtils.Abort;
end;
end;
except { InfoPtr error handler }
on EOutOfMemory do begin
Result := errPhysDbNotEnoughMemory;
FreeDataFileIndexInfo(IndexesPtr, ErrMsg);
StrPCopy(ErrMsg, '');
end;
on E: Exception do begin
FreeDataFileIndexInfo(IndexesPtr, ErrMsg);
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (IndexesPtr <> nil) then begin
with IndexesPtr^ do begin
AddToLogFmt(' IndexesPtr.NIndexes: [%d]', [NIndexes]);
for i := 0 to pred(NIndexes) do begin
AddToLogFmt(' IndexName[%d]: [%s]', [i, IndexInfo^[i].IndexFileName]);
end;
AddBlockToLog(' IndexesPtr.IndexInfo', IndexInfo, sizeOf(TPhysDbIndexInfo) * NIndexes);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function FreeDataFileIndexInfo(
var IndexesPtr: PPhysDbIndexesInfo;
ErrMsg: PAnsiChar) : TPhysDbError;
var
I: Integer;
begin
AddToLog('FreeDataFileIndexInfo');
Result := errPhysDbNoError;
try
if Assigned(IndexesPtr) then begin
if Assigned(IndexesPtr^.IndexInfo) then begin
for I := 0 to pred(IndexesPtr^.NIndexes) do
with IndexesPtr^.IndexInfo^[I] do begin
FFFreeMem(FieldNumInFile, SizeOf(Word) * NFields);
FFStrDispose(IndexExpr);
FFStrDispose(IndexFileName);
FFStrDispose(TagName);
end;
FFFreeMem(IndexesPtr^.IndexInfo, SizeOf(TPhysDbIndexInfo) * IndexesPtr^.NIndexes);
end;
FFFreeMem(IndexesPtr, SizeOf(TPhysDbIndexesInfo));
IndexesPtr := nil;
end;
except
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function BuildAndExecSQLQuery(
FileHandleList: PPhysDbFileHandleArray;
FileInfoList: PPhysDbFileInfoArray;
LinkNonSQLFlags: PcrBooleanArray;
IndexesInfoList: PPhysDbIndexesInfoArray;
RangeInfoList: PPhysDbRangeInfoArray;
NFiles: Word;
LinkInfoList: PPhysDbFileLinkInfoArray;
NFileLinks: Word;
SqlDrivingFile: TcrBoolean;
ErrMsg: PAnsiChar) : TPhysDbError;
begin
AddToLog('BuildAndExecSQLQuery');
{ This is what PDBBDE returns }
Result := errPhysDbNotImplemented;
AddResultToLog(Result);
end;
function InitDataFileForReadingVer17(
FileHandle : PPhysDbFileHandle;
InfoPtr : PPhysDbFileInfo;
IndexesPtr : PPhysDbIndexesInfo;
RangeInfoList : PPhysDbRangeInfoArray;
NRanges : TcrInt16u;
var CanDoRangeLimit : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
var
FFError : TffResult;
function CanDoRangeLimitOnField(
FileHandle : PPhysDbFileHandle;
InfoPtr : PPhysDbFileInfo;
IndexesPtr : PPhysDbIndexesInfo;
RangeInfoList : PPhysDbRangeInfoArray;
NRanges : Word;
var CanDoRangeLimit : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
var
IndexInfo : TPhysDbIndexInfo;
ContinueBuildStopKey : Boolean;
ContinueBuildStartKey : Boolean;
StopKeyOffset : integer;
StartKeyLen : integer;
NFieldsInStartKey : integer;
MinInclusive : Boolean;
RangeN : integer;
FieldN : integer;
TempPtr : Pointer;
TempBool : TcrBoolean;
IndexFieldN : integer;
RangeFieldN : integer;
SearchCond : TffSearchKeyAction;
function InitLimitRangeInfo(
FileHandle : PPhysDbFileHandle;
RangeInfoList : PPhysDbRangeInfoArray;
RangeIndex : integer;
RangeFieldN : integer;
FieldInfo : PPhysDbFieldInfo;
var ContinueBuildStartKey : Boolean;
var StartKeyLen : integer;
var ContinueBuildStopKey : Boolean;
var StopKeyOffset : integer;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
Result := errPhysDbNoError;
with FileHandle^.ReadInfo^.RangeFieldInfo^[RangeIndex] do begin
FieldNo := RangeFieldN;
OffsetInRecord := FieldInfo^.OffsetInIndexRecord;
FieldLength := FieldInfo^.NBytesInNativeField;
FieldType := FieldInfo^.FieldType;
NativeFieldOffset := FieldInfo^.NativeFieldOffset;
NativeFieldType := FieldInfo^.NativeFieldType;
NBytesInNativeField := FieldINfo^.NBytesInNativeField;
end;
with RangeInfoList^[RangeIndex].FieldRanges^[0] do begin
if ContinueBuildStartKey and Assigned(MinFieldValue) then
Inc(StartKeyLen, FieldInfo^.NBytesInNativeField)
else
ContinueBuildStartKey := False;
if ContinueBuildStopKey and Assigned(MaxFieldValue) then begin
with FileHandle^.ReadInfo^.RangeFieldInfo^[RangeIndex] do begin
{ Makes no sense to me; we already did this }
FieldNo := RangeFieldN;
OffsetInRecord := FieldInfo^.OffsetInIndexRecord;
FieldLength := FieldInfo^.NBytesInNativeField;
FieldType := FieldInfo^.FieldType;
NativeFieldOffset := FieldInfo^.NativeFieldOffset;
NativeFieldType := FieldInfo^.NativeFieldType;
NBytesInNativeField := FieldInfo^.NBytesInNativeField;
NDecPlacesInNativeField := FieldInfo^.NDecPlacesInNativeField;
OffsetInStopKeyBuf := StopKeyOffset;
StopInclusive := RangeInfoList^[RangeIndex].FieldRanges^[0].MaxInclusive;
end;
Inc(StopKeyOffset, FieldInfo^.NBytesInNativeField);
FileHandle^.ReadInfo^.StopKeyLen := StopKeyOffset;
Inc(FileHandle^.ReadInfo^.NStopKeyRanges);
end
else
ContinueBuildStopKey := False;
end;
end;
function BuildStringRanges(
FileHandle: PPhysDbFileHandle;
RangeInfoList: PPhysDbRangeInfoArray;
RangeIndex: TcrInt16u;
RangeFieldN: TcrInt16u;
FieldInfo: PPhysDbFieldInfo;
ErrMsg: PAnsiChar) : TPhysDbError;
var
SavedOffset: TcrINt16u;
KeyBuf,
KeyBufOem,
StartKeyBuf,
StopKeyBuf: PAnsiChar;
begin
SavedOffset := StopKeyOffset;
Result := InitLimitRangeInfo(FileHandle, RangeInfoList, RangeIndex,
RangeFieldN, FieldInfo, ContinueBuildStartKey,
StartKeyLen, ContinueBuildStopKey, StopKeyOffset,
ErrMsg);
if Result <> errPhysDbNoError then Exit;
if ContinueBuildStartKey then begin
KeyBuf := RangeInfoList^[RangeIndex].FieldRanges^[0].MinFieldValue;
try
KeyBufOem := FFStrAllocCopy(KeyBuf);
except
Result := errPhysDbNotEnoughMemory;
Exit;
end;
try
AnsiToOem(keyBufOem, keyBufOem);
TFFProxyCursor(FileHandle^.CursorID).Dictionary.SetRecordField(RangeFieldN,
FileHandle^.ReadInfo^.PhysRecordBuf,
KeyBufOem);
finally
FFStrDispose(KeyBufOem);
end;
end;
if ContinueBuildStopKey then begin
KeyBuf := @FileHandle^.ReadInfo^.StopKeyBuf[SavedOffset];
StrCopy(KeyBuf, @RangeInfoList^[RangeIndex].FieldRanges^[0].MaxFieldValue);
AnsiToOem(keyBuf, keyBuf);
end;
{ If current field of min and max range in index are not equal, do not
try to build stop key. }
with RangeInfoList^[RangeIndex].FieldRanges^[0] do begin
StartKeyBuf := @MinFieldValue;
StopKeyBuf := @MaxFieldValue;
end;
if not Assigned(StartKeyBuf) or
not Assigned(StopKeyBuf) or
(StrComp(StartKeyBuf, StopKeyBuf) <> 0) then
ContinueBuildStopKey := False;
end;
function BuildDateRanges(
FileHandle: PPhysDbFileHandle;
RangeInfoList: PPhysDbRangeInfoArray;
RangeIndex: TcrInt16u;
RangeFieldN: TcrInt16u;
FieldInfo: PPhysDbFieldInfo;
ErrMsg: PAnsiChar) : TPhysDbError;
var
SavedOffset : TcrInt16u;
Year : TcrInt16s;
Month : TcrInt16u;
Day : TcrInt16u;
DateValue : TDbiDate;
FieldLen : TcrInt16u;
KeyBuf : PDBIDate;
StartKeyBuf : PDBIDate;
StopKeyBuf : PDbiDate;
begin
SavedOffset := StopKeyOffset;
Result := InitLimitRangeInfo(FileHandle, RangeInfoList, RangeIndex,
RangeFieldN, FieldInfo, ContinueBuildStartKey,
StartKeyLen, ContinueBuildStopKey, StopKeyOffset,
ErrMsg);
if Result <> errPhysDbNoError then Exit;
if ContinueBuildStartKey then begin
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
CrDateToYearMonthDay(TcrDate(MinFieldValue^), Year, Month, Day);
DateValue := FFBDEDateEncode(Day, Month, Year);
TFFProxyCursor(FileHandle^.CursorID).Dictionary.SetRecordField(RangeFieldN,
FileHandle^.ReadInfo^.PhysRecordBuf,
@DateValue);
end;
if ContinueBuildStopKey then begin
FieldLen := StopKeyOffset - SavedOffset;
KeyBuf := PDbiDate(@FileHandle^.ReadInfo^.StopKeyBuf[SavedOffset]);
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
CrDateToYearMonthDay(TcrDate(MaxFieldValue^), Year, Month, Day);
DateValue := FFBDEDateEncode(Day, Month, Year);
Move(DateValue, KeyBuf^, FieldLen);
end;
{ If current field of min and max range in index are not equal, do not
try to build stop key. }
with RangeInfoList^[RangeIndex].FieldRanges^[0] do begin
StartKeyBuf := PDbiDate(@MinFieldValue);
StopKeyBuf := PDbiDate(@MaxFieldValue);
end;
if not Assigned(StartKeyBuf) or
not Assigned(StopKeyBuf) or
(StartKeyBuf^ <> StopKeyBuf^) then
ContinueBuildStopKey := False;
end;
function BuildIntegerRanges(
FileHandle: PPhysDbFileHandle;
RangeInfoList: PPhysDbRangeInfoArray;
RangeIndex: TcrInt16u;
RangeFieldN: TcrInt16u;
FieldInfo: PPhysDbFieldInfo;
ErrMsg: PAnsiChar) : TPhysDbError;
var
SavedLen,
SavedOffset: TcrInt16u;
FieldLen: TcrInt16u;
KeyBuf: PAnsiChar;
StartKeyValue,
StopKeyValue: TcrInt32s;
ShortValue: TcrInt16s;
LongValue: TcrInt32s;
begin
SavedLen := StartKeyLen;
SavedOffset := StopKeyOffset;
Result := InitLimitRangeInfo(FileHandle, RangeInfoList, RangeIndex,
RangeFieldN, FieldInfo, ContinueBuildStartKey,
StartKeyLen, ContinueBuildStopKey, StopKeyOffset,
ErrMsg);
if Result <> errPhysDbNoError then Exit;
StartKeyValue := 0;
StopKeyValue := 0;
if ContinueBuildStartKey then begin
FieldLen := StartKeyLen - SavedLen;
if FieldLen = 2 then begin
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
ShortValue := TcrInt16s(MinFieldValue^);
StartKeyValue := ShortValue;
TFFProxyCursor(FileHandle^.CursorID).Dictionary.SetRecordField(RangeFieldN,
FileHandle^.ReadInfo^.PhysRecordBuf,
@ShortValue);
end
else begin
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
LongValue := TcrInt32s(MinFieldValue^);
StartKeyValue := LongValue;
TFFProxyCursor(FileHandle^.CursorID).Dictionary.SetRecordField(RangeFieldN,
FileHandle^.ReadInfo^.PhysRecordBuf,
@LongValue);
end;
end;
if ContinueBuildStopKey then begin
FieldLen := stopKeyOffset - SavedOffset;
KeyBuf := @FileHandle^.ReadInfo^.StopKeyBuf[SavedOffset];
if FieldLen = 2 then begin
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
ShortValue := TcrInt16s(MaxFieldValue^);
StopKeyValue := ShortValue;
Move(ShortValue, KeyBuf^, FieldLen);
end
else begin
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
LongValue := TcrInt32s(MaxFieldValue^);
StopKeyValue := LongValue;
Move(LongValue, KeyBuf^, FieldLen);
end;
end;
{ If current field of min and max range in index are not equal, do not
try to build stop key. }
if ContinueBuildStopKey and ContinueBuildStartKey then
if StartKeyValue <> StopKeyValue then
ContinueBuildStopKey := False;
end;
function BuildDoubleRanges(
FileHandle: PPhysDbFileHandle;
RangeInfoList: PPhysDbRangeInfoArray;
RangeIndex: TcrInt16u;
RangeFieldN: TcrInt16u;
FieldInfo: PPhysDbFieldInfo;
ErrMsg: PAnsiChar) : TPhysDbError;
var
SavedOffset: TcrInt16u;
DoubleValue: Double;
FieldLen: TcrInt16u;
KeyBuf: PAnsiChar;
StartKeyBuf,
StopKeyBuf: PcrNumber;
begin
SavedOffset := StopKeyOffset;
Result := InitLimitRangeInfo(FileHandle, RangeInfoList, RangeIndex,
RangeFieldN, FieldInfo, ContinueBuildStartKey,
StartKeyLen, ContinueBuildStopKey, StopKeyOffset,
ErrMsg);
if Result <> errPhysDbNoError then Exit;
if ContinueBuildStartKey then begin
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
DoubleValue := NumberToDouble(TcrNumber(MinFieldValue^));
TFFProxyCursor(FileHandle^.CursorID).Dictionary.SetRecordField(RangeFieldN,
FileHandle^.ReadInfo^.PhysRecordBuf,
@DoubleValue);
end;
if ContinueBuildStopKey then begin
FieldLen := StopKeyOffset - SavedOffset;
KeyBuf := @FileHandle^.ReadInfo^.StopKeyBuf[SavedOffset];
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
DoubleValue := NumberToDouble(TcrNumber(MaxFieldValue^));
Move(DoubleValue, KeyBuf^, FieldLen);
end;
{ If current field of min and max range in index are not equal, do not
try to build stop key. }
with RangeInfoList^[RangeIndex].FieldRanges^[0] do begin
StartKeyBuf := PcrNumber(@MinFieldValue);
StopKeyBuf := PcrNumber(@MaxFieldValue);
end;
if not Assigned(StartKeyBuf) or
not Assigned(StopKeyBuf) or
(StartKeyBuf^ <> StopKeyBuf^) then
ContinueBuildStopKey := False;
end;
function BuildDecimalRanges(
FileHandle: PPhysDbFileHandle;
RangeInfoList: PPhysDbRangeInfoArray;
RangeIndex: TcrInt16u;
RangeFieldN: TcrInt16u;
FieldInfo: PPhysDbFieldInfo;
ErrMsg: PAnsiChar) : TPhysDbError;
(*
var
SavedLen,
SavedOffset: TcrInt16u;
FieldLen: TcrInt16u;
KeyBuf: PAnsiChar;
DoubleValue: Double;
StartKeyBuf,
StopKeyBuf: PcrNumber;
*)
begin
Result := errPhysDbNoError;
(* SavedLen := StartKeyLen;
SavedOffset := StopKeyOffset;
Result := InitLimitRangeInfo(FileHandle, RangeInfoList, RangeIndex,
RangeFieldN, FieldInfo, ContinueBuildStartKey,
StartKeyLen, ContinueBuildStopKey, StopKeyOffset,
ErrMsg);
if Result <> errPhysDbNoError then Exit;
if ContinueBuildStartKey then begin
FieldLen := StartKeyLen - SavedLen;
KeyBuf := @FileHandle^.ReadInfo^.KeyBuf[SavedLen];
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
DoubleValue := NumberToDouble(TcrNumber(MinFieldValue^));
if Doublevalue < 0 then begin
ContinueBuildStartKey := False;
StartKeyLen := SavedLen;
end
else
{
DoubleToDecimal(FileHandle, DoubleValue, KeyBuf, FieldLen,
FieldInfo^.NDecPlacesInNativeField)};
end;
if ContinueBuildStopKey then begin
FieldLen := StopKeyOffset - SavedOffset;
KeyBuf := @FileHandle^.ReadInfo^.StopKeyBuf[SavedOffset];
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
DoubleValue := NumberToDouble(TcrNumber(MaxFieldValue^));
Move(DoubleValue, KeyBuf^, FieldLen);
end;
{ If current field of min and max range in index are not equal, do not
try to build stop key. }
with RangeInfoList^[RangeIndex].FieldRanges^[0] do begin
StartKeyBuf := PcrNumber(@MinFieldValue);
StopKeyBuf := PcrNumber(@MaxFieldValue);
end;
if not Assigned(StartKeyBuf) or
not Assigned(StopKeyBuf) or
(StartKeyBuf^ <> StopKeyBuf^) then
ContinueBuildStopKey := False;*)
end;
function BuildTimeRanges(
FileHandle: PPhysDbFileHandle;
RangeInfoList: PPhysDbRangeInfoArray;
RangeIndex: TcrInt16u;
RangeFieldN: TcrInt16u;
FieldInfo: PPhysDbFieldInfo;
ErrMsg: PAnsiChar) : TPhysDbError;
(*
var
SavedLen,
SavedOffset: TcrInt16u;
KeyBuf: PAnsiChar;
TimeValue: TcrInt32s;
TimeValueN: TcrNumber;
StartKeyBuf,
StopKeyBuf: PcrNumber;
*)
begin
Result := errPhysDbNoError;
(* SavedLen := StartKeyLen;
SavedOffset := StopKeyOffset;
Result := InitLimitRangeInfo(FileHandle, RangeInfoList, RangeIndex,
RangeFieldN, FieldInfo, ContinueBuildStartKey,
StartKeyLen, ContinueBuildStopKey, StopKeyOffset,
ErrMsg);
if Result <> errPhysDbNoError then Exit;
if ContinueBuildStartKey then begin
KeyBuf := @FileHandle^.ReadInfo^.KeyBuf[SavedLen];
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
TimeValue := TBrahmaNumber(MinFieldValue^);
if TimeValue < 0 then begin
ContinueBuildStartKey := False;
StartKeyLen := SavedLen;
end
else
Convert2BTTime(TimeValue, KeyBuf);
end;
if ContinueBuildStopKey then begin
KeyBuf := @FileHandle^.ReadInfo^.StopKeyBuf[SavedOffset];
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
TimeValueN := TBrahmaNumber(MaxFieldValue^);
if TimeValue < 0 then begin
ContinueBuildStopKey := False;
StartKeyLen := SavedLen;
end
else
Move(TimeValueN, KeyBuf, SizeOf(TBrahmaNumber));
end;
{ If current field of min and max range in index are not equal, do not
try to extend the stop key. }
with RangeInfoList^[RangeIndex].FieldRanges^[0] do begin
StartKeyBuf := PBrahmaNumber(@MinFieldValue);
StopKeyBuf := PBrahmaNumber(@MaxFieldValue);
end;
if not Assigned(StartKeyBuf) or
not Assigned(StopKeyBuf) or
(StartKeyBuf^ <> StopKeyBuf^) then
ContinueBuildStopKey := False;*)
end;
function BuildLogicalRanges(
FileHandle: PPhysDbFileHandle;
RangeInfoList: PPhysDbRangeInfoArray;
RangeIndex: TcrInt16u;
RangeFieldN: TcrInt16u;
FieldInfo: PPhysDbFieldInfo;
ErrMsg: PAnsiChar) : TPhysDbError;
var
SavedLen,
SavedOffset,
FieldLen: TcrInt16u;
KeyBuf: PAnsiChar;
LogicalValue: TcrBoolean;
StartKeyValue,
StopKeyValue: TcrBoolean;
begin
SavedLen := StartKeyLen;
SavedOffset := StopKeyOffset;
Result := InitLimitRangeInfo(FileHandle, RangeInfoList, RangeIndex,
RangeFieldN, FieldInfo, ContinueBuildStartKey,
StartKeyLen, ContinueBuildStopKey, StopKeyOffset,
ErrMsg);
if Result <> errPhysDbNoError then Exit;
if ContinueBuildStartKey then begin
FieldLen := StartKeylen - SavedLen;
KeyBuf := @FileHandle^.ReadInfo^.KeyBuf[SavedLen];
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
LogicalValue := TcrBoolean(MinFieldValue^);
if FieldLen = 1 then
TcrInt8u(KeyBuf^) := Ord(LogicalValue)
else
PcrInt16u(KeyBuf)^ := Ord(LogicalValue);
end;
if ContinueBuildStopKey then begin
FieldLen := StopKeyOffset - SavedOffset;
KeyBuf := @FileHandle^.ReadInfo^.StopKeyBuf[SavedOffset];
with RangeInfoList^[RangeIndex].FieldRanges^[0] do
LogicalValue := TcrBoolean(MaxFieldValue^);
if FieldLen = 1 then
TcrInt8u(KeyBuf^) := Ord(LogicalValue)
else
PcrInt16u(KeyBuf)^ := Ord(LogicalValue);
end;
{ If current field of min and max range in index are not equal, do not
try to extend the stop key. }
with RangeInfoList^[RangeIndex].FieldRanges^[0] do begin
StartKeyValue := TcrBoolean(@MinFieldValue);
StopKeyValue := TcrBoolean(@MaxFieldValue);
end;
if StartKeyValue = StopKeyValue then
ContinueBuildStopKey := False;
end;
begin
CanDoRangeLimit := false;
Result := errPhysDbNoError;
if NRanges = 0 then Exit;
with FileHandle^.ReadInfo^ do
FillChar(KeyBuf, SizeOf(KeyBuf), #0);
StopKeyOffset := 0;
StartKeyLen := 0;
NFieldsInStartKey := 0;
ContinueBuildStopKey := True;
ContinueBuildStartKey := True;
MinInclusive := True;
FileHandle^.ReadInfo^.AscendingIndex :=
IndexesPtr^.IndexInfo^[IndexesPtr^.IndexInUse].Ascending;
{ Swap the begin and end range key, when descending index }
if not FileHandle^.ReadInfo^.AscendingIndex then begin
AddToLog(' swapping begin and end range key');
for RangeN := 0 to pred(NRanges) do begin
with RangeInfoList^[RangeN] do begin
for FieldN := 0 to pred(RangeInfoList^[RangeN].NFieldRanges) do begin
with RangeInfoList^[RangeN].FieldRanges^[FieldN] do begin
TempPtr := MinFieldValue;
MinFieldValue := MaxFieldValue;
MaxFieldValue := TempPtr;
TempBool := MinInclusive;
MinInclusive := MaxInclusive;
MaxInclusive := TempBool;
end;
end;
end;
end;
end;
{ Start to do the range search }
IndexInfo := IndexesPtr^.IndexInfo^[IndexesPtr^.IndexInUse];
for RangeN := 0 to pred(NRanges) do begin
RangeFieldN := IndexInfo.FieldNumInFile^[RangeN];
if not RangeInfoList^[RangeN].SelectIfWithinRange or
(RangeInfoList^[RangeN].NFieldRanges <> 1) then
Break;
case InfoPtr^.FieldInfo^[RangeFieldN].NativeFieldType of
fldZSTRING:
begin
CanDoRangeLimit := true;
Result := BuildStringRanges(FileHandle, RangeInfoList, RangeN,
RangeFieldN, @InfoPtr^.FieldInfo^[RangeFieldN], ErrMsg);
end;
fldDATE:
begin
CanDoRangeLimit := true;
Result := BuildDateRanges(FileHandle, RangeInfoList, RangeN,
RangeFieldN, @InfoPtr^.FieldInfo^[RangeFieldN], ErrMsg);
end;
fldINT16, fldINT32:
begin
CanDoRangeLimit := true;
Result := BuildIntegerRanges(FileHandle, RangeInfoList, RangeN,
RangeFieldN, @InfoPtr^.FieldInfo^[RangeFieldN], ErrMsg);
end;
fldFLOAT, fldstMONEY:
begin
CanDoRangeLimit := true;
Result := BuildDoubleRanges(FileHandle, RangeInfoList, RangeN,
RangeFieldN, @InfoPtr^.FieldInfo^[RangeFieldN], ErrMsg);
end;
fldBCD:
begin
CanDoRangeLimit := true;
ShowMessage('BCD datatypes not supported for ranges');
{Result := BuildDecimalRanges(FileHandle, RangeInfoList, RangeN,
RangeFieldN, @InfoPtr^.FieldInfo^[RangeFieldN], ErrMsg);}
end;
fldTIME:
begin
CanDoRangeLimit := true;
ShowMessage('Time datatypes are not supported for ranges');
{Result := BuildTimeRanges(FileHandle, RangeInfoList, RangeN,
RangeFieldN, @InfoPtr^.FieldInfo^[RangeFieldN], ErrMsg);}
end;
fldBOOL:
begin
CanDoRangeLimit := true;
Result := BuildLogicalRanges(FileHandle, RangeInfoList, RangeN,
RangeFieldN, @InfoPtr^.FieldInfo^[RangeFieldN], ErrMsg);
end;
else CanDoRangeLimit := false;
end;
if ContinueBuildStartKey and
not RangeInfoList^[RangeN].FieldRanges^[0].MinInclusive then
MinInclusive := False;
if (Result <> errPhysDbNoError) or not CanDoRangeLimit then
Break;
if ContinueBuildStartKey then
Inc(NFieldsInStartKey)
else
TFFProxyCursor(FileHandle^.CursorID).Dictionary.SetRecordField(RangeFieldN,
FileHandle^.ReadInfo^.PhysRecordBuf,
nil);
end;
{ Clear the remaining fields in index }
for FieldN := NFieldsInStartKey to pred(IndexInfo.NFields) do begin
IndexFieldN := IndexInfo.FieldNumInFile^[FieldN];
TFFProxyCursor(FileHandle^.CursorID).Dictionary.SetRecordField(IndexFieldN,
FileHandle^.ReadInfo^.PhysRecordBuf,
nil);
end;
if (Result = errPhysDbNoError) and CanDoRangeLimit then begin
if StartKeyLen > 0 then begin
with TFFProxyCursor(FileHandle^.CursorID) do begin
Dictionary.ExtractKey(IndexID,
@FileHandle^.ReadInfo^.PhysRecordBuf,
@FileHandle^.ReadInfo^.KeyBuf);
end;
if MinInclusive then
SearchCond := skaEqual
else
SearchCond := skaGreater;
FFError := ServerEngine.CursorSetToKey(FileHandle^.CursorID,
SearchCond,
True,
NFieldsInStartKey,
0,
@FileHandle^.ReadInfo^.KeyBuf);
AddToLogFmt(' CursorSetToKey: [%d]', [FFError]);
if FFError = DBIERR_NONE then begin
FFError := ServerEngine.CursorSetRange(FileHandle^.CursorID,
True,
NFieldsInStartKey,
0,
@FileHandle^.ReadInfo^.KeyBuf,
MinInclusive,
0,
0,
nil,
True);
AddToLogFmt(' CursorSetRange: [%d]', [FFError]);
end;
end else begin
FFError := ServerEngine.CursorSetToBegin(FileHandle^.CursorID);
AddToLogFmt(' CursorSetRange: [%d]', [FFError]);
end;
if FFError <> DBIERR_NONE then SysUtils.Abort;
FileHandle^.RangeLimit := True;
end;
end;
function InitReadInfoForRange(
FileHandle : PPhysDbFileHandle;
InfoPtr : PPhysDbFileInfo;
IndexesPtr : PPhysDbIndexesInfo;
RangeInfoList : PPhysDbRangeInfoArray;
NRanges : Word;
var CanDoRangeLimit : TcrBoolean;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
Result := errPhysDbNoError;
with fileHandle^ do begin
RangeLimit := False;
with ReadInfo^ do begin
RangeFieldInfo := nil;
NStopKeyRanges := 0;
StopKeyLen := 0;
if NRanges > 0 then begin
IndexCaseSensitive := IndexesPtr^.IndexInfo^[IndexesPtr^.IndexInUse].CaseSensitive;
{ Allocate structure for range field info }
FFGetZeroMem(RangeFieldInfo, SizeOf(TPhysDbReadFieldInfo) * NRanges);
Result := CanDoRangeLimitOnField(FileHandle, InfoPtr, IndexesPtr,
RangeInfoList, NRanges,
CanDoRangeLimit, ErrMsg);
end;
end;
end;
end;
var
ReadFieldNo : integer;
IndexFieldNo : integer;
FieldN : integer;
begin { InitDataFileForReadingVer17 }
AddToLog('InitDataFileForReadingVer17');
AddToLogFmt(' PathAndFilename: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
if Assigned(IndexesPtr) then
AddToLogFmt(' IndexesPtr^.IndexInUse: [%d]', [IndexesPtr^.IndexInUse]);
AddToLogFmt(' NRanges: [%d]', [NRanges]);
if Assigned(RangeInfoList) then begin
AddBlockToLog(' RangeInfoList^[0]: ', @RangeInfoList^[0], SizeOf(TPhysDbRangeInfo));
with RangeInfoList^[0] do begin
AddToLogFmt(' RangeInfoList^[0].FieldName: [%s]', [FieldName]);
AddToLogFmt(' RangeInfoList^[0].BrahmaType: [%s]', [FieldValueTypes[BrahmaType]]);
AddToLogFmt(' RangeInfoList^[0].BrahmaFieldLen: [%d]', [BrahmaFieldLen]);
AddToLogFmt(' RangeInfoList^[0].SelectIfWithinRange: [%s]', [BoolToStr(SelectIfWithinRange)]);
AddToLogFmt(' RangeInfoList^[0].NFieldRanges: [%d]', [NFieldRanges]);
end;
end;
Result := errPhysDbNoError;
try
try
CanDoRangeLimit := false;
with FileHandle^ do begin
ReadInfo := nil;
{ Allocate structure for read state info }
FFGetZeroMem(ReadInfo, SizeOf(TPhysDbReadInfo));
MainFile := True;
ReadInfo^.NumRanges := NRanges;
with ReadInfo^ do begin
ValuesUnique := true;
NBytesInReadRecord := InfoPtr^.NBytesInReadRecord;
NFieldsInReadRecord := InfoPtr^.NFieldsInReadRecord;
NBytesInIndexRecord := InfoPtr^.NBytesInIndexRecord;
NFieldsInIndexRecord := InfoPtr^.NFieldsInIndexRecord;
NBytesInPhysRecord := InfoPtr^.NBytesInPhysRecord + 1;
{ Allocate the physical record buffer }
FFGetZeroMem(PhysRecordBuf, NBytesInPhysRecord);
{ Position at first record of file for subsequent reading }
CurrentRecord := 0;
{ Allocate structure for read state information per translated field }
FFGetZeroMem(FieldInfo, SizeOf(TPhysDbReadFieldInfo) * InfoPtr^.NFieldsInReadRecord);
{ Allocate structure for read state information per untranslated field }
FFGetZeroMem(IndexFieldInfo, SizeOf(TPhysDbReadFieldInfo) * InfoPtr^.NFieldsInIndexRecord);
NFieldsInIndexDefn := 0;
IndexDefnInfo := nil;
{ Pass through complete file info structure to find all (translated)
read record fields and (untranslated) index record fields. }
ReadFieldNo := 0;
IndexFieldNo := 0;
for FieldN := 0 to pred(InfoPtr^.NFields) do begin
with InfoPtr^.FieldInfo^[FieldN] do begin
if UsedInReadRecord then begin
{ At a field to be translated in read record }
FieldInfo^[ReadFieldNo].FieldNo := FieldN;
FieldInfo^[ReadFieldNo].ReadFieldNo := ReadFieldNo;
FieldInfo^[ReadFieldNo].OffsetInRecord := OffsetInReadRecord;
FieldInfo^[ReadFieldNo].FieldType := FieldType;
FieldInfo^[ReadFieldNo].FieldLength := NBytesInField;
FieldInfo^[ReadFieldNo].NativeFieldOffset := NativeFieldOffset;
FieldInfo^[ReadFieldNo].NativeFieldType := NativeFieldType;
FieldInfo^[ReadFieldNo].NBytesInNativeField := NBytesInNativeField;
FieldInfo^[ReadFieldNo].NDecPlacesInNativeField := NDecPlacesInNativeField;
Inc(ReadFieldNo);
end;
if UsedInIndexRecord then begin
{ At a field to be untranslated in index record }
IndexFieldInfo^[IndexFieldNo].FieldNo := FieldN;
IndexFieldInfo^[IndexFieldNo].ReadFieldNo := IndexFieldNo;
IndexFieldInfo^[IndexFieldNo].OffsetInRecord := OffsetInIndexRecord;
IndexFieldInfo^[IndexFieldNo].FieldType := FieldType;
IndexFieldInfo^[IndexFieldNo].FieldLength := NBytesInNativeField;
IndexFieldInfo^[IndexFieldNo].NativeFieldOffset := NativeFieldOffset;
IndexFieldInfo^[IndexFieldNo].NativeFieldType := NativeFieldType;
IndexFieldInfo^[IndexFieldNo].NBytesInNativeField := NBytesInNativeField;
IndexFieldInfo^[IndexFieldNo].NDecPlacesInNativeField := NDecPlacesInNativeField;
Inc(IndexFieldNo);
end;
end;
end;
end;
end;
Result := InitReadInfoForRange(FileHandle, InfoPtr, IndexesPtr,
RangeInfoList, NRanges, CanDoRangeLimit, ErrMsg);
if Result <> errPhysDbNoError then
raise Exception.Create(StrPas(ErrMsg));
AddToLogFmt(' CanDoRangeLimit: [%s]', [BoolToStr(CanDoRangeLimit)]);
except
on EOutOfMemory do begin
Result := errPhysDbNotEnoughMemory;
TermDataFileForReading(FileHandle, ErrMsg);
StrPCopy(ErrMsg, '');
end;
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
TermDataFileForReading(FileHandle, ErrMsg);
StrPCopy(ErrMsg, E.Message);
if FFError <> DBIERR_NONE then
Result := IDAPIError(FFError, ErrMsg);
end;
end;
finally
StrPCopy(DebugBuff, PhysDbErrors[Result]); { this seems necessary for 32-bit }
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function InitDataFileAndIndexForReadV115(
FileHandle: PPhysDbFileHandle;
InfoPtr: PPhysDbFileInfo;
IndexesPtr: PPhysDbIndexesInfo;
LookupOptPtr: PPhysDbLookupOptInfo;
ErrMsg: PAnsiChar) : TPhysDbError;
var
CanDoRangeLimit: TcrBoolean;
{ This function serves the same purpose as InitDataFileForReading, but
is called when initializing reading from a file with an index,
whereas InitDataFileForReading is called when reading from a file
without. The index info structure (from FetchDataFileIndexInfo) is
passed to this function to identify the chosen index. }
function InitReadInfoForIndex: TPhysDbError;
var
IndexInfo: TPhysDbIndexInfo;
IndexOffset,
FieldIndex,
FieldN: integer;
begin
Result := errPhysDbNoError;
if IndexesPtr^.NIndexes = 0 then begin
Result := errPhysDbFileIntegrityError;
Exit;
end;
{ Allocate structure to save information on index fields }
IndexInfo := IndexesPtr^.IndexInfo^[IndexesPtr^.IndexInUse];
with FileHandle^.ReadInfo^ do begin
ValuesUnique := IndexInfo.ValuesUnique;
IndexCaseSensitive := IndexInfo.CaseSensitive;
NFieldsInIndexDefn := IndexInfo.NFields;
FFGetZeroMem(IndexDefnInfo, SizeOf(TPhysDbReadFieldInfo) * IndexInfo.NFields);
{ Default number of lookup fields to same as index }
NFieldsInLookupValue := NFieldsInIndexDefn;
LookupValueLen := LookupOptPtr^.LookupValueLen;
LastLookupFieldLen := 0;
LastLookupFieldIsSubstr := false;
IndexOffset := 0;
for FieldN := 0 to pred(IndexInfo.NFields) do begin
FieldIndex := IndexInfo.FieldNumInFile^[FieldN];
IndexDefnInfo^[FieldN].FieldNo := FieldIndex;
IndexDefnInfo^[FieldN].OffsetInRecord := IndexOffset;
IndexDefnInfo^[FieldN].FieldLength := InfoPtr^.FieldInfo^[FieldIndex].NBytesInNativeField;
IndexDefnInfo^[FieldN].FieldType := InfoPtr^.FieldInfo^[FieldIndex].FieldType;
{ Detect if we have link on partial number of fields }
if IndexDefnInfo^[FieldN].OffsetInRecord >= LookupOptPtr^.LookupValueLen then
if NFieldsInLookupValue = NFieldsInIndexDefn then
if FieldN > 0 then
NFieldsInLookupValue := FieldN;
IndexOffset := IndexOffset + InfoPtr^.FieldInfo^[FieldIndex].NBytesInNativeField;
end;
{ Detect if we have link to a partial string field at the end
of lookup value. }
if (IndexDefnInfo^[IndexInfo.NFields - 1].FieldType = ftStringField) and
LookupOptPtr^.PartialMatch then
LastLookupFieldIsSubstr := True;
end;
end;
begin
AddToLog('InitDataFileAndIndexForReadV115');
AddToLogFmt(' PathAndFilename: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
AddBlockToLog(' LookupOptPtr^:', LookupOptPtr, SizeOf(LookupOptPtr^));
Result := errPhysDbNoError;
try
{ Perform same initialization as for no index file }
Result := InitDataFileForReadingVer17(FileHandle, InfoPtr, IndexesPtr,
nil, 0, CanDoRangeLimit, ErrMsg);
if Result = errPhysDbNoError then begin
{ Perform index specific initialization }
Result := InitReadInfoForIndex;
if Result <> errPhysDbNoError then
TermDataFileForReading(FileHandle, ErrMsg);
end;
FileHandle^.MainFile := False;
except
on EOutOfMemory do begin
Result := errPhysDbNotEnoughMemory;
StrPCopy(ErrMsg, '');
end;
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function TermDataFileForReading(
FileHandle: PPhysDbFileHandle;
ErrMsg: PAnsiChar) : TPhysDbError;
begin
AddToLog('TermDataFileForReading');
Result := errPhysDbNoError;
try
if Assigned(FileHandle) then begin
AddToLogFmt(' FileName: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
with FileHandle^ do begin
if Assigned(ReadInfo) then begin
with ReadInfo^ do begin
FFFreeMem(PhysRecordBuf, NBytesInPhysRecord);
PhysRecordBuf := nil;
FFFreeMem(FieldInfo, SizeOf(TPhysDbReadFieldInfo) * NFieldsInReadRecord);
FieldInfo := nil;
FFFreeMem(IndexFieldInfo, SizeOf(TPhysDbReadFieldInfo) * NFieldsInIndexRecord);
IndexFieldInfo := nil;
FFFreeMem(RangeFieldInfo, SizeOf(TPhysDbReadFieldInfo) * NumRanges);
RangeFieldInfo := nil;
end;
FFFreeMem(ReadInfo, sizeof(TPhysDbReadInfo));
ReadInfo := nil;
end;
end;
end;
except
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function NRecurringRecordsToRead(
FileHandle: PPhysDbFileHandle;
var NRecordsToRead: LongInt;
ErrMsg: PAnsiChar) : TPhysDbError;
var
NRecords : TcrInt32u;
FFError : TffResult;
begin
AddToLog('NRecurringRecordsToRead');
AddToLogFmt(' FName: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
Result := errPhysDbNoError;
try
FFError := ServerEngine.TableGetRecCount(FileHandle^.CursorID, NRecords);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
NRecordsToRead := NRecords;
except
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
AddToLogFmt(' Records count: [%d]', [NRecordsToRead]);
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
{ Translate and copy fields requested by Database Manager to read
record buffer. }
function FetchReadRecFields(
ReadInfo : PPhysDbReadInfo;
HCursor : TffCursorID;
NotXlateDOSString : Boolean;
ReadRecordBuf : PByteArray;
ReadNullFlags : PcrBooleanArray;
ErrMsg : PAnsiChar) : TPhysDbError;
var
FFError : TffResult;
I : integer;
ReadRecOffset : integer;
BoolValue : Bool;
DoubleValue : Double;
SingleValue : Single; {!!.02}
CompValue : Comp; {!!.02}
ExtendedValue : Extended; {!!.02}
CurrencyValue : Currency; {!!.02}
Int16Value : TcrInt16s;
Int32Value : TcrInt32s;
UInt16Value : TcrInt16u;
UInt32Value : TcrInt32u;
DateValue : TDbiDate;
Year : TcrInt16u;
Month, Day : TcrInt16u;
SYear : Integer; {!!.02}
SMonth, SDay : Integer; {!!.02}
TimeValue : TDbiTime;
Millisec : TcrInt16u;
SHours, {!!.02}
SMinutes, {!!.02}
SSeconds : Byte; {!!.02}
HourL,
MinuteL : TcrInt32u;
DateTime : TDateTime; {!!.02}
CrTime : TcrTime;
CrTimeArray : array[1..4] of Byte absolute CrTime;
IsNull : boolean;
FType : TffFieldType; {!!.02}
aByte : Byte; {!!.02}
begin
// AddToLog('FetchReadRecFields');
// AddToLogFmt(' CursorID: [%d]', [HCursor]);
if hCursor = 0 then begin
Result := IDAPIError(DBIERR_NOTINITIALIZED, ErrMsg);
Exit;
end;
Result := errPhysDbNoError;
{ Translate and copy fields requested by Database Manager to read
record buffer. }
for I := 0 to pred(ReadInfo^.NFieldsInReadRecord) do begin
with ReadInfo^.FieldInfo^[I] do begin
ReadRecOffset := OffsetInRecord;
case NativeFieldType of
fldZSTRING:
begin
FType := TFFProxyCursor(HCursor).Dictionary.FieldType[FieldNo];
if (FType = fftNullString) or
(FType = fftNullAnsiStr) or {!!.02}
(FType = fftChar) then begin {!!.02}
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@ReadRecordBuf^[ReadRecOffset]);
// AddToLogFmt(' read Null String field: [%s]',
// [PChar(@ReadRecordBuf^[ReadRecOffset])]);
end
else if (FType = fftWideChar) or {!!.02}
(FType = fftWideString) then {!!.02}
ShowMessage('Widestring types not supported') {!!.02}
else begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@ReadRecordBuf^[Pred(ReadRecOffset)]);
// AddToLogFmt(' read String field: [%s]',
// [PChar(@ReadRecordBuf^[Pred(ReadRecOffset)])]);
end;
ReadNullFlags^[I] := IsNull;
if not NotXlateDOSString then
OemToAnsi(@ReadRecordBuf^[ReadRecOffset],
@ReadRecordBuf^[ReadRecOffset]);
TrimStrR(@ReadRecordBuf^[ReadRecOffset]);
end;
fldBOOL:
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@BoolValue);
if IsNull then
ReadNullFlags^[I] := true
else begin
ReadNullFlags^[I] := false;
PcrBoolean(@ReadRecordBuf^[ReadRecOffset])^ := BoolValue;
end;
// AddToLogFmt(' read Bool field: [%s]',
// [BoolToStr(BoolValue)]);
end;
fldFLOAT,
fldstMONEY:
begin
FType := TFFProxyCursor(HCursor).Dictionary.FieldType[FieldNo];{begin !!.02}
case FType of
fftSingle :
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@SingleValue);
DoubleValue := SingleValue;
end;
fftComp :
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@CompValue);
DoubleValue := CompValue;
end;
fftExtended :
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@ExtendedValue);
DoubleValue := ExtendedValue;
end;
fftCurrency :
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@CurrencyValue);
DoubleValue := CurrencyValue;
end;
else
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@DoubleValue);
end; {end !!.02}
if IsNull then
ReadNullFlags^[I] := true
else begin
ReadNullFlags^[I] := false;
PcrNumber(@ReadRecordBuf^[ReadRecOffset])^ :=
DoubleToNumber(DoubleValue);
end;
end;
fldINT16:
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@Int16Value);
if IsNull then
ReadNullFlags^[I] := true
else begin
ReadNullFlags^[I] := false;
PcrInt16s(@ReadRecordBuf^[ReadRecOffset])^ := Int16Value;
end;
end;
fldINT32:
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@Int32Value);
if IsNull then
ReadNullFlags^[I] := true
else begin
ReadNullFlags^[I] := false;
PcrInt32s(@ReadRecordBuf^[ReadRecOffset])^ := Int32Value;
end;
end;
fldUINT16:
begin
FType := TFFProxyCursor(HCursor).Dictionary.FieldType[FieldNo]; {begin !!.02}
if FType <> fftByte then
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@UInt16Value)
else begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@aByte);
UInt16Value := aByte;
end; {end !!.02}
if IsNull then
ReadNullFlags^[I] := true
else begin
ReadNullFlags^[I] := false;
PcrInt16u(@ReadRecordBuf^[ReadRecOffset])^ := UInt16Value;
end;
end;
fldUINT32:
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@UInt32Value);
if IsNull then
ReadNullFlags^[I] := true
else begin
ReadNullFlags^[I] := false;
PcrInt32s(@ReadRecordBuf^[ReadRecOffset])^ := UInt32Value;
end;
end;
fldDATE:
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@DateValue);
if IsNull then
ReadNullFlags^[I] := true
else begin
ReadNullFlags^[I] := false;
FType := TFFProxyCursor(HCursor).Dictionary.FieldType[FieldNo]; {begin !!.02}
if FType = fftStDate then begin
StDateToDMY(TStDate(DateValue), SDay, SMonth, SYear);
Day := SDay;
Month := SMonth;
Year := SYear;
end else
FFBDEDateDecode(DateValue, Day, Month, Year); {end !!.02}
PcrDate(@ReadRecordBuf^[ReadRecOffset])^ :=
YearMonthDayToCrDate(Year, Month, Day);
end;
end;
fldTIME:
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@TimeValue);
if IsNull then
ReadNullFlags^[I] := true
else begin
ReadNullFlags^[I] := false;
StTimeToHMS(TimeValue, SHours, SMinutes, SSeconds); {begin !!.02}
HourL := SHours;
MinuteL := SMinutes;
Millisec := SSeconds * 1000;
{ Compute Brahma time (number of hundredths of seconds) }
CrTime := (HourL * 360000 + MinuteL * 6000 + (Millisec div 10)) div 100; {end !!.02}
PcrTime(@ReadRecordBuf^[ReadRecOffset])^ := CrTime;
end;
end;
fldTIMESTAMP:
begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo, {begin !!.02}
ReadInfo^.PhysRecordBuf,
IsNull,
@DateTime);
StrPCopy(PChar(@ReadRecordBuf^[ReadRecOffset]),
FormatDateTime('yyyy/mm/dd hh:nn:zz', DateTime - 693594.0)); {end !!.02}
end;
fldBCD:
begin
ShowMessage('BCD datatypes not supported');
end;
fldBLOB,
fldstMEMO,
fldstFMTMEMO,
fldstBINARY,
fldstOLEOBJ,
fldstGRAPHIC,
fldstTYPEDBINARY:
begin
(*
{ Check the unstable bookmark }
FFError := DbiGetCursorProps(HCursor, CursorProps);
if not CursorProps.bBookMarkStable then begin
Result := IDAPIError(90, ErrMsg); { 90? }
Exit;
end;
{ Check any primary index, sometimes bBookMarkStable doesn't work }
HasPrimaryIndex := False;
for IndexN := 0 to CursorProps.iIndexes do begin
DbiGetIndexDesc(HCursor, IndexN + 1, IndexDesc);
if IndexDesc.bPrimary = True then begin
HasPrimaryIndex := True;
Break;
end;
end;
(*
if not HasPrimaryIndex then begin
Result := IDAPIError(90, ErrMsg);
Exit;
end;
*)
{ Save the field info and RecNo for memo read. }
PcrInt16u(@ReadRecordBuf^[ReadRecOffset])^:= FieldNo;
FFError := ServerEngine.CursorGetBookmark(HCursor,
@ReadRecordBuf^[ReadRecOffset + SizeOf(TcrInt16u)]);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
end;
else
Break;
end;
end;
end;
end;
{ Copy fields (without translating) requested by Database Manager to
index record buffer. }
function FetchIndexRecFields(
ReadInfo : PPhysDbReadInfo;
HCursor : TffCursorID;
IndexRecordBuf : PByteArray;
IndexNullFlags : PcrBooleanArray;
ErrMsg : PAnsiChar) : TPhysDbError;
var
I : integer;
IsNull : Boolean;
begin
// AddToLog('FetchIndexRecFields');
// AddToLogFmt(' CursorID: [%d]', [HCursor]);
// AddToLogFmt(' Field count: [%d]', [ReadInfo^.NFieldsInIndexRecord]);
Result := errPhysDbNoError;
for I := 0 to pred(ReadInfo^.NFieldsInIndexRecord) do begin
IndexNullFlags^[I] := false;
// AddToLogFmt(' Field: [%d]', [ReadInfo^.IndexFieldInfo^[I].FieldNo]);
with ReadInfo^.IndexFieldInfo^[I] do begin
TFFProxyCursor(HCursor).Dictionary.GetRecordField(FieldNo,
ReadInfo^.PhysRecordBuf,
IsNull,
@IndexRecordBuf^[OffsetInRecord]);
IndexNullFlags^[I] := IsNull;
end;
end;
end;
function ReadFlatRecordVer15(
FileHandle: PPhysDbFileHandle;
ReadRecordBuf: PByteArray;
ReadNullFlags: PcrBooleanArray;
IndexRecordBuf: PByteArray;
IndexNullFlags: PcrBooleanArray;
var RecordRead: TcrBoolean;
ErrMsg: PAnsiChar) : TPhysDbError;
var
NRecordsSkipped : TcrInt32u;
FFError : TffResult;
begin
AddToLog('ReadFlatRecordVer15');
AddToLogFmt(' FName: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
Result := errPhysDbNoError;
try
if FileHandle^.ReadInfo^.CurrentRecord > 0 then begin
FileHandle^.ReadInfo^.CurrentRecord := 0;
{ Position at the first record of file for subsequent reading }
FFError := ServerEngine.CursorSetToBegin(FileHandle^.CursorID);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
end;
Result := ReadNextRecurringRecordVer15(
FileHandle,
ReadRecordBuf,
ReadNullFlags,
IndexRecordBuf,
IndexNullFlags,
RecordRead,
NRecordsSkipped,
ErrMsg);
except
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function ReadNextRecurringRecordVer15(
FileHandle: PPhysDbFileHandle;
ReadRecordBuf: PByteArray;
ReadNullFlags: PcrBooleanArray;
IndexRecordBuf: PByteArray;
IndexNullFlags: PcrBooleanArray;
var RecordRead: TcrBoolean;
var NRecordsSkipped: LongInt;
ErrMsg: PAnsiChar) : TPhysDbError;
function RecordWithinRange(
FileHandle: PPhysDbFileHandle;
StopHere: Boolean;
ErrMsg: PAnsiChar) : TPhysDbError;
var
RangeFieldInfo : TPhysDbReadFieldInfo;
RangeN : integer;
StopKeyOffset : integer;
KeyBuf : Pointer;
StopKeyBuf : PAnsiChar;
NullField : Boolean;
function TestRangeLimitForOneField(
KeyBuf : PAnsiChar;
IndexCaseSensitive,
AscendingIndex : TcrBoolean;
var StopHere : Boolean) : TPhysDbError;
var
SaveKeyCh : AnsiChar;
SaveStopKeyCh : AnsiChar;
CompResult : Integer;
MinLen : Integer;
DateKey,
StopDate : TDbiDate;
ShortKey,
StopShort : TcrInt16s;
LongKey,
StopLong : TcrInt32s;
DoubleKey,
StopDouble : Double;
StopKeyLen : TcrInt16u;
Evaluate : Boolean;
begin
Result := errPhysDbNoError;
StopHere := False;
Evaluate := True;
CompResult := 0;
case RangeFieldInfo.NativeFieldType of
fldZSTRING:
begin
StopKeyLen := StrLen(StopKeyBuf);
if RangeFieldInfo.NBytesInNativeField > StopKeyLen then
MinLen := StopKeyLen
else
MinLen := RangeFieldInfo.NBytesInNativeField;
SaveKeyCh := KeyBuf[MinLen];
KeyBuf[MinLen] := #0;
SaveStopKeyCh := StopKeyBuf[RangeFieldInfo.NBytesInNativeField];
StopKeyBuf[RangeFieldInfo.NBytesInNativeField] := #0;
if IndexCaseSensitive then
CompResult := StrComp(KeyBuf, StopKeyBuf)
else
CompResult := StrIComp(KeyBuf, StopKeyBuf);
KeyBuf[MinLen] := SaveKeyCh;
StopKeyBuf[RangeFieldInfo.NBytesInNativeField] := SaveStopKeyCh;
end;
fldDATE:
begin
DateKey := PDbiDate(KeyBuf)^;
StopDate := PDbiDate(StopKeyBuf)^;
CompResult := -1;
if DateKey = StopDate then CompResult := 0
else if DateKey > StopDate then CompResult := 1;
end;
fldINT16:
begin
ShortKey := PcrInt16s(KeyBuf)^;
StopShort := PcrInt16s(StopKeyBuf)^;
CompResult := -1;
if ShortKey = StopShort then CompResult := 0
else if ShortKey > StopShort then CompResult := 1;
end;
fldINT32:
begin
LongKey := PcrInt32s(KeyBuf)^;
StopLong := PcrInt32s(StopKeyBuf)^;
CompResult := -1;
if LongKey = StopLong then CompResult := 0
else if LongKey > StopLong then CompResult := 1;
end;
fldFLOAT,
fldstMONEY:
begin
DoubleKey := PDouble(KeyBuf)^;
StopDouble := PDouble(StopKeyBuf)^;
CompResult := -1;
if DoubleKey = StopDouble then CompResult := 0
else if DoubleKey > StopDouble then CompResult := 1;
end;
fldTIME:
begin
{}
end;
fldBOOL:
begin
Evaluate := False;
if TcrBoolean(StopKeyBuf^) then
if TcrBoolean(KeyBuf^) then
StopHere := False
else
StopHere := True
else
if TcrBoolean(KeyBuf^) then
StopHere := True
else
StopHere := False;
end;
else
begin
Result := errPhysDbProgrammingError;
Exit;
end;
end;
if Evaluate then begin
if RangeFieldInfo.StopInclusive then
if AscendingIndex then
StopHere := (CompResult > 0)
else
StopHere := (CompResult < 0)
else
if AscendingIndex then
StopHere := (CompResult >= 0)
else
StopHere := (CompResult <= 0);
end;
end;
begin
Result := errPhysDbNoError;
if FileHandle^.ReadInfo^.StopKeyLen = 0 then Exit;
{ Loop through all the range fields for the current index }
for RangeN := 0 to pred(FileHandle^.ReadInfo^.NStopKeyRanges) do begin
RangeFieldInfo := FileHandle^.ReadInfo^.RangeFieldInfo^[RangeN];
StopHere := False;
{ KeyBuf points to the values from the current current.
stopKeyBuf points to the values that define the end of the range. }
StopKeyOffset := RangeFieldInfo.OffsetInStopKeyBuf;
KeyBuf := Addr(FileHandle^.ReadInfo^.KeyBuf[StopKeyOffset]);
StopKeyBuf := Addr(FileHandle^.ReadInfo^.StopKeyBuf[StopKeyOffset]);
{ Get the range value values out of the current record and into
the comparison buffer in their native format. }
TFFProxyCursor(FileHandle^.CursorID).Dictionary.GetRecordField(RangeFieldInfo.FieldNo,
FileHandle^.ReadInfo^.PhysRecordBuf,
NullField,
KeyBuf);
if NullField then
Continue;
{ Test this record for out of range on the current range field }
Result := TestRangeLimitForOneField(
KeyBuf,
FileHandle^.ReadInfo^.IndexCaseSensitive,
FileHandle^.ReadInfo^.AscendingIndex,
StopHere);
if Result <> errPhysDbNoError then Exit;
{ Once we've found a field with an out of range value, we needn't look
at the remaining range fields }
if StopHere then
Break;
end;
end;
var
FFError : TffResult;
StopHere : Boolean;
Buffer : TffShStr;
begin
// AddToLog('ReadNextRecurringRecordVer15');
// AddToLogFmt(' FName: [%s]', [FileHandle^.PathAndFileName]);
// AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
Result := errPhysDbNoError;
try
try
RecordRead := false;
NRecordsSkipped := 0;
while not RecordRead do begin
{ Advance to the next recurring record, skipping if it is locked
or deleted by another user }
FFError := ServerEngine.RecordGetNext(FileHandle^.CursorID,
ffltNoLock,
nil);
if (FFError = DBIERR_RECDELETED) or (FFError = DBIERR_RECNOTFOUND) then begin
Inc(FileHandle^.ReadInfo^.CurrentRecord);
Inc(NRecordsSkipped);
Continue; { Try the next record }
end;
if FFError = DBIERR_EOF then
Exit;
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
FFError := ServerEngine.RecordGet(FileHandle^.CursorID,
ffltNoLock,
FileHandle^.ReadInfo^.PhysRecordBuf);
if FFError = DBIERR_NONE then begin
{ Test if index fields still in range. If in range, break, else return }
if FileHandle^.RangeLimit then begin
StopHere := False;
if RecordWithinRange(FileHandle, StopHere, ErrMsg) <> errPhysDbNoError then
Break;
if StopHere then
Exit;
end;
RecordRead := true;
Inc(FileHandle^.ReadInfo^.CurrentRecord);
end
else begin
if (FileHandle^.ReadInfo^.CurrentRecord > 0) and
((FFError = DBIERR_RECDELETED) or (FFError = DBIERR_RECNOTFOUND)) then
Inc(NRecordsSkipped)
else begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
end;
end;
Result := FetchReadRecFields(FileHandle^.ReadInfo, FileHandle^.CursorID,
FileHandle^.NotXlateDOSString, ReadRecordBuf, ReadNullFlags, ErrMsg);
if Result <> errPhysDbNoError then Exit;
RecordRead := true;
Result := FetchIndexRecFields(FileHandle^.ReadInfo, FileHandle^.CursorID,
IndexRecordBuf, IndexNullFlags, ErrMsg);
except
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
finally
Buffer := PhysDbErrors[Result]; { this seems necessary for 32-bit }
end;
end;
function LookupMatchingRecurringRecVer15(
FileHandle: PPhysDbFileHandle;
LookupValueRecordBuf: PAnsiChar;
LookupValueNullFlags: PcrBooleanArray;
LookupValueType: TcrInt16u;
StartTopOfFile: TcrBoolean;
ReadRecordBuf: PByteArray;
ReadNullFlags: PcrBooleanArray;
IndexRecordBuf: PByteArray;
IndexNullFlags: PcrBooleanArray;
var RecordRead: TcrBoolean;
ErrMsg: PAnsiChar) : TPhysDbError;
function CompareLookupResult(
FileHandle : PPhysDbFileHandle;
LookupValueRecordBuf : PAnsiChar;
LookupValueNullFlags : PcrBooleanArray;
var Match : Boolean;
ErrMsg : PAnsiChar) : TPhysDbError;
var
FFError : TffResult;
I : integer;
FieldNo : integer;
LookupOffset : integer;
LookupValueLen : DWORD;
FieldLen : integer;
CompareLen : DWORD;
NFields : integer;
LookupNullFlag : Boolean;
NullField : Boolean;
begin
Result := errPhysDbNoError;
Match := False;
{ Ensure that fields are in system buffer }
FFError := ServerEngine.RecordGet(FileHandle^.CursorID,
ffltNoLock,
FileHandle^.ReadInfo^.PhysRecordBuf);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
{ Fetch fields from system record buffer }
NFields := FileHandle^.ReadInfo^.NFieldsInLookupValue;
LookupNullFlag := False;
if NFields > 0 then
LookupNullFlag := LookupValueNullFlags^[0];
for I := 0 to pred(NFields) do begin
FieldNo := FileHandle^.ReadInfo^.IndexDefnInfo^[I].FieldNo;
LookupOffset := FileHandle^.ReadInfo^.IndexDefnInfo^[I].OffsetInRecord;
FieldLen := FileHandle^.ReadInfo^.IndexDefnInfo^[I].FieldLength;
NullField := False;
TFFProxyCursor(FileHandle^.CursorID).Dictionary.GetRecordField(FieldNo,
FileHandle^.ReadInfo^.PhysRecordBuf,
NullField,
@FileHandle^.ReadInfo^.KeyBuf[LookupOffset]);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
if LookupNullFlag or NullField then Exit;
{ Compare each individual field to see if matches lookup value.
Only compare as much data as present if substring field }
CompareLen := FieldLen;
if FileHandle^.ReadInfo^.LookupValueLen < (LookupOffset + FieldLen) then
CompareLen := FileHandle^.ReadInfo^.LookupValueLen - LookupOffset;
if FileHandle^.ReadInfo^.IndexDefnInfo^[I].FieldType = ftStringField then begin
LookupValueLen := StrLen(@LookupValueRecordBuf[LookupOffset]);
if LookupValueLen < CompareLen then begin
if I = NFields - 1 then begin
if FileHandle^.ReadInfo^.LastLookupFieldIsSubstr then
CompareLen := LookupValueLen
else if LookupValueLen <> StrLen(@FileHandle^.ReadInfo^.KeyBuf[LookupOffset]) then
Exit
else
CompareLen := LookupValueLen;
end;
end;
end;
(* if FileHandle^.ReadInfo^.IndexCaseSensitive then begin*)
if (CompareLen = 0) or (FFCmpBytes(PffByteArray(@FileHandle^.ReadInfo^.KeyBuf[LookupOffset]),
PffByteArray(@LookupValueRecordBuf[LookupOffset]),
CompareLen) <> 0) then begin
Exit;
end;
end;
Match := True;
end;
var
FFError : TffResult;
Match : Boolean;
I : integer;
FieldN : integer;
NFields : integer;
LookupNullFlag : Boolean;
begin
AddToLog('LookupMatchingRecurringRecVer15');
AddToLogFmt(' FileName: [%s]', [FileHandle^.PathAndFileName]);
AddToLogFmt(' CursorID: [%d]', [FileHandle^.CursorID]);
Result := errPhysDbNoError;
try
{ Set up for search }
RecordRead := false;
with FileHandle^.ReadInfo^ do begin
if not StartTopOfFile then begin
AddToLog(' StartTopOfFile [False]');
if ValuesUnique and
not LastLookupFieldIsSubstr and
(NFieldsInLookupValue > NFieldsInIndexDefn) then
Exit;
{ See if next record also matches }
FFError := ServerEngine.RecordGetNext(FileHandle^.CursorID,
ffltNoLock,
nil);
AddToLogFmt(' RecordGetNext Result [%d]', [FFError]);
if FFError <> DBIERR_NONE then
Exit;
Result := CompareLookupResult(FileHandle,
LookupValueRecordBuf,
LookupValueNullFlags,
Match,
ErrMsg);
AddToLogFmt(' Match Result [%s]', [BoolToStr(Match)]); {!!.12}
if (Result <> errPhysDbNoError) or not Match then Exit;
end else begin
AddToLog(' StartTopOfFile [True]');
{ Clear all the fields in index }
for FieldN := 0 to pred(NFieldsInIndexDefn) do begin
TFFProxyCursor(FileHandle^.CursorID).Dictionary.SetRecordField(
IndexDefnInfo^[FieldN].FieldNo,
PhysRecordBuf,
nil);
end;
{ Copy fields (without translating) from lookup value buffer to
system record buffer }
NFields := NFieldsInLookupValue;
LookupNullFlag := False;
if NFields > 0 then
LookupNullFlag := LookupValueNullFlags^[0];
FillChar(PhysRecordBuf^, NBytesInPhysRecord, #0);
for I := 0 to pred(NFields) do begin
if not LookupNullFlag then begin
{ Copy index record field into system record buffer }
with IndexDefnInfo^[I] do
TFFProxyCursor(FileHandle^.CursorID).Dictionary.SetRecordField(
FieldNo,
PhysRecordBuf,
@LookupValueRecordBuf[OffsetInRecord]);
end;
end;
with TFFProxyCursor(FileHandle^.CursorID) do
Dictionary.ExtractKey(IndexID, PhysRecordBuf, @KeyBuf);
FFError := ServerEngine.CursorSetToKey(FileHandle^.CursorID,
skaEqual,
True,
NFieldsInLookupValue,
LastLookupFieldLen,
@KeyBuf);
AddToLogFmt(' CursorSetToKey Result [%d]', [FFError]);
{ Test if exact lookup succeeeded }
if (FFError = DBIERR_EOF) or
(FFError = DBIERR_OUTOFRANGE) or
(FFError = DBIERR_RECNOTFOUND) or
(FFError = DBIERR_RECDELETED) then begin
Result := errPhysDbNoError;
Exit;
end
else if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
{ read in the current record }
FFError := ServerEngine.RecordGetNext(FileHandle^.CursorID,
ffltNoLock,
FileHandle^.ReadInfo^.PhysRecordBuf);
AddToLogFmt(' RecordGetNext Result here [%d]', [FFError]);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
end;
Result := FetchReadRecFields(FileHandle^.ReadInfo,
FileHandle^.CursorID,
FileHandle^.NotXlateDOSString,
ReadRecordBuf,
ReadNullFlags,
ErrMsg);
if Result <> errPhysDbNoError then Exit;
RecordRead := true;
Result := FetchIndexRecFields(FileHandle^.ReadInfo,
FileHandle^.CursorID,
IndexRecordBuf,
IndexNullFlags,
ErrMsg);
end;
except
on EOutOfMemory do begin
Result := errPhysDbNotEnoughMemory;
StrPCopy(ErrMsg, '');
end;
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
{ ------------------------- Memo Fields ---------------------------- }
function FetchMemoField(
MemoFieldRecordBuf: PAnsiChar;
var MemoField: PAnsiChar;
ErrMsg: PAnsiChar) : TPhysDbError;
begin
AddToLog('FetchMemoField');
MemoField := nil;
Result := errPhysDbNoError;
AddResultToLog(Result);
end;
function FreeMemoField(
var MemoField: PAnsiChar;
ErrMsg: PAnsiChar) : TPhysDbError;
begin
AddToLog('FreeMemoField');
FFStrDispose(MemoField);
MemoField := nil;
Result := errPhysDbNoError;
AddResultToLog(Result);
end;
function FetchPersistentMemoField(FileHandle : PPhysDbFileHandle;
MemoFieldRecordBuf : PAnsiChar;
var MemoField : PAnsiChar;
ErrMsg : PAnsiChar) : TPhysDbError;
var
FFError : TffResult;
NativeType : TcrInt16u;
FieldN : integer;
FieldNo : integer;
ValueType : TFieldValueType;
CmpResult : Integer;
BlobSize : TcrInt32u;
NBytesReturned : TffWord32;
BlobHandle : THandle;
Handle : THandle;
BlobFieldPtr : PByteArray;
FinalBlobFieldPtr : PByteArray;
Size : TcrInt32u;
SavedBlobSize : TcrInt32u;
FirstTime : Boolean;
Offset : TcrInt32u;
NBytesCopied : TcrInt32u;
StartPos : TcrInt32u;
BookmarkSize : Integer;
IsNull : Boolean;
aBlobNr : TffInt64;
TempI64 : TffInt64;
BookmarkBuf : Pointer;
begin
AddToLog('FetchPersistentMemoField');
AddBlockToLog(' Memo Data', MemoFieldRecordBuf, 12);
Result := errPhysDbNoError;
MemoField := nil;
try
try
{ Restore the field info from brahma buffer }
FieldNo := TcrInt16s(MemoFieldRecordBuf^);
ValueType := ftPersistentMemoField;
NativeType := 0;
with FileHandle^.ReadInfo^ do
for FieldN := 0 to pred(NFieldsInReadRecord) do begin
if FieldInfo^[FieldN].FieldNo = FieldNo then begin
NativeType := FieldInfo^[FieldN].NativeFieldType;
if FieldInfo^[FieldN].FieldType = ftBlobField then
ValueType := ftBlobField;
end;
end;
{ Get the current bookmark }
FFError := ServerEngine.CursorGetBookmarkSize(FileHandle^.CursorID, BookmarkSize);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
FFGetMem(BookmarkBuf, BookmarkSize + 1);
try
FFError := ServerEngine.CursorGetBookmark(FileHandle^.CursorID,
BookmarkBuf);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
ServerEngine.CursorCompareBookmarks(FileHandle^.CursorID,
BookmarkBuf,
@MemoFieldRecordBuf[SizeOf(TcrInt16u)],
CmpResult);
finally
FFFreeMem(BookmarkBuf, BookmarkSize+1);
end;
{ If it is not the current position, reposition to the old position }
if CmpResult <> 0 then begin
FFError := ServerEngine.CursorSetToBookmark(FileHandle^.CursorID,
@MemoFieldRecordBuf[SizeOf(TcrInt16u)]);
if FFError = DBIERR_NONE then
FFError := ServerEngine.RecordGet(FileHandle^.CursorID,
ffltNoLock,
FileHandle^.ReadInfo^.PhysRecordBuf);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
end;
TempI64.iLow := 0;
TempI64.iHigh := 0;
TFFProxyCursor(FileHandle^.CursorID).Dictionary.GetRecordField(
FieldNo, FileHandle^.ReadInfo^.PhysRecordBuf, IsNull, @aBLOBNr);
if (not IsNull) and (ffCmpI64(aBLOBNr, TempI64) = 0) then
FFError := DBIERR_INVALIDBLOBHANDLE
else
FFError := DBIERR_NONE;
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
if not IsNull then begin {!!.02}
try
FFError := ServerEngine.BLOBGetLength(FileHandle^.CursorID,
aBlobNr,
BlobSize);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
if BlobSize = 0 then
Exit;
if ValueType = ftPersistentMemoField then begin
{Handle only 64K memos for now }
BlobSize := BlobSize;
Handle := GlobalAlloc(GHND or GMEM_SHARE, BlobSize);
if Handle = 0 then
raise EOutOfMemory.Create('');
try
BlobHandle := GlobalAlloc(GHND or GMEM_SHARE, BlobSize + 1);
BlobFieldPtr := GlobalLock(Handle);
try
if Assigned(BlobFieldPtr) then begin
FinalBlobFieldPtr := GlobalLock(BlobHandle);
try
FFError := ServerEngine.BLOBRead(FileHandle^.CursorID,
aBlobNr,
0,
BlobSize,
BlobFieldPtr^,
NBytesReturned);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
if NativeType = fldstFMTMEMO then begin
if BlobSize > 44 then begin
if StrLComp(PAnsiChar(BlobFieldPtr), #1#0#0#0#$C7#0#0#0, 8) = 0 then begin
Move(BlobFieldPtr^[8], FinalBlobFieldPtr^, BlobSize - 8);
FinalBlobFieldPtr[NBytesReturned - 8] := $0;
end else begin
Move(BlobFieldPtr^[44], FinalBlobFieldPtr^, BlobSize - 44);
FinalBlobFieldPtr[NBytesReturned - 44] := $0;
end;
end else begin
Move(BlobFieldPtr^, FinalBlobFieldPtr^, BlobSize);
FinalBlobFieldPtr[NBytesReturned] := $0;
end;
end else begin
Move(BlobFieldPtr^, FinalBlobFieldPtr^, BlobSize);
FinalBlobFieldPtr[NBytesReturned] := $0;
end;
if not FileHandle^.NotXlateDOSMemo then
OemToAnsi(PAnsiChar(FinalBlobFieldPtr), PAnsiChar(FinalBlobFieldPtr));
MemoField := PAnsiChar(FinalBlobFieldPtr);
finally
GlobalUnlock(BlobHandle);
end;
end;
finally
GlobalUnlock(Handle)
end;
finally
GlobalFree(Handle);
end;
end else begin
{ Nonmemo BLOB, may be a bitmap }
Handle := GlobalAlloc(GHND or GMEM_SHARE, BlobSize);
if Handle = 0 then
raise EOutOfMemory.Create('');
try
if NativeType = fldstBINARY then
{ No BLOB_INFO }
BlobHandle := GlobalAlloc(GHND or GMEM_SHARE, BlobSize - SizeOf(TBitmapFileHeader))
else
BlobHandle := GlobalAlloc(GHND or GMEM_SHARE, BlobSize - BLOB_INFO_SIZE - SizeOf(TBitmapFileHeader));
BlobFieldPtr := GlobalLock(Handle);
try
if Assigned(BlobFieldPtr) then begin
FinalBlobFieldPtr := GlobalLock(BlobHandle);
try
NBytesReturned := 0;
SavedBlobSize := BlobSize;
Size := FFMinDW(SavedBlobSize, $FFE0);
FirstTime := True;
Offset := 0;
NBytesCopied := 0;
while Size <> 0 do begin
FFError := ServerEngine.BLOBRead(FileHandle^.CursorID,
aBlobNr,
Offset,
Size,
BlobFieldPtr^,
NBytesReturned);
if FFError <> DBIERR_NONE then begin
Result := IDAPIError(FFError, ErrMsg);
Exit;
end;
Inc(Offset, NBytesReturned);
Dec(SavedBlobSize, NBytesReturned);
StartPos := 0;
if FirstTime then begin
if NativeType <> fldstBINARY then
Inc(StartPos, BLOB_INFO_SIZE);
{ If it is not a bitmap, return nil }
if Copy(StrPas(PAnsiChar(@BlobFieldPtr^[StartPos])), 1 ,2) <> 'BM' then
Exit;
Inc(StartPos, SizeOf(TBitmapFileHeader));
end;
{ Copy the bitmap data of size FFE0 or less depending on the size
of whole bitmap }
Move(BlobFieldPtr^[StartPos], FinalBlobFieldPtr^[NBytesCopied], Size - StartPos);
Inc(NBytesCopied, Size - StartPos);
{ The size of data to be got }
Size := FFMinDW(SavedBlobSize, $FFE0);
FirstTime := False
end;
finally
GlobalUnlock(BlobHandle);
end;
end;
finally
GlobalUnlock(Handle);
end;
finally
GlobalFree(Handle);
end;
{ Pass back the handle to the bitmap to Crystal. Allegedly, Crystal
will handle freeing it }
MemoField := PAnsiChar(BlobHandle);
end;
finally
ServerEngine.BLOBFree(FileHandle^.CursorID,
aBlobNr,
True);
end;
end; {!!.02}
except
on EOutOfMemory do begin
Result := errPhysDbNotEnoughMemory;
StrPCopy(ErrMsg, '');
end;
on E: Exception do begin
if Result = errPhysDbNoError then
Result := errPhysDbErrMsgReturned;
StrPCopy(ErrMsg, E.Message);
end;
end;
finally
StrPCopy(DebugBuff, PhysDbErrors[Result]); { this seems necessary for 32-bit }
end;
if (Result = errPhysDbErrMsgReturned) then
AddToLogFmt(' ErrMsg: [%s]', [ErrMsg]);
AddResultToLog(Result);
end;
function FreePersistentMemoField(
FileHandle: PPhysDbFileHandle;
var MemoField: PAnsiChar;
ErrMsg: PAnsiChar) : TPhysDbError;
begin
AddToLog('FreePersistentMemoField');
GlobalFree(THandle(MemoField));
MemoField := nil;
Result := errPhysDbNoError;
AddResultToLog(Result);
end;
{ --------------------- Multi-User Access -------------------------- }
function UseRecordLocking(FileHandle : PPhysDbFileHandle;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('UseRecordLocking');
Result := errPhysDbNotImplemented;
AddResultToLog(Result);
end;
function UseFileLocking(FileHandle : PPhysDbFileHandle;
ErrMsg : PAnsiChar) : TPhysDbError;
begin
AddToLog('UseFileLocking');
Result := errPhysDbNotImplemented;
AddResultToLog(Result);
end;
{===Debug logging====================================================}
{Begin !!.12}
procedure StartLog;
begin
{$IFDEF Debug}
Log := TffEventLog.Create(nil);
Log.FileName := FFMakeFullFileName(FFExtractPath(FFGetExeName), 'FFDRIVER.LOG');
Log.Enabled := True;
Log.WriteString('FF server log started');
{$ELSE}
Log := nil;
{$ENDIF}
end;
{--------}
procedure EndLog;
begin
if Log <> nil then
Log.Free;
end;
{--------}
procedure AddToLog(const S : string);
begin
if Log <> nil then
Log.WriteString(S);
end;
{--------}
procedure AddToLogFmt(const S : string; args : array of const);
begin
if Log <> nil then
Log.WriteStringFmt(S, args);
end;
{--------}
procedure AddBlockToLog(const S : string; Buf : pointer; BufLen : TffMemSize);
begin
if Log <> nil then
Log.WriteBlock(S, Buf, BufLen);
end;
{--------}
procedure AddResultToLog(aResult : TPhysDbError);
{$IFDEF Debug}
var
S : string;
{$ENDIF}
begin
{$IFDEF Debug}
case aResult of
errPhysDbNoError : S := 'errPhysDbNoError';
errPhysDbErrMsgReturned : S := 'errPhysDbErrMsgReturned';
errPhysDbNotEnoughMemory : S := 'errPhysDbNotEnoughMemory';
errPhysDbFileDoesNotExist : S := 'errPhysDbFileDoesNotExist';
errPhysDbFilePermissionError : S := 'errPhysDbFilePermissionError';
errPhysDbFileIntegrityError : S := 'errPhysDbFileIntegrityError';
errPhysDbUserCancelOperation : S := 'errPhysDbUserCancelOperation';
errPhysDbProgrammingError : S := 'errPhysDbProgrammingError';
errPhysDbNotImplemented : S := 'errPhysDbNotImplemented';
errPhysDbSQLServerError : S := 'errPhysDbSQLServerError';
errPhysDbIncorrectPassword : S := 'errPhysDbIncorrectPassword';
errPhysDbOpenSessionError : S := 'errPhysDbOpenSessionError';
errPhysDbLogOnServerError : S := 'errPhysDbLogOnServerError';
errPhysDbErrorHandledByDBDLL : S := 'errPhysDbErrorHandledByDBDLL';
errPhysDbStopProceeding : S := 'errPhysDbStopProceeding';
else
S := '***Unknown***';
end;{case}
Log.WriteStringFmt(' Result: %s [%d]', [S, ord(aResult)]);
{$ENDIF}
end;
{End !!.12}
{====================================================================}
procedure UnitEnterProc;
begin
TaskList := TTaskList.Create;
StartLog;
end;
procedure UnitExitProc;
begin
EndLog;
TaskList.Free;
end;
initialization
UnitEnterProc;
finalization
UnitExitProc;
end.