unit Myc.DataRecord; interface uses System.SysUtils, System.Generics.Collections, System.Rtti, System.TypInfo; type TDataRecord = record public type TFieldType = (dfFloat, dfInteger, dfString, dfTimestamp); TField = record private FFieldType: TFieldType; FName: string; FOffset: Integer; procedure FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src); procedure ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst); procedure Assign(const [ref] Buffer: TBytes; const Src); overload; procedure Finalize(const [ref] Buffer: TBytes); property Offset: Integer read FOffset; constructor Create(const AName: string; AFieldType: TFieldType; AOffset: Integer); function GetSize: Integer; inline; function GetAlignedSize: Integer; inline; public property FieldType: TFieldType read FFieldType; property Name: string read FName; property Size: Integer read GetSize; property AlignedSize: Integer read GetAlignedSize; end; TFieldDef = record public Name: String; FieldType: TFieldType end; TLayout = record private FFields: TArray; constructor Create(const AFields: TArray); public class function FromRecord: TLayout; static; class function Construct(const Def: TArray): TLayout; static; function IndexOf(const Name: String): Integer; property Fields: TArray read FFields; end; const Align = 8; private FLayout: TLayout; FBuffer: TBytes; const DataSize: array[TFieldType] of Integer = (sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime)); public constructor Create(const ALayout: TLayout; const ABuffer: TBytes = nil); class operator Finalize(var Dest: TDataRecord); class function FromRecord: TDataRecord; overload; static; class function FromRecord(const Src: T): TDataRecord; overload; static; procedure SetValue(const Name: String; const Value: T); overload; procedure SetValue(Idx: Integer; const Value); overload; function GetValue(const Name: String): T; overload; procedure GetValue(Idx: Integer; out Value); overload; procedure CopyField(Idx: Integer; Dst: TDataRecord; DstIdx: Integer); property Layout: TLayout read FLayout; end; implementation uses System.Classes; constructor TDataRecord.Create(const ALayout: TLayout; const ABuffer: TBytes = nil); begin FLayout := ALayout; FBuffer := ABuffer; var bufSize := 0; if Length(FLayout.Fields) > 0 then with FLayout.Fields[High(FLayout.Fields)] do bufSize := Offset + AlignedSize; if FBuffer = nil then SetLength(FBuffer, bufSize) else Assert(Length(FBuffer) >= bufSize); end; procedure TDataRecord.CopyField(Idx: Integer; Dst: TDataRecord; DstIdx: Integer); begin Assert(Dst.Layout.Fields[DstIdx].FieldType = FLayout.Fields[Idx].FieldType); Dst.Layout.Fields[DstIdx].Assign(Dst.FBuffer, FBuffer[FLayout.Fields[DstIdx].Offset]) end; { TDataRecord } class function TDataRecord.FromRecord: TDataRecord; begin Result := TDataRecord.Create(TLayout.FromRecord); end; class function TDataRecord.FromRecord(const Src: T): TDataRecord; begin Result := FromRecord; var ctx := TRttiContext.Create; var rttiType := ctx.GetType(TypeInfo(T)); var rttiFields := rttiType.GetFields; var fields: TArray; for var i := 0 to High(rttiFields) do begin var rf := rttiFields[i]; var idx := Result.Layout.IndexOf(rf.Name); if (idx >= 0) then begin var S: PByte := @Src; inc(S, rf.Offset); Result.Layout.Fields[idx].FromType(Result.FBuffer, rf.FieldType.Handle, S^); end; end; end; procedure TDataRecord.GetValue(Idx: Integer; out Value); begin var P := @FBuffer[FLayout.Fields[Idx].Offset]; case FLayout.Fields[Idx].FieldType of dfFloat: Double(Value) := PDouble(P)^; dfInteger: Int64(Value) := PInt64(Value)^; dfString: String(Value) := PString(Value)^; dfTimestamp: TDateTime(Value) := PDateTime(Value)^; else Assert(false); end; end; procedure TDataRecord.SetValue(Idx: Integer; const Value); begin var P := @FBuffer[FLayout.Fields[Idx].Offset]; case FLayout.Fields[Idx].FieldType of dfFloat: PDouble(P)^ := Double(Value); dfInteger: PInt64(P)^ := Int64(Value); dfString: PString(P)^ := String(Value); dfTimestamp: PDateTime(P)^ := TDateTime(Value); else Assert(false); end; end; function TDataRecord.GetValue(const Name: String): T; begin var idx := FLayout.IndexOf(Name); if (idx >= 0) then FLayout.Fields[idx].ToType(FBuffer, TypeInfo(T), Result); end; procedure TDataRecord.SetValue(const Name: String; const Value: T); begin var idx := FLayout.IndexOf(Name); if (idx >= 0) then FLayout.Fields[idx].FromType(FBuffer, TypeInfo(T), Value); end; class operator TDataRecord.Finalize(var Dest: TDataRecord); begin for var i := 0 to High(Dest.FLayout.Fields) do Dest.FLayout.Fields[i].Finalize(Dest.FBuffer); end; constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; AOffset: Integer); begin FName := AName; FFieldType := AFieldType; FOffset := AOffset; end; procedure TDataRecord.TField.Finalize(const [ref] Buffer: TBytes); begin if not (FFieldType in [dfString]) then exit; Assert(FOffset + Size <= Length(Buffer)); var P := @Buffer[FOffset]; case FFieldType of dfString: PString(P)^ := ''; end; end; procedure TDataRecord.TField.FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src); begin Assert(FOffset + Size <= Length(Buffer)); Finalize(Buffer); var Dst := @Buffer[FOffset]; case FFieldType of dfFloat: begin Assert(SrcType.Kind = tkFloat); case GetTypeData(SrcType).FloatType of ftSingle: PDouble(Dst)^ := PSingle(@Src)^; ftDouble: PDouble(Dst)^ := PDouble(@Src)^; else Assert(false); end; end; dfInteger: begin case SrcType.Kind of tkInteger: PInt64(Dst)^ := PInteger(@Src)^; tkInt64: PInt64(Dst)^ := PInt64(@Src)^; else Assert(false); end; end; dfString: begin Assert(SrcType.Kind in [tkString, tkLString, tkUString, tkWString]); PString(Dst)^ := PString(@Src)^; end; dfTimestamp: begin Assert(SrcType.Kind = tkFloat); PDateTime(Dst)^ := PDateTime(@Src)^; end; else Assert(false); end; end; function TDataRecord.TField.GetSize: Integer; begin Result := DataSize[FFieldType]; end; function TDataRecord.TField.GetAlignedSize: Integer; begin Result := (GetSize + (Align - 1)) and not (Align - 1); end; procedure TDataRecord.TField.ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst); begin var Src := @Buffer[FOffset]; case FFieldType of dfFloat: begin Assert(DstType.Kind = tkFloat); case GetTypeData(DstType).FloatType of ftSingle: PSingle(@Dst)^ := PDouble(Src)^; ftDouble: PDouble(@Dst)^ := PDouble(Src)^; else Assert(false); end; end; dfInteger: begin case DstType.Kind of tkInteger: PInteger(@Dst)^ := PInt64(Src)^; tkInt64: PInt64(@Dst)^ := PInt64(Src)^; else Assert(false); end; end; dfString: begin Assert(DstType.Kind in [tkString, tkLString, tkUString, tkWString]); PString(@Dst)^ := PString(Src)^; end; dfTimestamp: begin Assert(DstType.Kind = tkFloat); PDateTime(@Dst)^ := PDateTime(Src)^; end; else Assert(false); end; end; procedure TDataRecord.TField.Assign(const [ref] Buffer: TBytes; const Src); begin Assert(FOffset + Size <= Length(Buffer)); Finalize(Buffer); var Dst := @Buffer[FOffset]; case FFieldType of dfFloat: PDouble(Dst)^ := PDouble(@Src)^; dfInteger: PInt64(Dst)^ := PInt64(@Src)^; dfString: PString(Dst)^ := PString(@Src)^; dfTimestamp: PDateTime(Dst)^ := PDateTime(@Src)^; else Assert(false); end; end; constructor TDataRecord.TLayout.Create(const AFields: TArray); begin FFields := AFields; end; class function TDataRecord.TLayout.Construct(const Def: TArray): TLayout; var Fields: TArray; begin SetLength(Fields, Length(Def)); var ofs := 0; for var i := 0 to High(Fields) do begin Fields[i] := TField.Create(Def[i].Name, Def[i].FieldType, ofs); inc(ofs, Fields[i].AlignedSize); end; end; { TLayout } class function TDataRecord.TLayout.FromRecord: TLayout; begin var ctx := TRttiContext.Create; var rttiType := ctx.GetType(TypeInfo(T)); var rttiFields := rttiType.GetFields; var fields: TArray; // Add each field and its value to the builder var ofs := 0; SetLength(fields, Length(rttiFields)); for var i := 0 to High(rttiFields) do begin var rf := rttiFields[i]; case rf.FieldType.TypeKind of tkInteger, tkInt64: fields[i] := TField.Create(rf.Name, dfInteger, ofs); tkFloat: if SameText(rf.FieldType.Name, 'TDateTime') then fields[i] := TField.Create(rf.Name, dfTimestamp, ofs) else fields[i] := TField.Create(rf.Name, dfFloat, ofs); tkString, tkWString, tkLString, tkUString: fields[i] := TField.Create(rf.Name, dfString, ofs); else raise Exception.Create('Type ' + rf.FieldType.Name + ' not supported in data records'); end; inc(ofs, fields[i].AlignedSize); end; Result.Create(fields); end; function TDataRecord.TLayout.IndexOf(const Name: String): Integer; begin for var i := 0 to High(FFields) do if (FFields[i].Name = Name) then exit(i); exit(-1); end; end.