unit Myc.Data.POD; interface uses System.SysUtils, Myc.Data.Decimal, Myc.Data.Series; type // POD: Plain old data (...that fits into 64 bits) TScalarKind = ( skInteger, skInt64, skUInt64, skSingle, skDouble, skDateTime, skTimestamp, skBoolean, skChar, skPChar, skString, skBytes, skDecimal ); TScalarBytes = array[0..7] of Byte; TScalarPChar = array[0..3] of Char; TScalarString = String[7]; TScalarValue = record public // Factory methods for creating a TScalarValue without a kind. class function FromInteger(AValue: Integer): TScalarValue; static; inline; class function FromInt64(AValue: Int64): TScalarValue; static; inline; class function FromUInt64(AValue: UInt64): TScalarValue; static; inline; class function FromSingle(AValue: Single): TScalarValue; static; inline; class function FromDouble(AValue: Double): TScalarValue; static; inline; class function FromDateTime(AValue: TDateTime): TScalarValue; static; inline; class function FromTimestamp(AValue: TTimestamp): TScalarValue; static; inline; class function FromBoolean(AValue: Boolean): TScalarValue; static; inline; class function FromChar(AValue: Char): TScalarValue; static; inline; class function FromPChar(const AValue: String): TScalarValue; overload; static; inline; class function FromPChar(const AValue: TScalarPChar): TScalarValue; overload; static; inline; class function FromString(const AValue: String): TScalarValue; overload; static; inline; class function FromString(const AValue: TScalarString): TScalarValue; overload; static; inline; class function FromBytes(const AValue: TScalarBytes): TScalarValue; static; inline; class function FromDecimal(AValue: TDecimal): TScalarValue; static; inline; // Direct field access for performance. case TScalarKind of skInteger: (AsInteger: Integer); skInt64: (AsInt64: Int64); skUInt64: (AsUInt64: UInt64); skSingle: (AsSingle: Single); skDouble: (AsDouble: Double); skDateTime: (AsDateTime: TDateTime); skTimestamp: (AsTimestamp: TTimestamp); skBoolean: (AsBoolean: Boolean); skChar: (AsChar: Char); skPChar: (AsPChar: TScalarPChar); skString: (AsString: String[7]); skBytes: (AsBytes: TScalarBytes); skDecimal: (AsDecimal: TDecimal); end; // A scalar value with a type identifier. TScalar = record public Kind: TScalarKind; Value: TScalarValue; // Creates a new scalar from a kind and a value. constructor Create(AKind: TScalarKind; const AValue: TScalarValue); // Factory methods for specific scalar types. class function FromInteger(AValue: Integer): TScalar; static; inline; class function FromInt64(AValue: Int64): TScalar; static; inline; class function FromUInt64(AValue: UInt64): TScalar; static; inline; class function FromSingle(AValue: Single): TScalar; static; inline; class function FromDouble(AValue: Double): TScalar; static; inline; class function FromDateTime(AValue: TDateTime): TScalar; static; inline; class function FromTimestamp(AValue: TTimestamp): TScalar; static; inline; class function FromBoolean(AValue: Boolean): TScalar; static; inline; class function FromChar(AValue: Char): TScalar; static; inline; class function FromPChar(const AValue: TScalarPChar): TScalar; static; inline; class function FromString(const AValue: String): TScalar; static; inline; class function FromBytes(const AValue: TScalarBytes): TScalar; static; inline; class function FromDecimal(const AValue: TDecimal): TScalar; static; inline; function ToString: String; end; // Basic data structures using the scalar type (these are not POD of course) // An array of scalar values of the same kind. TScalarArray = record public Kind: TScalarKind; Items: TArray; // Creates a new scalar array. constructor Create(AKind: TScalarKind; const AItems: TArray); end; TScalarTuple = TArray; // A field definition for a scalar record. TScalarRecordField = record Name: String; Kind: TScalarKind; constructor Create(const AName: String; AKind: TScalarKind); end; TScalarRecordDefinition = record private FFields: TArray; public constructor Create(const AFields: TArray); function IndexOf(const Name: String): Integer; property Fields: TArray read FFields; end; // A record of scalar values, based on a definition. TScalarRecord = record public // Creates a new scalar record. constructor Create(const ADef: TScalarRecordDefinition; const AFields: TArray); function GetDef: TScalarRecordDefinition; inline; function GetFields: TArray; inline; function GetItems(const Name: String): TScalar; property Def: TScalarRecordDefinition read GetDef; property Fields: TArray read GetFields; property Items[const Name: String]: TScalar read GetItems; default; strict private FDef: TScalarRecordDefinition; FFields: TArray; end; // A time series of scalar values of the same kind. TScalarSeries = record public // Creates a new scalar series. constructor Create(AKind: TScalarKind; const AItems: TSeries); function GetKind: TScalarKind; inline; function GetItems: TSeries; inline; property Kind: TScalarKind read GetKind; property Items: TSeries read GetItems; strict private FKind: TScalarKind; FItems: TSeries; end; TScalarMemberSeries = record private FDef: TScalarRecordDefinition; FArray: TChunkArray; FElement: Integer; function GetCount: Int64; inline; function GetItems(Idx: Integer): TScalarValue; public constructor Create(const ADef: TScalarRecordDefinition; const AArray: TChunkArray; AElement: Integer); property Count: Int64 read GetCount; property Element: Integer read FElement; property Items[Idx: Integer]: TScalarValue read GetItems; default; end; // A time series of scalar records, optimized for memory and access speed. TScalarRecordSeries = record private FDef: TScalarRecordDefinition; FArray: TChunkArray; FTotalCount: Int64; function GetDef: TScalarRecordDefinition; inline; function GetItems(Idx: Integer): TScalarRecord; public constructor Create(const ADef: TScalarRecordDefinition); procedure Add(const Item: TScalarRecord; Lookback: Int64 = -1); function CreateMemberSeries(const Field: String): TScalarMemberSeries; property Def: TScalarRecordDefinition read GetDef; property Items[Idx: Integer]: TScalarRecord read GetItems; default; property TotalCount: Int64 read FTotalCount; end; implementation { TScalarValue } class function TScalarValue.FromBoolean(AValue: Boolean): TScalarValue; begin Result.AsBoolean := AValue; end; class function TScalarValue.FromBytes(const AValue: TScalarBytes): TScalarValue; begin Result.AsBytes := AValue; end; class function TScalarValue.FromChar(AValue: Char): TScalarValue; begin Result.AsChar := AValue; end; class function TScalarValue.FromDateTime(AValue: TDateTime): TScalarValue; begin Result.AsDateTime := AValue; end; class function TScalarValue.FromDecimal(AValue: TDecimal): TScalarValue; begin Result.AsDecimal := AValue; end; class function TScalarValue.FromDouble(AValue: Double): TScalarValue; begin Result.AsDouble := AValue; end; class function TScalarValue.FromInt64(AValue: Int64): TScalarValue; begin Result.AsInt64 := AValue; end; class function TScalarValue.FromInteger(AValue: Integer): TScalarValue; begin Result.AsInteger := AValue; end; class function TScalarValue.FromPChar(const AValue: String): TScalarValue; var len, i: Integer; begin FillChar(Result.AsPChar, SizeOf(Result.AsPChar), #0); len := Length(AValue); if (len > 0) then begin if (len > Length(Result.AsPChar)) then len := Length(Result.AsPChar); for i := 0 to len - 1 do Result.AsPChar[i] := AValue[i + 1]; end; end; class function TScalarValue.FromPChar(const AValue: TScalarPChar): TScalarValue; begin Result.AsPChar := AValue; end; class function TScalarValue.FromSingle(AValue: Single): TScalarValue; begin Result.AsSingle := AValue; end; class function TScalarValue.FromString(const AValue: String): TScalarValue; begin Result.AsString := ShortString(AValue); end; class function TScalarValue.FromString(const AValue: TScalarString): TScalarValue; begin Result.AsString := AValue; end; class function TScalarValue.FromTimestamp(AValue: TTimestamp): TScalarValue; begin Result.AsTimestamp := AValue; end; class function TScalarValue.FromUInt64(AValue: UInt64): TScalarValue; begin Result.AsUInt64 := AValue; end; { TScalar } constructor TScalar.Create(AKind: TScalarKind; const AValue: TScalarValue); begin Kind := AKind; Value := AValue; end; class function TScalar.FromBoolean(AValue: Boolean): TScalar; begin Result.Kind := skBoolean; Result.Value := TScalarValue.FromBoolean(AValue); end; class function TScalar.FromBytes(const AValue: TScalarBytes): TScalar; begin Result.Kind := skBytes; Result.Value := TScalarValue.FromBytes(AValue); end; class function TScalar.FromChar(AValue: Char): TScalar; begin Result.Kind := skChar; Result.Value := TScalarValue.FromChar(AValue); end; class function TScalar.FromDateTime(AValue: TDateTime): TScalar; begin Result.Kind := skDateTime; Result.Value := TScalarValue.FromDateTime(AValue); end; class function TScalar.FromDecimal(const AValue: TDecimal): TScalar; begin Result.Kind := skDecimal; Result.Value := TScalarValue.FromDecimal(AValue); end; class function TScalar.FromDouble(AValue: Double): TScalar; begin Result.Kind := skDouble; Result.Value := TScalarValue.FromDouble(AValue); end; class function TScalar.FromInt64(AValue: Int64): TScalar; begin Result.Kind := skInt64; Result.Value := TScalarValue.FromInt64(AValue); end; class function TScalar.FromInteger(AValue: Integer): TScalar; begin Result.Kind := skInteger; Result.Value := TScalarValue.FromInteger(AValue); end; class function TScalar.FromPChar(const AValue: TScalarPChar): TScalar; begin Result.Kind := skPChar; Result.Value := TScalarValue.FromPChar(AValue); end; class function TScalar.FromSingle(AValue: Single): TScalar; begin Result.Kind := skSingle; Result.Value := TScalarValue.FromSingle(AValue); end; class function TScalar.FromString(const AValue: String): TScalar; begin Result.Kind := skString; Result.Value := TScalarValue.FromString(AValue); end; class function TScalar.FromTimestamp(AValue: TTimestamp): TScalar; begin Result.Kind := skTimestamp; Result.Value := TScalarValue.FromTimestamp(AValue); end; class function TScalar.FromUInt64(AValue: UInt64): TScalar; begin Result.Kind := skUInt64; Result.Value := TScalarValue.FromUInt64(AValue); end; function TScalar.ToString: String; begin case Kind of skInteger: Result := IntToStr(Value.AsInteger); skInt64: Result := IntToStr(Value.AsInt64); skUInt64: Result := UIntToStr(Value.AsUInt64); skSingle: Result := FloatToStr(Value.AsSingle); skDouble: Result := FloatToStr(Value.AsDouble); skDateTime: Result := DateTimeToStr(Value.AsDateTime); skTimestamp: Result := FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz', TimeStampToDateTime(Value.AsTimestamp)); skBoolean: Result := BoolToStr(Value.AsBoolean, True); skChar: Result := '''' + Value.AsChar + ''''; skPChar: Result := PChar(@Value.AsPChar); // Needs explicit cast/pointer skString: Result := '''' + string(Value.AsString) + ''''; skBytes: Result := '(Bytes)'; skDecimal: Result := FloatToStr(Double(Value.AsDecimal)); else Result := '[Unknown Scalar]'; end; end; { TScalarArray } constructor TScalarArray.Create(AKind: TScalarKind; const AItems: TArray); begin Kind := AKind; Items := AItems; end; { TScalarRecordField } constructor TScalarRecordField.Create(const AName: String; AKind: TScalarKind); begin Name := AName; Kind := AKind; end; { TScalarRecord } constructor TScalarRecord.Create(const ADef: TScalarRecordDefinition; const AFields: TArray); begin Assert(Length(ADef.Fields) = Length(AFields), 'Field definition and value count must match.'); FDef := ADef; FFields := AFields; end; function TScalarRecord.GetDef: TScalarRecordDefinition; begin Result := FDef; end; function TScalarRecord.GetFields: TArray; begin Result := FFields; end; function TScalarRecord.GetItems(const Name: String): TScalar; var i: Integer; fieldDef: TScalarRecordField; begin // Find the index of the field by name (case-insensitive) for i := 0 to High(FDef.Fields) do begin fieldDef := FDef.Fields[i]; if (CompareText(fieldDef.Name, Name) = 0) then begin // Found it. Construct the TScalar result from the kind and value. Result.Create(fieldDef.Kind, FFields[i]); exit; end; end; // If we get here, the field was not found. raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Name]); end; { TScalarSeries } constructor TScalarSeries.Create(AKind: TScalarKind; const AItems: TSeries); begin FKind := AKind; FItems := AItems; end; function TScalarSeries.GetKind: TScalarKind; begin Result := FKind; end; function TScalarSeries.GetItems: TSeries; begin Result := FItems; end; { TScalarRecordSeries } // Implements a time series of scalar records using a chunk array for efficient storage. // Each record's fields are stored sequentially in the TChunkArray. constructor TScalarRecordSeries.Create(const ADef: TScalarRecordDefinition); begin Assert(Length(ADef.Fields) > 0); FDef := ADef; FArray := Default(TChunkArray); FTotalCount := 0; end; procedure TScalarRecordSeries.Add(const Item: TScalarRecord; Lookback: Int64 = -1); begin FArray.Add(Item.Fields, Length(FDef.Fields) * Lookback); inc(FTotalCount); end; function TScalarRecordSeries.CreateMemberSeries(const Field: String): TScalarMemberSeries; begin var elem := FDef.IndexOf(Field); if elem < 0 then raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Field]); Result := TScalarMemberSeries.Create(FDef, FArray, elem); end; function TScalarRecordSeries.GetDef: TScalarRecordDefinition; begin Result := FDef; end; function TScalarRecordSeries.GetItems(Idx: Integer): TScalarRecord; var values: TArray; fieldCount: Integer; begin fieldCount := Length(FDef.Fields); SetLength(values, fieldCount); Move(FArray.ItemRef[(FArray.Count - fieldCount) - (Idx * fieldCount)]^, values[0], sizeof(TScalarValue) * fieldCount); Result.Create(FDef, values); end; constructor TScalarMemberSeries.Create(const ADef: TScalarRecordDefinition; const AArray: TChunkArray; AElement: Integer); begin FDef := ADef; FArray := AArray; FElement := AElement; end; function TScalarMemberSeries.GetCount: Int64; begin Result := FArray.Count; end; function TScalarMemberSeries.GetItems(Idx: Integer): TScalarValue; var values: TArray; fieldCount: Integer; begin fieldCount := Length(FDef.Fields); SetLength(values, fieldCount); Result := FArray.ItemRef[(FArray.Count - fieldCount) - (Idx * fieldCount)]^; end; constructor TScalarRecordDefinition.Create(const AFields: TArray); begin FFields := AFields; end; function TScalarRecordDefinition.IndexOf(const Name: String): Integer; var i: Integer; fieldDef: TScalarRecordField; begin // Find the index of the field by name (case-insensitive) for i := 0 to High(FFields) do if (CompareText(FFields[i].Name, Name) = 0) then exit(i); // If we get here, the field was not found. raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Name]); end; initialization Assert(sizeof(TScalarValue) = 8); end.