abstractdata.pas
unit abstractdata;
{ Implementation of item data manipulation classes.
Two conditional defines are used:
ACTIVE_X - turn on in item business logic module
LOG_ON - can be used for debug purposes. Allow to log
benchmarking information of apply procedures.
Developed by Alex Feigin. }
interface
uses
Windows, Classes, DBTables, Db, SysUtils, StrUtils, Databkr, ComObj, itemdm, Dialogs;
type
{ Class TItemSlice supports manipulation with item data slice }
TItemSlice = class(TPersistent)
private
{ Physical name of data field }
FFieldName:String;
{ Field value}
FValue:Variant;
public
constructor Create(EFieldName:string;EValue:Variant); virtual;
destructor Destroy; override;
property FieldName : string read FFieldName write FFieldName;
property Value : Variant read FValue write FValue;
end;
{ Class TItemData supports manipulations with item slices list }
TItemData = class(TPersistent)
private
{ Current index value}
currentIndex:integer;
{ TStringList, which includes references to item data slices }
ItemData:TStringList;
{ Reference to item logic RDM }
DMItemLog:TRemoteDataModule;
{ Function returns item slice, correspondend to the index value }
function GetItemSlice(I:Integer):TItemSlice;
{ Function process changes used to apply item slice to the appropriated
table. Available in item business logic module only. }
{$IFDEF ACTIVE_X}
function ProcessChanges(DataSet:TTable;ItemSlice:TItemSlice;var ParentKeySlice:TItemSlice):HResult;
{$ENDIF}
public
{$IFDEF LOG_ON}
ItemApplyTime:TDateTime;
DefaultApplyTime:TDateTime;
ProcessRecordTime:TDateTime;
checkIndexTime:TDateTime;
indexForFieldTime:TDateTime;
ProtocolApplyTime:TDateTime;
{$ENDIF}
constructor Create(DMItemLog:TRemoteDataModule;isSorted:Boolean=true);
destructor Destroy; override;
{ Function ApplyToDataSet used to apply item slices to the item database.
Available in item business logic module only. }
{$IFDEF ACTIVE_X}
function ApplyToDataSet(useDefaultLabel:Boolean;useDefaultCompItem:Boolean):HResult;
{$ENDIF}
{ Function, which adds new data slice to item data buffer}
function AddDataSlice(fieldName:string;Value:Variant):HResult;
{ Function, which reads first data slice from the item data buffer}
function ReadFirstDataSlice(var fieldName:string;var Value:Variant):HResult;
{ Function, which reads next data slice from the item data buffer}
function ReadNextDataSlice(var fieldName:string;var Value:Variant):HResult;
{ Function, InitFormDataSet was designed for retriving of item data buffer from the
item database. Not implemented in the current implementation, because there is no
users.}
function InitFromDataSet(Moniker:string):HResult;
{ Function makes search for item slice by field name }
function FindItemSlice(fieldName:String):TItemSlice;
{ Function reads item slice from item data buffer by field name }
function ReadSliceByName(fieldName:String):OleVariant;
{ Function returns field value, correspondend to the index value }
function GetItemValue(I:Integer) : Variant;
{ Procedure, which clears item data buffer }
procedure ClearItemData;
end;
implementation
{$IFDEF ACTIVE_X}
uses
dmLogItem;
{$ENDIF}
{ TItemSlice object constructor }
constructor TItemSlice.Create(EFieldName:string;EValue:Variant);
begin
inherited Create;
FieldName:=EFieldName;
Value:=EValue;
end;
{ TItemSlice object destructor }
destructor TItemSlice.Destroy;
begin
inherited Destroy;
end;
{ TItemData object constructor }
constructor TItemData.Create(DMItemLog:TRemoteDataModule;isSorted:Boolean);
begin
inherited Create;
{ Creation of string list, which will store references to item slices }
ItemData:=TStringList.Create;
ItemData.Sorted:=isSorted;
currentIndex:=0;
self.DMItemLog:=DMItemLog;
{$IFDEF LOG_ON}
ItemApplyTime:=0;
DefaultApplyTime:=0;
ProtocolApplyTime:=0;
ProcessRecordTime:=0;
checkIndexTime:=0;
indexForFieldTime:=0;
{$ENDIF}
end;
{ Function, which retrives item value by index from item data buffer
Input value : index
Returns : field value in case of success, otherwise returns varEmpty
}
function TItemData.GetItemValue(I:Integer) : Variant;
var
ItemSlice:TItemSlice;
begin
ItemSlice:=GetItemSlice(I);
if(ItemSlice<>nil) then
Result:=ItemSlice.Value;
end;
{ TItemData class destructor }
destructor TItemData.Destroy;
var
I:Integer;
begin
{ Destroing of item slice objects }
for I:=0 to ItemData.Count-1 do
ItemData.Objects[I].Destroy;
{ Destroying of item data buffer }
ItemData.Free;
inherited Destroy;
end;
{ Procedure to add new data slice to item data buffer.
Attention attempt to add value equal to varEmpty or varNull produce
an error.
Input values:
fieldName - physical field name
Value - field value
Returns NOERROR, if operation is successful. E_INVALIDARG in case of error. }
function TItemData.AddDataSlice(fieldName:string;Value:Variant):HResult;
begin
if(VarIsNull(Value) OR
VarIsEmpty(Value)) then
Result:=E_INVALIDARG
else
begin
ItemData.AddObject(fieldName,TItemSlice.Create(fieldName,Value));
Result:=NOERROR;
end;
end;
{ Function, which retrieves first data slice from item data buffer
Output values:
fieldName - physical field name
Value - field value
Returns NOERROR, if operation is successful, E_OUTOFMEMORY if item data
buffer is empty }
function TItemData.ReadFirstDataSlice(var fieldName:string;var Value:Variant):HResult;
var
ItemSlice:TItemSlice;
begin
currentIndex:=0;
if(currentIndexthen
begin
ItemSlice:=(ItemData.Objects[currentIndex] as TItemSlice);
fieldName:=ItemSlice.fieldName;
Value:=ItemSlice.Value;
Result:=NOERROR;
end
else
Result:=E_OUTOFMEMORY;
end;
{ Function, which retrieves next data slice from item data buffer
Output values:
fieldName - physical field name
Value - field value
Returns NOERROR, if operation is successful, E_OUTOFMEMORY if end of item data
buffer is reached }
function TItemData.ReadNextDataSlice(var fieldName:string;var Value:Variant):HResult;
var
ItemSlice:TItemSlice;
begin
Inc(currentIndex);
if(currentIndexthen
begin
ItemSlice:=(ItemData.Objects[currentIndex] as TItemSlice);
fieldName:=ItemSlice.fieldName;
Value:=ItemSlice.Value;
Result:=NOERROR;
end
else
Result:=E_OUTOFMEMORY;
end;
{ Function return item slice by index value.
Input value : I - index in item data buffer
Result : item slice object with index I, nil if appropriated
item slice is absent in item data buffer }
function TItemData.GetItemSlice(I:Integer):TItemSlice;
begin
if (I>=0) and (Ithen
Result:=TItemSlice(ItemData.Objects[I])
else
Result:=nil;
end;
{ Procedure, which clears item data buffer }
procedure TItemData.ClearItemData;
var
I:Integer;
begin
for I:=0 to ItemData.Count-1 do
ItemData.Objects[I].Destroy;
ItemData.Clear;
currentIndex:=0;
end;
{ Making search of slice from item data buffer by field name.
Input values : field name
Ouput values : item slice in case of success, nil if field
is not found }
function TItemData.FindItemSlice(fieldName:String):TItemSlice;
var
I:Integer;
isFound:Boolean;
begin
if(ItemData.Sorted) then
isFound:=ItemData.Find(fieldName,I)
else
begin
I:=ItemData.IndexOf(fieldName);
isFound:=(I<>-1);
end;
if(isFound) then
Result:=GetItemSlice(I)
else
Result:=nil;
end;
{ Reading of slice from item data buffer by field name.
Input values : field name
Ouput values : field value in case of success, varEmpty if field is not
found }
function TItemData.ReadSliceByName(fieldName:String):OleVariant;
var
ItemSlice:TItemSlice;
begin
ItemSlice:=FindItemSlice(fieldName);
if(ItemSlice<>nil) then
Result:=ItemSlice.Value;
end;
{ Not implemented }
function TItemData.InitFromDataSet(Moniker:string):HResult;
begin
Result:=NOERROR;
end;
{ Function process changes is available only in item business logic module.
Used to aaply item slice to the appropriated dataset.
Input values : DataSet - target table;
ItemsSlice - item data slice to apply;
Output value : ParenKeySlice - item slice, which stores data, correspondend to
item moniker value ('NUMBER');
Result : NOERROR - in case of success;
OLE_E_CANT_BINDTOSOURCE - current item slice includes one of the
index fields of the dataset, but concatenation
of parent key name and current field name does't
correspond to the availbale for the dataset index
E_PENDING - current item slice includes one of the
index fields of the dataset it is not complete index
and parent key is not initialized
OLE_E_STATIC - attempt to apply item splice, if record was not located
before (wrong order of the item slices. Right order must include
item slices with key values previous to data values.
E_IVALIDARG - attempt to add item slice, where field name is not found in the
current dataset.
}
{$IFDEF ACTIVE_X}
function TItemData.ProcessChanges(DataSet:TTable;ItemSlice:TItemSlice;var ParentKeySlice:TItemSlice):HResult;
var
IndexFields,currentIndexFieldName,detailIndex,boolStr:String;
currentPos:Integer;
isOccured:Boolean;
OleRes,CurrentValue:Variant;
CurrentField:TField;
{$IFDEF LOG_ON}
StartTime:TDateTime;
{$ENDIF}
begin
{ Comparison of current field and primary index fields }
{$IFDEF LOG_ON}
StartTime:=SysUtils.Now;
{$ENDIF}
currentPos:=1;
isOccured:=false;
if(DataSet.IndexDefs.Updated=false) then
DataSet.IndexDefs.Update;
if(DataSet.IndexDefs.Count=0) then
begin
Result:=OLE_E_CANT_BINDTOSOURCE;
Exit;
end;
{ Retrieving of primary index field names }
IndexFields:=DataSet.IndexDefs.Items[0].Fields;
while(currentPosdo
begin
currentIndexFieldName:=ExtractSubstr(IndexFields,currentPos,[';']);
{ Recognition of key fields in current item data slice }
if(currentIndexFieldName=ItemSlice.fieldName) then
begin
isOccured:=true;
break;
end;
end;
if(isOccured) then { There is a new key field value }
begin
{ Attempt to found index correspondend to found key field name in the current dataset }
if(DataSet.IndexDefs.GetIndexForFields(ItemSlice.fieldName,false)<>nil) then { Parent key is found}
begin
{ Parent key is found ( parent key here means key of item.db table }
parentKeySlice:=ItemSlice;
{ Attempt to locate record, correspondend to the parent key }
if(DataSet.Locate(parentKeySlice.fieldName,parentKeySlice.Value,[])) then
DataSet.Edit { Record is located. Will be modified }
else
DataSet.Insert; { Record is not located. New record addition.}
end
else
begin
{ Detail key recognition }
if(parentKeySlice<>nil) then
begin
DetailIndex:=parentKeySlice.FieldName+';'+ItemSlice.FieldName;
{ Attempt to found index correspondend to the detail index string in the current dataset }
if(DataSet.IndexDefs.GetIndexForFields(DetailIndex,false)<>nil) then
begin
{ Index is found. Attempt to locate record, correspondend to the detail key. }
if(DataSet.Locate(DetailIndex,VarArrayOf([parentKeySlice.Value,ItemSlice.Value]),[])) then
DataSet.Edit { Record is located. Will be modified }
else
begin
DataSet.Insert; { Record is not located. New record addition.}
{ Procedure RefreshNames produces automatic addition of new price, extended field,
compare item names to the system tables}
(DMItemLog as TDMItemLogic).ISysTbl.RefreshNames(WideString(ItemSlice.FieldName),ItemSlice.Value);
end;
end
else
begin
Result:=OLE_E_CANT_BINDTOSOURCE;
Exit;
end;
end
else
begin
Result:=E_PENDING;
Exit;
end;
end;
end;
{ Writing of information }
if (not (DataSet.State in [dsInsert,dsEdit])) then
begin
{ Dataset is not Edit or Insert mode and not ready to the writing of information }
Result:=OLE_E_STATIC;
Exit;
end
else
begin
{ Current field, correspondend to the item slice recognition }
CurrentField:=DataSet.FindField(ItemSlice.FieldName);
if(CurrentField<>nil) then
begin
{ If field is boolean special typecast procedure is performed. Values
'0', 'F' are converted to "false", the rest - to "true". }
if(CurrentField.DataType=ftBoolean) then
begin
try
CurrentValue:=Boolean(ItemSlice.Value);
except
boolStr:=VarToStr(ItemSlice.Value);
if(boolStr[1]='0') or (AnsiUppercase(boolStr[1])='F') then
CurrentValue:=false
else
CurrentValue:=true;
end;
end
else
CurrentValue:=ItemSlice.Value;
if(DataSet.State=dsEdit) then
begin
{ If field exists and don't overwrite flag is set in item defintion
table - new value is not applied }
OleRes:=(DMItemLog as TDMItemLogic).ISysTbl.IsKeepExistingInfo(WideString(ItemSlice.FieldName));
if(Boolean(OleRes)) then
begin
Result:=NOERROR;
Exit;
end;
end;
try
{ Apply of item slice value to the dataset field }
if((CurrentField.Value<>CurrentValue) and
(CurrentField.CanModify)) then
CurrentField.AsVariant:=CurrentValue;
except
CurrentField.AsVariant:=varNull;
end;
Result:=NOERROR;
end
else
Result:=E_INVALIDARG;
end;
{$IFDEF LOG_ON}
ProcessRecordTime:=ProcessRecordTime+StartTime-SysUtils.Now;
{$ENDIF}
end;
{ Procedure, which applies filled item data buffer
to item database.
Input values : useDefaultLabel switch. If it's "true" - default label
will be automatically added to each new item;
useDefaultCompItem switch. If it's "true" - default compare
item will be automatically added to each new item
Returns : NOERROR, if apply was finished successfully }
function TItemData.ApplyToDataSet(useDefaultLabel:Boolean;useDefaultCompItem:Boolean):HResult;
var
I,J:Integer;
Res:HResult;
currentSlice,ParentKeySlice:TItemSlice;
isOpenedHere:Boolean;
{$IFDEF LOG_ON}
StartTime:TDateTime;
{$ENDIF}
begin
ParentKeySlice:=nil;
Res:=NOERROR;
with TdmItemDBT((DMItemLog as TDMItemLogic).dmItemDbT) do
begin
{ If item data tables is not opened we should open it }
isOpenedHere:=NOT MasterTable.Active;
if(isOpenedHere) then
MasterTable.Open;
try
try
{ Item data buffer loop }
for I:=0 to ItemData.Count-1 do
begin
currentSlice:=GetItemSlice(I);
{ Attempt to find field, correspondend to the current item slice name
in tbItem }
if(MasterTable.FindField(currentSlice.fieldName)<>nil) then
Res:=ProcessChanges(MasterTable,currentSlice,ParentKeySlice) { Process changes if found }
else
begin
{ Attempt to find field, correspondend to the current item slice name
in detail tables }
for J:=0 to DETAIL_LINK_COUNT do
begin
if(detailTables[J].FindField(currentSlice.fieldName)<>nil) then
begin
Res:=ProcessChanges(detailTables[J],currentSlice,ParentKeySlice); { Process changes if found }
case Res of
NOERROR: Break;
OLE_E_STATIC: Continue; {It's normal situation. Current detail table doesn't includes field,
correspondend to the current item slice. We have to go to the next
detail table }
else
Abort;
end;
end;
end;
end;
if(Res<>NOERROR) then
Abort;
end;
{$IFDEF LOG_ON}
StartTime:=SysUtils.Now;
{$ENDIF}
{ Registration of virtual updates for update flag UF_ALWAYS }
if { (not CheckPendingUpdates) and} (updateFlag=UF_ALWAYS) and (ParentKeySlice<>nil) then
(DMItemLog as TDMItemLogic).RegisterUpdatedItem(WideString(ParentKeySlice.FieldName),ParentKeySlice.Value,userID,updateFlag,
ParentKeySlice.Value,ParentKeySlice.Value);
{ Apply of pending changes to the item database }
if(MasterTable.State in [dsInsert,dsEdit]) then
MasterTable.Post
else
CheckDetailBrowseMode;
{$IFDEF LOG_ON}
ItemApplyTime:=ItemApplyTime+StartTime-SysUtils.Now;
StartTime:=SysUtils.Now;
{$ENDIF}
if(useDefaultLabel) then { Default label addition }
begin
with tbItem2Lbl do
begin
Insert;
try
Post;
except
On E:Exception do
begin
Cancel;
end;
end;
end;
end;
if(useDefaultCompItem) then { Default compare item addition }
begin
with tbCompare do
begin
Insert;
try
Post;
except
On E:Exception do
begin
Cancel;
end;
end;
end;
end;
{$IFDEF LOG_ON}
DefaultApplyTime:=DefaultApplyTime+StartTime-SysUtils.Now;
{$ENDIF}
except
On E:Exception do
begin
MasterTable.Cancel;
if(Succeeded(Res)) then
raise Exception.Create(E.message)
else
raise Exception.Create(SysErrorMessage(Res));
end;
end;
finally
if(isOpenedHere) then
MasterTable.Close;
{ Apply of cached changes to the protocol }
{$IFDEF LOG_ON}
StartTime:=SysUtils.Now;
{$ENDIF}
(DMItemLog as TDMItemLogic).ProtocolApplyChanges;
{$IFDEF LOG_ON}
ProtocolApplyTime:=ProtocolApplyTime+StartTime-SysUtils.Now;
{$ENDIF}
Result:=Res;
end;
end;
end;
{$ENDIF}
end.