////////////////////////////////////////////////// // Copyright © 1998-2007 Core Lab. All right reserved. // Virtual table // Created: 11.12.98 ////////////////////////////////////////////////// {$IFNDEF CLR} {$I Dac.inc} unit VirtualTable; {$ENDIF} interface uses {$IFDEF VER6P} StrUtils, Variants, {$ENDIF} {$IFDEF LINUX} Libc, Classes, SysUtils, DB, MemDS, MemData; {$ELSE} Windows, Classes, SysUtils, DB, MemDS, MemData; {$ENDIF} type TCRFileFormat = (ffVTD, ffXML); TVirtualTableOption = (voPersistentData, voStored); TVirtualTableOptions = set of TVirtualTableOption; TVirtualTable = class(TMemDataSet) private FOptions: TVirtualTableOptions; FStreamedActive: boolean; FAvoidRefreshData: boolean; FAvoidReload: integer; FRecordDataStream: TMemoryStream; procedure ReadBinaryData(Stream: TStream); procedure WriteBinaryData(Stream: TStream); function IsFieldDefsStored: boolean; function GetFieldDefs: TFieldDefs; procedure SetFieldDefs(Value: TFieldDefs); protected FFieldDefsByField: boolean; procedure Loaded; override; procedure CreateIRecordSet; override; procedure OpenCursor(InfoQuery: boolean); override; procedure InternalOpen; override; procedure InternalClose; override; function IsCursorOpen: boolean; override; procedure CreateFieldDefs; override; procedure DefChanged(Sender: TObject); override; procedure Reload; {$IFDEF CLR} procedure DataEvent(Event: TDataEvent; Info: TObject); override; {$ELSE} procedure DataEvent(Event: TDataEvent; Info: longint); override; {$ENDIF} procedure DefineProperties(Filer: TFiler); override; procedure AssignDataSet(Source: TDataSet); procedure SetActive(Value:boolean); override; public constructor Create(Owner: TComponent); override; destructor Destroy; override; function IsSequenced: boolean; override; procedure AddField(Name: string; FieldType: TFieldType; Size: integer = 0; Required: boolean = False); procedure DeleteField(Name: string); procedure DeleteFields; procedure Clear; { Stream/File } procedure LoadFromStream(Stream: TStream; LoadFields: boolean = True); procedure SaveToStream(Stream: TStream; StoreFields: boolean = True); procedure LoadFromFile(const FileName: string); procedure SaveToFile(const FileName: string); procedure Assign(Source: TPersistent); override; published property Options: TVirtualTableOptions read FOptions write FOptions default [voPersistentData, voStored]; property Active; property AutoCalcFields; property Filtered; property Filter; property FilterOptions; property IndexFieldNames; property BeforeOpen; property AfterOpen; property BeforeClose; property AfterClose; property BeforeInsert; property AfterInsert; property BeforeEdit; property AfterEdit; property BeforePost; property AfterPost; property BeforeCancel; property AfterCancel; property BeforeDelete; property AfterDelete; property BeforeScroll; property AfterScroll; property OnCalcFields; property OnDeleteError; property OnEditError; property OnFilterRecord; property OnNewRecord; property OnPostError; //property Fields stored False; property FieldDefs: TFieldDefs read GetFieldDefs write SetFieldDefs stored IsFieldDefsStored; end; var VTOldBehavior: boolean; implementation uses CRParser, DAConsts, MemUtils, {$IFDEF VER6P} FMTBcd, {$ENDIF} {$IFDEF CLR} System.XML, System.IO, System.Runtime.InteropServices, System.Text, DateUtils, RTLConsts; {$ELSE} CLRClasses, CRXml; {$ENDIF} const // Must be sync with 'case' in AddFieldDesc and 'case' in SaveToStream SupportFieldTypes = [ftString, ftWideString, ftSmallint, ftInteger, ftAutoInc, ftWord, ftBoolean, ftLargeint, ftFloat, ftCurrency, ftDate, ftTime, ftDateTime, ftBlob, ftMemo{$IFDEF VER10P}, ftWideMemo{$ENDIF}, ftGuid, ftBCD, {$IFDEF VER6P}ftFmtBcd,{$ENDIF} ftBytes, ftVarBytes, ftVariant]; SNotSupportFieldType = 'Field type is not supported by TVirtualTable. '#13 + 'Valid types is String, WideString, Smallint, Integer, Word, Boolean, Largeint, Float, Currency, Date, Time, DateTime, Blob, Memo, Guid, Bcd, ' + {$IFDEF VER6P} 'FmtBcd, ' + {$ENDIF} 'Bytes, VarBytes, Variant'; type TVirtualData = class (TMemData) protected Owner: TDataSet; procedure InternalOpen; override; procedure InternalInitFields; override; public constructor Create; end; { TVirtualData } constructor TVirtualData.Create; begin inherited; Owner := nil; end; procedure TVirtualData.InternalInitFields; procedure AddFieldDesc(const FieldName: string; const FieldType: TFieldType; const FieldSize: integer; const Precision: integer; const Scale: integer; const Required: boolean; const Fixed: boolean); var Field: TFieldDesc; begin Field := TFieldDesc.Create; try Field.FieldNo := FFields.Count + 1; Field.Name := FieldName; Field.DataType := GetDataType(FieldType); case FieldType of ftString: begin Field.Size := FieldSize + 1; Field.Length := FieldSize; end; ftWideString: begin Field.Size := (FieldSize + 1) * sizeof(WideChar); Field.Length := FieldSize; end; ftSmallint: if (Precision <= 4) and (Precision <> 0) then begin Field.DataType := dtInt8; Field.Size := sizeof(SmallInt); Field.Length := Precision; end else Field.Size := sizeof(SmallInt); ftInteger, ftAutoInc: Field.Size := sizeof(Integer); ftWord: Field.Size := sizeof(word); ftBoolean: Field.Size := sizeof(Wordbool); ftLargeint: if (Precision <= 10) and (Precision <> 0) then begin Field.DataType := dtUInt32; Field.Size := sizeof(Integer); Field.Length := Precision; end else Field.Size := sizeof(Largeint); ftFloat: begin Field.Size := sizeof(Double); Field.Length := Precision; end; ftCurrency: Field.Size := sizeof(Double); ftDate, ftTime, ftDateTime: begin Field.Size := sizeof(TDateTime); Field.Length := Precision; end; ftBlob, ftMemo{$IFDEF VER10P}, ftWideMemo{$ENDIF}: Field.Size := sizeof(Pointer); ftGuid: begin Field.Size := FieldSize + 1; Field.Length := FieldSize; end; ftBCD: begin Field.Size := sizeof(Currency); Field.Scale := Scale; Field.Length := Precision; end; {$IFDEF VER6P} ftFmtBcd: begin if Precision < SizeOfTBcd then Field.Size := SizeOfTBcd else Field.Size := Precision + 1{'.'} + 1 {#0}; // To right notation of large NUMERIC values Field.Scale := Scale; Field.Length := Precision; end; {$ENDIF} ftBytes: begin Field.Size := FieldSize; Field.Length := FieldSize; end; ftVarbytes: begin Field.Size := sizeof(word) + FieldSize; Field.Length := FieldSize; end; {$IFDEF VER5P} ftVariant: begin Field.Size := sizeof(TVariantObject); end; {$ENDIF} { ftBytes, ftVarBytes, ftAutoInc, ftBlob, ftMemo, ftGraphic, ftFmtMemo, ftParadoxOle, ftDBaseOle, ftTypedBinary, ftCursor, ftFixedChar, ftWideString, ftLargeint, ftADT, ftArray, ftReference, ftDataSet, ftOraBlob, ftOraClob, ftVariant, ftInterface, ftIDispatch, ftGuid, ftTimeStamp, ftFMTBcd, ftVariant } else DatabaseError(SNotSupportFieldType); end; Field.Required := Required; Field.Fixed := Fixed; FFields.Add(Field); except Field.Free; end; end; var i: integer; OwnerField: TField; OwnerFieldDef: TFieldDef; DataFieldCount: integer; begin inherited; DataFieldCount := 0; for i := 0 to Owner.FieldCount - 1 do if Owner.Fields[i].FieldKind = fkData then Inc(DataFieldCount); if not Owner.DefaultFields and ((DataFieldCount > Owner.FieldDefs.Count) or (TVirtualTable(Owner).FFieldDefsByField and not VTOldBehavior)) then // From fields for i := 0 to Owner.FieldCount - 1 do begin if Owner.Fields[i].FieldKind = fkData then begin OwnerField := Owner.Fields[i]; AddFieldDesc(OwnerField.FieldName, OwnerField.DataType, OwnerField.Size, 0, 0, False, False); end end else // From FieldDefs for i := 0 to Owner.FieldDefs.Count - 1 do begin OwnerFieldDef := Owner.FieldDefs[i]; AddFieldDesc(OwnerFieldDef.Name, OwnerFieldDef.DataType, OwnerFieldDef.Size, OwnerFieldDef.Precision, OwnerFieldDef.Size, faRequired in OwnerFieldDef.Attributes, faFixed in OwnerFieldDef.Attributes); end end; procedure TVirtualData.InternalOpen; begin InitFields; inherited; end; { TVirtualTable } constructor TVirtualTable.Create(Owner: TComponent); begin inherited; Data.EnableEmptyStrings := True; FOptions := [voPersistentData,voStored]; FStreamedActive := False; FRecordDataStream := TMemoryStream.Create; end; destructor TVirtualTable.Destroy; begin Data.Close; // Clear data FRecordDataStream.Free; inherited; end; procedure TVirtualTable.Loaded; begin inherited; try try FRecordDataStream.Seek(0, soFromBeginning); if FRecordDataStream.Size > 0 then LoadFromStream(FRecordDataStream, False); finally FRecordDataStream.Clear; end; if FStreamedActive then Active := True; except if csDesigning in ComponentState then InternalHandleException else raise; end; end; procedure TVirtualTable.CreateIRecordSet; begin FCreateCalcFieldDescs := False; SetIRecordSet(TVirtualData.Create); TVirtualData(Data).Owner := Self; end; procedure TVirtualTable.OpenCursor(InfoQuery: boolean); begin Inc(FAvoidReload); try inherited; finally Dec(FAvoidReload); end; end; procedure TVirtualTable.InternalOpen; begin if FAvoidReload = 0 then FFieldDefsByField := False; inherited; end; procedure TVirtualTable.InternalClose; begin Inc(FAvoidReload); try BindFields(False); if DefaultFields then DestroyFields; if not (voPersistentData in FOptions) then Data.Close else Data.SetToBegin; finally Dec(FAvoidReload); end; end; function TVirtualTable.IsCursorOpen: boolean; begin Result := inherited IsCursorOpen; end; procedure TVirtualTable.CreateFieldDefs; var DataFieldCount: integer; OldFieldDefsCount: integer; i: integer; begin OldFieldDefsCount := FieldDefs.Count; DataFieldCount := 0; for i := 0 to FieldCount - 1 do if Fields[i].FieldKind = fkData then Inc(DataFieldCount); if not DefaultFields and ((DataFieldCount > FieldDefs.Count) or FFieldDefsByField)then try // Used to prevent save/load table DefChanged Inc(FAvoidReload); inherited; if FFieldDefsByField then FFieldDefsByField := (DataFieldCount = FieldDefs.Count) else FFieldDefsByField := (OldFieldDefsCount = 0) and (FieldDefs.Count > 0); finally Dec(FAvoidReload); end; end; procedure TVirtualTable.Reload; var OldActive: boolean; Stream: TMemoryStream; begin if Data.RecordCount > 0 then begin OldActive := Active; Stream := TMemoryStream.Create; DisableControls; try SaveToStream(Stream, False); Close; Clear; finally LoadFromStream(Stream, False); Active := OldActive; Stream.Free; EnableControls; end; end else begin OldActive := Active; Close; Clear; Active := OldActive; end; end; procedure TVirtualTable.DefChanged(Sender: TObject); var FieldDef: TFieldDef; i: integer; begin if not FAvoidRefreshData then begin if Active then FFieldDefsByField := False; for i := 0 to TFieldDefs(Sender).Count - 1 do begin FieldDef := TFieldDefs(Sender)[i]; if FieldDef.DataType = ftUnknown then begin FAvoidRefreshData := True; FieldDef.DataType := ftString; FieldDef.Size := 20; FAvoidRefreshData := False; end; end; if FAvoidReload = 0 then Reload; end; end; {$IFDEF CLR} procedure TVirtualTable.DataEvent(Event: TDataEvent; Info: TObject); {$ELSE} procedure TVirtualTable.DataEvent(Event: TDataEvent; Info: longint); {$ENDIF} begin if FFieldDefsByField and (Event = deFieldListChange) and (FAvoidReload = 0) and not VTOldBehavior then begin Inc(FAvoidReload); try FieldDefs.Updated := False; if Data.Active {and (voPersistentData in FOptions)} then Reload; finally Dec(FAvoidReload); end; end; inherited DataEvent(Event, Info); end; procedure TVirtualTable.Assign(Source: TPersistent); var Stream: TMemoryStream; begin if Source is TVirtualTable then begin Stream := TMemoryStream.Create; try TVirtualTable(Source).SaveToStream(Stream); LoadFromStream(Stream); finally Stream.Free; end; FFieldDefsByField := TVirtualTable(Source).FFieldDefsByField; end else if Source is TDataSet then AssignDataSet(TDataSet(Source)) else inherited; end; procedure TVirtualTable.AssignDataSet(Source: TDataSet); procedure CreateFieldDefs(Fields: TFields; FieldDefs: TFieldDefs); var I: Integer; F: TField; FieldDef: TFieldDef; SourceFieldDef: TFieldDef; NewDataType: TFieldType; begin FieldDefs.BeginUpdate; try for I := 0 to Fields.Count - 1 do begin F := Fields[I]; SourceFieldDef := Source.FieldDefs.Find(F.FieldName); with F do begin case DataType of ftOraBlob: NewDataType := ftBlob; ftOraClob: NewDataType := ftMemo; else NewDataType := DataType; end; if (FieldKind = fkData) and (NewDataType in SupportFieldTypes) then begin FieldDef := FieldDefs.AddFieldDef; FieldDef.Name := FieldName; FieldDef.DataType := NewDataType; FieldDef.Size := Size; if Required then FieldDef.Attributes := [faRequired]; if ReadOnly then FieldDef.Attributes := FieldDef.Attributes + [faReadonly]; if faFixed in SourceFieldDef.Attributes then FieldDef.Attributes := FieldDef.Attributes + [faFixed]; if (DataType = ftBCD) and (F is TBCDField) then FieldDef.Precision := TBCDField(F).Precision; if F is TObjectField then CreateFieldDefs(TObjectField(F).Fields, FieldDef.ChildDefs); end; end; end; finally FieldDefs.EndUpdate; end; end; var OldActive: boolean; Bookmark: string; i: integer; SourceField: TField; FieldsRO: array of boolean; Value: variant; begin OldActive := Active; Close; Clear; DeleteFields; CreateFieldDefs(Source.Fields, FieldDefs); if Source.Active then begin DisableControls; Source.DisableControls; Bookmark := Source.Bookmark; Source.First; Open; // Temporary clear Field.ReadOnly flag SetLength(FieldsRO, Fields.Count); for i := 0 to Fields.Count - 1 do begin FieldsRO[i] := Fields[i].ReadOnly; Fields[i].ReadOnly := False; end; try while not Source.EOF do begin Append; for i := 0 to Fields.Count - 1 do begin SourceField := Source.FieldByName(Fields[i].FieldName); if not SourceField.IsNull then if Fields[i] is TLargeIntField then TLargeIntField(Fields[i]).AsLargeInt := TLargeIntField(SourceField).AsLargeInt else begin // To avoid memory leaks Value := Unassigned; Value := SourceField.Value; Fields[i].Value := Value; end; end; Post; Source.Next; end; finally First; // Restore Field.ReadOnly flag for i := 0 to Fields.Count - 1 do Fields[i].ReadOnly := FieldsRO[i]; if Source.RecordCount > 0 then Source.Bookmark := Bookmark; Source.EnableControls; EnableControls end; end; Active := OldActive; end; procedure TVirtualTable.DefineProperties(Filer: TFiler); function WriteData: boolean; begin Result := True; end; begin inherited DefineProperties(Filer); Filer.DefineBinaryProperty('Data', ReadBinaryData, WriteBinaryData, WriteData); end; procedure TVirtualTable.ReadBinaryData(Stream: TStream); begin if voStored in FOptions then begin FRecordDataStream.Clear; FRecordDataStream.CopyFrom(Stream, Stream.Size - Stream.Position); end; end; procedure TVirtualTable.WriteBinaryData(Stream: TStream); begin if voStored in FOptions then SaveToStream(Stream, False); end; function TVirtualTable.IsSequenced: boolean; begin Result := True; end; procedure TVirtualTable.AddField(Name: string; FieldType: TFieldType; Size: integer; Required: boolean); begin if not (FieldType in SupportFieldTypes) then DatabaseError(SNotSupportFieldType); FieldDefs.Add(Name, FieldType, Size, Required); end; procedure TVirtualTable.DeleteField(Name: string); var Stream: TMemoryStream; OldActive: boolean; FieldDef: TFieldDef; begin FieldDef := FieldDefs.Find(Name); if VTOldBehavior then begin OldActive := Active; Stream := TMemoryStream.Create; try SaveToStream(Stream, False); Close; Clear; FieldDef.Free; //FieldDefs.Delete(FieldDef.Index); finally LoadFromStream(Stream, False); Active := OldActive; Stream.Free; end; end else FieldDef.Free; end; procedure TVirtualTable.DeleteFields; begin Clear; FieldDefs.Clear; {$IFDEF VER3} while FieldCount > 0 do Fields[0].Free; {$ELSE} Fields.Clear; {$ENDIF} end; procedure TVirtualTable.Clear; begin if State in [dsInsert,dsEdit] then Cancel; Data.Close; if Active then begin Data.Open; Resync([]); end; end; { Stream/File } { Storage format: Version 2 // 0 = 2.00, 1 = 2.10, 2 = 5.10.1.8 (Blob storage) -- FieldDefs FieldCount 2 NameLength 2 Name Length(Name) DataType 2 Size 2 -- Fields FieldCount 2 -| NameLength 2 | Name Length(Name) | for 1 Kind 2 | DataType 2 | Size 2 -| RecordCount 4 Size 2 (4 from Version = 2) Value Size } function XMLDecode(const AStr: String): String; var sb: StringBuilder; begin sb := StringBuilder.Create(AStr, Length(AStr)); try sb.Replace(''', ''''); sb.Replace('"', '"'); sb.Replace('<', '<'); sb.Replace('>', '>'); sb.Replace('&', '&'); Result := sb.ToString; finally sb.Free; end; end; type {$IFDEF VER6P} TStringListQ = class(TStringList) protected function CompareStrings(const S1, S2: string): Integer; override; end; function TStringListQ.CompareStrings(const S1, S2: string): Integer; // +10% to performance begin if S1 > S2 then Result := 1 else if S1 < S2 then Result := -1 else Result := 0; end; {$ELSE} TStringListQ = TStringList; {$ENDIF} procedure TVirtualTable.LoadFromStream(Stream: TStream; LoadFields: boolean); var LocFieldDefs: TFieldDefs; FieldAliases: TStringList; StrReader: StringReader; Reader: XMLTextReader; StreamXML: string; Version: word; procedure ReadArray(var A: TBytes; Count: integer; const Offset: integer = 0); begin {$IFDEF CLR} if (Count <> 0) and (Stream.Read(A, Offset, Count) <> Count) then raise EReadError.Create(SReadError); {$ELSE} Stream.ReadBuffer(A[Offset], Count); {$ENDIF} end; function DetectFileFormat: TCRFileFormat; var Signature: TBytes; Offset: integer; {$IFDEF CLR} Bytes: TBytes; {$ENDIF} begin Result := ffVTD; Stream.Position := 0; SetLength(Signature, 5); ReadArray(Signature, 5); Stream.Position := 0; if (Signature[0] = $EF) and (Signature[1] = $BB) and (Signature[2] = $BF) then begin // UTF8 preamble Stream.Position := 3; ReadArray(Signature, 5); end; if Signature[0] <> Byte('<') then Exit; if Signature[1] = Byte('?') then Offset := 1 else Offset := 0; if ((Signature[1 + Offset] <> Byte('x')) and (Signature[1 + Offset] <> Byte('X'))) or ((Signature[2 + Offset] <> Byte('m')) and (Signature[2 + Offset] <> Byte('M'))) or ((Signature[3 + Offset] <> Byte('l')) and (Signature[3 + Offset] <> Byte('L'))) then Exit; Result := ffXML; StreamXML := ''; SetLength(StreamXML, Stream.Size); {$IFDEF CLR} Bytes := Encoding.Default.GetBytes(StreamXML); Stream.Read(Bytes, Length(StreamXML)); StreamXML := Encoding.Default.GetString(Bytes); {$ELSE} Stream.Read(StreamXML[1], Length(StreamXML)); {$ENDIF} end; procedure ReadFieldDefsVTD; var FieldCount: word; i: integer; D2: word; FieldName: TBytes; FieldType: word; FieldSize: word; FieldPrecision: integer; FieldDef: TFieldDef; begin Stream.Read(FieldCount, 2); for i := 0 to FieldCount - 1 do begin Stream.Read(D2, 2); SetLength(FieldName, D2); ReadArray(FieldName, D2); Stream.Read(FieldType, 2); Stream.Read(FieldSize, 2); if Version >= 3 then Stream.Read(FieldPrecision, 4); LocFieldDefs.Add(Encoding.Default.GetString(FieldName), TFieldType(FieldType), FieldSize, False); FieldDef := LocFieldDefs.Items[LocFieldDefs.Count - 1]; if TFieldType(FieldType) in [ftCurrency, ftFloat, ftInteger, ftSmallInt, ftLargeInt, ftDate, ftTime, ftDateTime] then FieldDef.Precision := FieldPrecision; end; end; procedure ReadFieldDefsXML; function FieldTypeFromXML(FieldType: string; FieldDBType: string; const IsLong: boolean; const FixedLength: boolean): TFieldType; var InternalType: Word; begin FieldType := LowerCase(FieldType); FieldDBType := LowerCase(FieldDBType); if (FieldType = 'i8') and (FieldDBType = '') {and FixedLength} then InternalType := dtInt64 else if (FieldType = 'bin.hex') and (FieldDBType = '') then if IsLong then InternalType := dtBlob else InternalType := dtBytes else if (FieldType = 'boolean') and (FieldDBType = '') {and FixedLength} then InternalType := dtBoolean else if (FieldType = 'string') and (FieldDBType = 'variant') then {$IFDEF VER5P} InternalType := dtVariant {$ELSE} InternalType := dtString {$ENDIF} else if (FieldType = 'string') and (FieldDBType = '') then if IsLong then InternalType := {$IFDEF VER10P}dtWideMemo{$ELSE}dtMemo{$ENDIF} else InternalType := dtWideString else if (FieldType = 'string') and ((FieldDBType = 'str') or (FieldDBType = 'string')) then if IsLong then InternalType := dtMemo else InternalType := dtString else if (FieldType = 'i8') and (FieldDBType = 'currency') {and FixedLength} then InternalType := dtCurrency else if (FieldType = 'number') and (FieldDBType = 'currency') {and FixedLength} then InternalType := dtCurrency else if (FieldType = 'datetime') and (FieldDBType = 'variantdate') {and FixedLength} then InternalType := dtDateTime else if (FieldType = 'date') and (FieldDBType = '') {and FixedLength} then InternalType := dtDate else if (FieldType = 'time') and (FieldDBType = '') {and FixedLength} then InternalType := dtTime else if (FieldType = 'datetime') and (FieldDBType = 'timestamp') {and FixedLength} then InternalType := dtDateTime else if (FieldType = 'number') and (FieldDBType = 'decimal') {and FixedLength} then InternalType := dtCurrency else if (FieldType = 'float') and (FieldDBType = '') {and FixedLength} then InternalType := dtFloat else if (FieldType = 'uuid') and (FieldDBType = '') and FixedLength then {$IFDEF VER5P} InternalType := dtGuid {$ELSE} InternalType := dtString {$ENDIF} else if (FieldType = 'int') and (FieldDBType = '') {and FixedLength} then InternalType := dtInt32 else if (FieldType = 'number') and (FieldDBType = 'numeric') {and FixedLength} then {$IFDEF VER6P} InternalType := dtFmtBCD {$ELSE} InternalType := dtBCD {$ENDIF} else if (FieldType = 'r4') and (FieldDBType = '') {and FixedLength} then InternalType := dtFloat else if (FieldType = 'i2') and (FieldDBType = '') {and FixedLength} then InternalType := dtInt16 else if (FieldType = 'i1') and (FieldDBType = '') {and FixedLength} then InternalType := dtInt8 else if (FieldType = 'ui8') and (FieldDBType = '') {and FixedLength} then InternalType := dtInt64 else if (FieldType = 'ui4') and (FieldDBType = '') {and FixedLength} then InternalType := dtUInt32 else if (FieldType = 'ui2') and (FieldDBType = '') {and FixedLength} then InternalType := dtWord else if (FieldType = 'ui1') and (FieldDBType = '') {and FixedLength} then InternalType := dtWord else begin DatabaseError(SDataTypeNotSupported, Self); InternalType := 0; // to prevent compiler warning end; Result := GetFieldType(InternalType); end; var i: integer; AttrName, AttrValue: string; AttributeCount: integer; FieldDef: TFieldDef; FieldName: string; FieldAlias: string; FieldDataType: TFieldType; FieldSize: integer; FieldPrecision: integer; FieldScale: integer; FieldRequired: boolean; FieldFixed: boolean; FieldType, FieldDBType: string; FieldIsLong: boolean; HaveLength: boolean; TmpValue: string; begin while Reader.Read do begin if (UpperCase(Reader.Name) = 'S:SCHEMA') and (Reader.NodeType = ntEndElement) then break; if (UpperCase(Reader.Name) = 'S:ATTRIBUTETYPE') and (Reader.NodeType <> ntEndElement) then begin AttributeCount := Reader.AttributeCount; for i := 0 to AttributeCount - 1 do begin Reader.MoveToAttribute(i); if LowerCase(Reader.Name) = 'name' then FieldName := Reader.Value; if LowerCase(Reader.Name) = 'rs:name' then begin FieldAlias := Reader.Value; {$IFDEF CLR} FieldAlias := XMLDecode(FieldAlias); {$ENDIF} end; end; if FieldAlias <> '' then begin TmpValue := FieldName; FieldName := FieldAlias; FieldAlias := TmpValue; FieldAliases.Add(FieldName + '=' + FieldAlias); end; while not ((UpperCase(Reader.Name) = 'S:DATATYPE') and (Reader.NodeType <> ntEndElement)) do begin Reader.Read; if Reader.EOF then raise Exception.Create(SInvalidXML); end; FieldSize := 0; FieldPrecision := 0; FieldScale := 0; FieldType := ''; FieldDBType := ''; FieldIsLong := False; FieldRequired := False; FieldFixed := False; HaveLength := False; AttributeCount := Reader.AttributeCount; for i := 0 to AttributeCount - 1 do begin Reader.MoveToAttribute(i); AttrName := LowerCase(Reader.Name); AttrValue := LowerCase(Reader.Value); if AttrName = 'rs:fixedlength' then FieldFixed := StrToBool(AttrValue) else if AttrName = 'rs:maybenull' then FieldRequired := not StrToBool(AttrValue) else if AttrName = 'dt:maxlength' then begin FieldSize := Integer(Round(StrToFloat(AttrValue)) and $7FFFFFFF); HaveLength := True; end else if AttrName = 'rs:precision' then FieldPrecision := StrToInt(AttrValue) else if AttrName = 'rs:scale' then FieldScale := StrToInt(AttrValue) else if AttrName = 'dt:type' then FieldType := AttrValue else if AttrName = 'rs:dbtype' then FieldDBType := AttrValue else if AttrName = 'rs:long' then FieldIsLong := StrToBool(AttrValue); end; if (FieldType = '') and (FieldDBType = '') then raise Exception.Create(SInvalidXML); if (not HaveLength) and (FieldDBType = '') then FieldDBType := 'variant'; FieldDataType := FieldTypeFromXML(FieldType, FieldDBType, FieldIsLong, FieldFixed); if not (FieldDataType in [ftString, ftWideString, ftVariant, ftGuid, ftBcd{$IFDEF VER6P}, ftFmtBcd{$ENDIF}, ftBytes, ftVarBytes, ftBlob, ftMemo, ftFixedChar{$IFDEF VER6P}, ftTimeStamp{$ENDIF}{$IFDEF VER10P}, ftWideMemo{$ENDIF}]) then FieldSize := 0; if FieldIsLong then FieldSize := 0; if FieldDataType = ftGuid then FieldSize := 38; if (FieldDataType = ftBytes) and not FieldFixed then FieldDataType := ftVarBytes; LocFieldDefs.Add(FieldName, FieldDataType, FieldSize, FieldRequired); FieldDef := LocFieldDefs.Items[LocFieldDefs.Count - 1]; if FieldDataType in [ftCurrency, ftFloat, ftInteger, ftSmallInt, ftLargeInt, ftDate, ftTime, ftDateTime] then FieldDef.Precision := FieldPrecision else if FieldDataType in [ftBCD{$IFDEF VER6P}, ftFMTBCD{$ENDIF}] then begin FieldDef.Precision := FieldPrecision; FieldDef.Size := FieldScale; end; {$IFDEF VER5P} if FieldFixed then FieldDef.Attributes := FieldDef.Attributes + [DB.faFixed]; {$ENDIF} end; end; end; procedure SetXMLValueToField(Field: TField; const FieldValue: string); var Year: integer; Month: Word; Day: Word; Hour: Word; Minute: Word; Second: Word; function GetNext(const Value: string; Offset: integer; Digits: Integer): string; var i: Integer; begin SetLength(Result, Digits); for i := 1 to Digits do begin if (Value[Offset + i] >= '0') and (Value[Offset + i] <= '9') then Result[i] := Value[Offset + i] else raise Exception.Create(SInvalidXML); end; end; procedure ConvertDate(const Value: string); var Offset: integer; begin Offset := 0; if Value[1] = '-' then Inc(Offset); Year := StrToInt(GetNext(Value, Offset, 4)); Inc(Offset, 5); Month := StrToInt(GetNext(Value, Offset, 2)); Inc(Offset, 3); Day := StrToInt(GetNext(Value, Offset, 2)); end; procedure ConvertTime(const Value: string); var Offset: integer; begin if Length(Value) < 8 then raise Exception.Create(SInvalidXML); Offset := 0; Hour := StrToInt(GetNext(Value, Offset, 2)); Inc(Offset, 3); Minute := StrToInt(GetNext(Value, Offset, 2)); Inc(Offset, 3); Second := StrToInt(GetNext(Value, Offset, 2)); end; function DecodeXMLDateTime(const XMLDateTime: string): TDateTime; var TimePosition: integer; begin TimePosition := Pos('T', XMLDateTime); if TimePosition > 0 then begin ConvertDate(Copy(XMLDateTime, 1, TimePosition -1)); ConvertTime(Copy(XMLDateTime, TimePosition + 1, Length(XMLDateTime) - TimePosition)); end else begin Hour := 0; Minute := 0; Second := 0; ConvertDate(XMLDateTime); end; Result := {$IFNDEF CLR}MemUtils.{$ENDIF}EncodeDateTime(Year, Month, Day, Hour, Minute, Second, 0); end; function DecodeXMLTime(const XMLTime: string): TDateTime; begin Year := 1000; Month := 1; Day := 1; ConvertTime(XMLTime); Result := {$IFNDEF CLR}MemUtils.{$ENDIF}EncodeDateTime(Year, Month, Day, Hour, Minute, Second, 0); end; var FieldDesc: TFieldDesc; {$IFDEF VER6P} Bcd: TBCD; TmpBcd: TBCD; FieldLength, FieldScale: integer; {$IFDEF VER9P} Delta: word; {$ENDIF} {$ENDIF} Buffer: TBytes; i: Integer; IsValidChar: boolean; TextOffset, BuffOffset, Count: integer; {$IFDEF CLR} TextBytes: TBytes; {$ENDIF} begin FieldDesc := GetFieldDesc(Field); case FieldDesc.DataType of dtBoolean: Field.AsBoolean := StrToBool(FieldValue); dtInt8, dtInt16, dtInt32, dtInt64, dtUInt16, dtUInt32: Field.AsString := FieldValue; dtFloat: Field.AsFloat := StrToFloat(ChangeDecimalSeparator(FieldValue, '.', DecimalSeparator)); dtDate, dtDateTime: Field.AsDateTime := DecodeXMLDateTime(FieldValue); dtTime: Field.AsDateTime := DecodeXMLTime(FieldValue); dtCurrency, dtBcd: Field.AsCurrency := StrToCurr(ChangeDecimalSeparator(FieldValue, '.', DecimalSeparator)); {$IFDEF VER6P} dtFmtBCD: begin BCD := StrToBCD(ChangeDecimalSeparator(FieldValue, '.', DecimalSeparator)); FieldLength := FieldDesc.Length; FieldScale := FieldDesc.Scale; {$IFDEF VER9P} // Delphi 9 NormalizeBcd Bug Delta := FieldLength - FieldScale; if Delta > 34 then begin Delta := 34; FieldLength := FieldScale + Delta; end; {$ENDIF} NormalizeBcd(Bcd, TmpBcd, FieldLength, FieldScale); Field.AsBCD := TmpBcd; end; {$ENDIF} dtBlob, dtBytes, dtVarBytes, dtExtVarBytes: begin {$IFDEF CLR} TextBytes := Encoding.Default.GetBytes(FieldValue); {$ENDIF} TextOffset := 0; BuffOffset := 0; SetLength(Buffer, Length(FieldValue) div 2); for i := 1 to Length(FieldValue) do begin IsValidChar := not ((FieldValue[i] = #$D) or (FieldValue[i] = #$A) or (FieldValue[i] = #9)); if ((not IsValidChar) or (i = Length(FieldValue))) then begin Count := i - TextOffset; if not IsValidChar then Dec(Count); if Count > 0 then begin {$IFNDEF CLR} HexToBin(PChar(Integer(@FieldValue[1]) + TextOffset), PChar(Integer(@Buffer[0]) + BuffOffset), Count); {$ELSE} HexToBin(TextBytes, TextOffset, Buffer, BuffOffset, Count div 2); {$ENDIF} Inc(BuffOffset, Count div 2); end; TextOffset := i; end; end; if Length(Buffer) > BuffOffset then SetLength(Buffer, BuffOffset); Field.AsString := Encoding.Default.GetString(Buffer); end; else Field.AsString := FieldValue; end; end; procedure ProcessXMLData; var AttributeCount: integer; p, i, j: integer; Field: TField; FieldList: TStringList; FieldIndex: integer; ActualName, Alias: string; begin FieldList := TStringListQ.Create; try {$IFDEF VER6P} FieldList.CaseSensitive := True; {$ENDIF} FieldList.Sorted := True; while Reader.Read do if (UpperCase(Reader.Name) = 'Z:ROW') and (Reader.NodeType <> ntEndElement) then begin Append; try AttributeCount := Reader.AttributeCount; for i := 0 to AttributeCount - 1 do begin Reader.MoveToAttribute(i); FieldIndex := FieldList.IndexOf(Reader.Name); if FieldIndex > -1 then Field := FieldList.Objects[FieldIndex] as TField else begin Field := FindField(Reader.Name); FieldList.AddObject(Reader.Name, Field); if Field = nil then for j := 0 to FieldAliases.Count - 1 do begin p := Pos('=', FieldAliases[j]); if p > 0 then begin ActualName := LowerCase(Copy(FieldAliases[j], 0, p - 1)); Alias := Copy(FieldAliases[j], p + 1, Length(FieldAliases[j]) - p); if Reader.Name = Alias then begin Field := FindField(ActualName); if Field <> nil then break; end; end; end; end; if Field = nil then raise Exception.Create(SInvalidXML); SetXMLValueToField(Field, UTF8Decode(Reader.Value)); end; Post; finally Cancel; end; end; finally FieldList.Free; end; end; var D2: word; D4: cardinal; FieldName: TBytes; FieldType: word; FieldSize: word; FieldKind: word; RecordCount: integer; i, j: integer; OldActive: boolean; St: TBytes; WSt: TBytes; FieldClass: TFieldClass; Field: TField; FieldArr: Array of TField; Handle: IntPtr; FileFormat: TCRFileFormat; begin Inc(FAvoidReload); try OldActive := Active; Close; Clear; if LoadFields then begin LocFieldDefs := FieldDefs; DeleteFields; end else LocFieldDefs := TFieldDefs.Create(Self); Stream.Seek(0, soFromBeginning); FileFormat := DetectFileFormat; Stream.Seek(0, soFromBeginning); Stream.Read(Version, 2); // Version StrReader := nil; Reader := nil; FieldAliases := nil; try // FieldDefs FAvoidRefreshData := True; try case FileFormat of ffVTD: ReadFieldDefsVTD; ffXML: begin FieldAliases := TStringList.Create; StrReader := StringReader.Create(StreamXML); Reader := XMLTextReader.Create(StrReader); ReadFieldDefsXML; end; end; finally FAvoidRefreshData := False; end; with Stream do begin if (FileFormat = ffVTD) and (Version >= 1) then begin // Fields Read(D2, 2); for i := 0 to D2 - 1 do begin Read(D2, 2); SetLength(FieldName, D2); ReadArray(FieldName, D2); Read(FieldKind, 2); Read(FieldType, 2); Read(FieldSize, 2); if TFieldKind(FieldKind) = fkLookup then continue; FieldClass := GetFieldClass(TFieldType(FieldType)); Field := FieldClass.Create(Self.Owner);// Self); try Field.FieldName := Encoding.Default.GetString(FieldName); Field.FieldKind := TFieldKind(FieldKind); case TFieldType(FieldType) of ftString: Field.Size := FieldSize; ftWideString: Field.Size := FieldSize * sizeof(WideChar); end; Field.DataSet := Self; except Field.Free; raise; end; end; end; if FileFormat = ffVTD then Read(RecordCount, 4); if (FileFormat = ffXML) or ((FileFormat = ffVTD) and (RecordCount > 0)) then begin DisableControls; if not (csReading in ComponentState) then Open else begin DoBeforeOpen; try OpenCursor(False); SetState(dsBrowse) except SetState(dsInactive); CloseCursor; raise; end; DoAfterOpen; DoAfterScroll; end; try case FileFormat of ffXML: ProcessXMLData; ffVTD: begin SetLength(FieldArr, LocFieldDefs.Count); for i := 0 to LocFieldDefs.Count - 1 do FieldArr[i] := FindField(LocFieldDefs[i].Name); for j := 0 to RecordCount - 1 do begin Append; try for i := 0 to LocFieldDefs.Count - 1 do begin Field := FieldArr[i]; if Version < 2 then begin Read(D2, 2); D4 := D2; end else Read(D4, 4); if D4 > 0 then begin if (Field <> nil) and (Field.DataType = ftWideString) then begin SetLength(WSt, D4); ReadArray(WSt, D4); TWideStringField(Field).Value := Encoding.Unicode.{$IFDEF CLR}GetString{$ELSE}GetWideString{$ENDIF}(WSt); end else begin if (Field <> nil) and (Field.DataType = ftVarBytes) then begin SetLength(St, D4 + 2); D2 := D4; St[0] := Lo(D2); St[1] := Hi(D2); ReadArray(St, D4, 2); end else begin SetLength(St, D4); ReadArray(St, D4); end; if Field <> nil then case Field.DataType of ftString, ftBlob, ftMemo: Field.AsString := Encoding.Default.GetString(St); {$IFDEF VER10P} ftWideMemo: TWideMemoField(Field).Value := Encoding.Unicode.{$IFDEF CLR}GetString{$ELSE}GetWideString{$ENDIF}(St); {$ENDIF} else Handle := AllocGCHandle(St, True); try Field.SetData(GetAddrOfPinnedObject(Handle)); finally FreeGCHandle(Handle); end; end; end; end; end; finally Post; end; end; end; else Assert(False); end; finally First; EnableControls; end; if not OldActive and (voPersistentData in FOptions) then if csReading in ComponentState then begin SetState(dsInactive); CloseCursor; end else Close; end else Active := OldActive; end; finally if LocFieldDefs <> FieldDefs then LocFieldDefs.Free; FieldAliases.Free; Reader.Free; StrReader.Free; end; finally Dec(FAvoidReload); end; end; {$IFDEF CLR} function StrLen(S: IntPtr): integer; begin Result := 0; while Marshal.ReadByte(S, Result) <> 0 do Inc(Result); end; function StrLenW(S: IntPtr): integer; begin Result := 0; while Marshal.ReadInt16(S, Result * 2) <> 0 do Inc(Result); end; {$ENDIF} procedure TVirtualTable.SaveToStream(Stream: TStream; StoreFields: boolean); var D2: word; D4: cardinal; St: {$IFDEF CLR}TBytes{$ELSE}string{$ENDIF}; i: integer; OldRecNo: integer; OldActive: boolean; TempFields: TFields; Field: TField; FieldDesc: TFieldDesc; FieldArr: array of TField; FieldDescArr: array of TFieldDesc; Buffer: TBytes; pBuffer: IntPtr; RecBuf: TRecordBuffer; IsNull: boolean; Piece: PPieceHeader; BufLen: cardinal; Blob: TBlob; BlobBuffer: TBytes; Handle: IntPtr; procedure AssignFields(Dest: TFields; Source: TFields); var Field:TField; begin Dest.Clear; while Source.Count > 0 do begin Field := Source[0]; Source.Remove(Field); Dest.Add(Field); end; end; procedure WriteArray(const A: TBytes; Count: integer; const Offset: integer = 0); begin {$IFDEF CLR} if (Count <> 0) and (Stream.Write(A, Offset, Count) <> Count) then raise EWriteError.Create(SWriteError); {$ELSE} Stream.WriteBuffer(A[Offset], Count); {$ENDIF} end; var Offset: integer; begin Inc(FAvoidReload); try OldActive := Active; with Stream do begin D2 := 3; Write(D2, 2); // Version 0 - 2.00 1 - 2.10 // FieldDefs D2 := FieldDefs.Count; Write(D2, 2); for i := 0 to FieldDefs.Count - 1 do begin D2 := Length(FieldDefs[i].Name); Write(D2, 2); {$IFDEF CLR} St := Encoding.Default.GetBytes(FieldDefs[i].Name); WriteArray(St, D2); {$ELSE} St := FieldDefs[i].Name; Write(PChar(St)^, Length(St)); {$ENDIF} D2 := Word(FieldDefs[i].DataType); Write(D2, 2); D2 := FieldDefs[i].Size; Write(D2, 2); D4 := FieldDefs[i].Precision; Write(D4, 4); end; // Fields if DefaultFields or not StoreFields then begin D2 := 0; Write(D2, 2); end else begin D2 := FieldCount; for i := 0 to FieldCount - 1 do if Fields[i].FieldKind = fkLookup then Dec(D2); Write(D2, 2); for i := 0 to FieldCount - 1 do begin if Fields[i].FieldKind = fkLookup then continue; D2 := Length(Fields[i].FieldName); Write(D2, 2); {$IFDEF CLR} St := Encoding.Default.GetBytes(Fields[i].FieldName); WriteArray(St, D2); {$ELSE} St := Fields[i].FieldName; Write(PChar(St)^, Length(St)); {$ENDIF} D2 := Word(Fields[i].FieldKind); // for ver 1 Write(D2, 2); D2 := Word(Fields[i].DataType); Write(D2, 2); D2 := Fields[i].Size; Write(D2, 2); end; end; if FieldDefs.Count = 0 then begin D4 := 0; Write(D4, 4); end else begin DisableControls; if Active then OldRecNo := RecNo else OldRecNo := -1; if not DefaultFields then begin Close; TempFields := TFields.Create(nil); AssignFields(TempFields, Fields); Fields.Clear; end else TempFields := nil; Open; First; BufLen := 0; SetLength(FieldArr, FieldDefs.Count); SetLength(FieldDescArr, FieldDefs.Count); for i := 0 to FieldDefs.Count - 1 do begin FieldArr[i] := FindField(FieldDefs[i].Name); FieldDescArr[i] := Data.FindField(FieldDefs[i].Name); if (FieldDescArr[i] <> nil) and (FieldDescArr[i].Size > BufLen) then BufLen := FieldDescArr[i].Size; end; SetLength(Buffer, BufLen); Handle := AllocGCHandle(Buffer, True); pBuffer := GetAddrOfPinnedObject(Handle); try D4 := RecordCount; Write(D4, 4); while not EOF do begin for i := 0 to FieldDefs.Count - 1 do begin Field := FieldArr[i]; FieldDesc := FieldDescArr[i]; // get field desc and data from record buffer if FieldDesc <> nil then begin GetActiveRecBuf(RecBuf); if FieldDesc.DataType = dtVariant then FillChar(pBuffer, Length(Buffer), 0); Data.GetField(FieldDesc.FieldNo, RecBuf, pBuffer, IsNull); end else IsNull := True; Blob := nil; Offset := 0; if (Field = nil) or (FieldDesc = nil) or IsNull then D4 := 0 else begin // to write field data there must be Field and FieldDesc case FieldDesc.DataType of dtString: D4 := StrLen(pBuffer); dtWideString: D4 := StrLenW(pBuffer) * sizeof(WideChar); dtInt8, dtInt16, dtInt32, dtInt64, dtUInt16, dtUInt32, dtBoolean, dtCurrency, dtFloat, dtGuid, dtBytes: D4 := FieldDesc.Size; dtDateTime, dtDate, dtTime: D4 := sizeof(TDateTime); dtBlob, dtMemo{$IFDEF VER10P}, dtWideMemo{$ENDIF}: begin Blob := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(pBuffer))); D4 := Blob.Size; end; dtVarBytes: begin D4 := Marshal.ReadInt16(pBuffer); Offset := 2; end; {$IFDEF VER6P} dtFMTBCD: D4 := SizeOfTBcd; {$ENDIF} else Assert(False, SUnknownDataType + ' FieldDesc.DataType=' + IntToStr(Integer(FieldDesc.DataType))); end; end; Write(D4, 4); if D4 > 0 then begin if FieldDesc.DataType in [dtBlob, dtMemo{$IFDEF VER10P}, dtWideMemo{$ENDIF}] then begin // save blob to stream Piece := Blob.FirstPiece; while IntPtr(Piece) <> nil do begin BufLen := Piece.Used; SetLength(BlobBuffer, BufLen); Marshal.Copy(IntPtr(integer(Piece) + Sizeof(TPieceHeader)), BlobBuffer, 0, BufLen); WriteArray(BlobBuffer, BufLen); Piece := Piece.Next; end; end else WriteArray(Buffer, D4, Offset); end; end; Next; end; finally FreeGCHandle(Handle); if TempFields <> nil then begin Close; AssignFields(Fields, TempFields); TempFields.Free; end; Active := OldActive; if OldActive and (RecordCount > 0) then RecNo := OldRecNo; EnableControls; end; end; end; finally Dec(FAvoidReload); end; end; {$IFNDEF D3_CB3} function TVirtualTable.IsFieldDefsStored: boolean; begin Result := FieldDefs.Count > 0; end; {$ENDIF} procedure TVirtualTable.LoadFromFile(const FileName: string); var Stream: TStream; begin Stream := TFileStream.Create(FileName, fmOpenRead); try LoadFromStream(Stream); finally Stream.Free; end; end; procedure TVirtualTable.SaveToFile(const FileName: string); var Stream:TStream; begin Stream := TFileStream.Create(FileName, fmCreate); try SaveToStream(Stream); finally Stream.Free; end; end; procedure TVirtualTable.SetActive(Value: boolean); begin if (csReading in ComponentState) then begin if not FStreamedActive then FStreamedActive := Value end else inherited; end; function TVirtualTable.GetFieldDefs: TFieldDefs; begin Result := inherited FieldDefs; end; procedure TVirtualTable.SetFieldDefs(Value: TFieldDefs); begin inherited FieldDefs := Value; end; initialization VTOldBehavior := False; end.