unit Myc.Data.Scalar; 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 private FKind: TScalarKind; FItems: TSeries; public // Creates a new scalar series. constructor Create(AKind: TScalarKind; const AItems: TSeries); property Kind: TScalarKind read FKind; property Items: TSeries read FItems; end; TScalarMemberSeries = record private FArray: TChunkArray; FElementIdx: Integer; FKind: TScalarKind; FElemCount: Integer; function GetCount: Int64; function GetItems(Idx: Integer): TScalar; public constructor Create(AKind: TScalarKind; const AArray: TChunkArray; AElementIdx, AElemCount: Integer); property Count: Int64 read GetCount; property Items[Idx: Integer]: TScalar read GetItems; default; property Kind: TScalarKind read FKind; end; // A time series of scalar tuples, optimized for memory and access speed. TScalarTupleSeries = record private FDef: TArray; FArray: TChunkArray; FTotalCount: Int64; function GetCount: Int64; function GetItems(Idx: Integer): TScalarTuple; public constructor Create(const ADef: TArray); procedure Add(const Item: TScalarTuple; Lookback: Int64 = -1); function CreateMemberSeries(Idx: Integer): TScalarMemberSeries; property Count: Int64 read GetCount; property Def: TArray read FDef; property Items[Idx: Integer]: TScalarTuple read GetItems; default; property TotalCount: Int64 read FTotalCount; end; // A time series of scalar records, optimized for memory and access speed. TScalarRecordSeries = record private FDef: TScalarRecordDefinition; FArray: TChunkArray; FTotalCount: Int64; function GetCount: 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 AsTupleSeries: TScalarTupleSeries; function CreateMemberSeries(const Field: String): TScalarMemberSeries; property Count: Int64 read GetCount; 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 idx: Integer; begin idx := FDef.IndexOf(Name); if idx < 0 then raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Name]); Result.Create(FDef.Fields[idx].Kind, FFields[idx]); end; { TScalarSeries } constructor TScalarSeries.Create(AKind: TScalarKind; const AItems: TSeries); begin FKind := AKind; FItems := AItems; end; { TScalarTupleSeries } constructor TScalarTupleSeries.Create(const ADef: TArray); begin // Implements a time series of scalar tuples using a chunk array for efficient storage. Assert(Length(ADef) > 0, 'Tuple definition cannot be empty.'); FDef := ADef; FTotalCount := 0; end; procedure TScalarTupleSeries.Add(const Item: TScalarTuple; Lookback: Int64 = -1); var values: TArray; i: Integer; tupleSize: Integer; maxCount: Integer; begin tupleSize := Length(FDef); Assert(Length(Item) = tupleSize, 'Tuple does not match series definition.'); // Extract TScalarValue from TScalarTuple. SetLength(values, tupleSize); for i := 0 to tupleSize - 1 do begin Assert(Item[i].Kind = FDef[i], 'Tuple element kind mismatch.'); values[i] := Item[i].Value; end; if Lookback < 0 then maxCount := -1 else maxCount := tupleSize * Lookback; FArray.Add(values, maxCount); inc(FTotalCount); end; function TScalarTupleSeries.CreateMemberSeries(Idx: Integer): TScalarMemberSeries; var tupleSize: Integer; begin tupleSize := Length(FDef); if (Idx < 0) or (Idx >= tupleSize) then raise EArgumentException.CreateFmt('Index %d is out of bounds for a tuple of size %d.', [Idx, tupleSize]); // Creates a series view for a specific member of the tuple. Result := TScalarMemberSeries.Create(FDef[Idx], FArray, Idx, tupleSize); end; function TScalarTupleSeries.GetCount: Int64; begin if Length(FDef) = 0 then exit(0); Result := FArray.Count div Length(FDef); end; function TScalarTupleSeries.GetItems(Idx: Integer): TScalarTuple; var values: TArray; tupleSize, i: Integer; begin tupleSize := Length(FDef); Assert(tupleSize > 0); Assert((Idx >= 0) and (Idx < (FArray.Count div tupleSize))); // Create a temporary array to hold the values for one tuple. SetLength(values, tupleSize); // Copy the tuple data from the chunk array (latest item is at the end). Move(FArray.ItemRef[(FArray.Count - tupleSize) - (Idx * tupleSize)]^, values[0], sizeof(TScalarValue) * tupleSize); // Reconstruct the TScalarTuple from the values and the definition. SetLength(Result, tupleSize); for i := 0 to tupleSize - 1 do Result[i] := TScalar.Create(FDef[i], values[i]); end; { TScalarMemberSeries } constructor TScalarMemberSeries.Create(AKind: TScalarKind; const AArray: TChunkArray; AElementIdx, AElemCount: Integer); begin Assert(AArray.Count mod AElemCount = 0); Assert(AElementIdx >= 0); Assert(AElementIdx < AElemCount); FKind := AKind; FArray := AArray; FElementIdx := AElementIdx; FElemCount := AElemCount; end; function TScalarMemberSeries.GetCount: Int64; begin if FElemCount = 0 then exit(0); Result := FArray.Count div FElemCount; end; function TScalarMemberSeries.GetItems(Idx: Integer): TScalar; begin Result.Create(FKind, FArray.Items[(FArray.Count - FElemCount) - (Idx * FElemCount) + FElementIdx]); end; { TScalarRecordDefinition } constructor TScalarRecordDefinition.Create(const AFields: TArray); begin FFields := AFields; end; function TScalarRecordDefinition.IndexOf(const Name: String): Integer; var i: Integer; begin for i := 0 to High(FFields) do begin if (CompareText(FFields[i].Name, Name) = 0) then exit(i); end; Result := -1; 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; 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.AsTupleSeries: TScalarTupleSeries; var tupleDef: TArray; i: Integer; begin // Extract the kinds from the record definition to create a tuple definition. SetLength(tupleDef, Length(FDef.Fields)); for i := 0 to High(FDef.Fields) do tupleDef[i] := FDef.Fields[i].Kind; // Create the tuple series with the new definition. Result := TScalarTupleSeries.Create(tupleDef); // The underlying data is compatible, so we can share it. Result.FArray := FArray; Result.FTotalCount := 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.Fields[elem].Kind, FArray, elem, Length(FDef.Fields)); end; function TScalarRecordSeries.GetCount: Int64; begin Result := FArray.Count div Length(FDef.Fields); 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; initialization Assert(sizeof(TScalarValue) = 8); end.