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 FromDecimal(AValue: TDecimal): 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; // 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; class function StringToKind(const AName: string): TScalarKind; static; function ToString: String; class operator Add(const A, B: TScalar): TScalar; class operator Divide(const A, B: TScalar): TScalar; class operator Equal(const A, B: TScalar): Boolean; //TODO implementieren class operator GreaterThan(const A, B: TScalar): Boolean; class operator GreaterThanOrEqual(const A, B: TScalar): Boolean; class operator LessThan(const A, B: TScalar): Boolean; class operator LessThanOrEqual(const A, B: TScalar): Boolean; class operator Multiply(const A, B: TScalar): TScalar; class operator Negative(const A: TScalar): TScalar; class operator NotEqual(const A, B: TScalar): Boolean; class operator Round(const A: TScalar): TScalar; class operator Subtract(const A, B: TScalar): TScalar; class operator Trunc(const A: TScalar): TScalar; end; TScalarKindHelper = record helper for TScalarKind public 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; class function TScalar.StringToKind(const AName: string): TScalarKind; begin if SameText(AName, 'integer') then Result := skInteger else if SameText(AName, 'int64') then Result := skInt64 else if SameText(AName, 'uint64') then Result := skUInt64 else if SameText(AName, 'single') then Result := skSingle else if SameText(AName, 'double') then Result := skDouble else if SameText(AName, 'datetime') then Result := skDateTime else if SameText(AName, 'timestamp') then Result := skTimestamp else if SameText(AName, 'boolean') then Result := skBoolean else if SameText(AName, 'char') then Result := skChar else if SameText(AName, 'pchar') then Result := skPChar else if SameText(AName, 'string') then Result := skString else if SameText(AName, 'bytes') then Result := skBytes else if SameText(AName, 'decimal') then Result := skDecimal else raise EArgumentException.CreateFmt('Unknown scalar type name: "%s"', [AName]); 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; class operator TScalar.Add(const A, B: TScalar): TScalar; begin // Fast path for identical types if (A.Kind = B.Kind) then begin Result.Kind := A.Kind; case A.Kind of skInteger: Result.Value.AsInteger := A.Value.AsInteger + B.Value.AsInteger; skInt64: Result.Value.AsInt64 := A.Value.AsInt64 + B.Value.AsInt64; skUInt64: Result.Value.AsUInt64 := A.Value.AsUInt64 + B.Value.AsUInt64; skSingle: Result.Value.AsSingle := A.Value.AsSingle + B.Value.AsSingle; skDouble: Result.Value.AsDouble := A.Value.AsDouble + B.Value.AsDouble; skDecimal: Result.Value.AsDecimal := A.Value.AsDecimal + B.Value.AsDecimal; else raise EArgumentException.CreateFmt('Operator Add not supported for type %s', [A.Kind.ToString]); end; exit; end; // Slow path for mixed types with compatible conversion const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; const ORDINAL_KINDS: set of TScalarKind = [skInteger, skInt64, skUInt64]; const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + ORDINAL_KINDS + [skDecimal]; if not (((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) or ((A.Kind = skDateTime) and (B.Kind in ORDINAL_KINDS + FLOAT_KINDS)) or ((B.Kind = skDateTime) and (A.Kind in ORDINAL_KINDS + FLOAT_KINDS))) then raise EArgumentException.CreateFmt('Operator Add requires compatible types, but got %s and %s', [A.Kind.ToString, B.Kind.ToString]); if (A.Kind = skDecimal) or (B.Kind = skDecimal) then begin if (A.Kind in FLOAT_KINDS) or (B.Kind in FLOAT_KINDS) then raise EArgumentException .CreateFmt('Operator Add cannot mix Decimal and floating-point types (%s, %s)', [A.Kind.ToString, B.Kind.ToString]); var valA, valB: TDecimal; case A.Kind of skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); skDecimal: valA := A.Value.AsDecimal; end; case B.Kind of skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); skDecimal: valB := B.Value.AsDecimal; end; Result := TScalar.FromDecimal(valA + valB); end else if (A.Kind = skDateTime) or (B.Kind = skDateTime) then begin if A.Kind = skDateTime then begin var dtPart := A.Value.AsDateTime; var numPart: Double := 0; case B.Kind of skInteger: numPart := B.Value.AsInteger; skInt64: numPart := B.Value.AsInt64; skUInt64: numPart := B.Value.AsUInt64; skSingle: numPart := B.Value.AsSingle; skDouble: numPart := B.Value.AsDouble; end; Result := TScalar.FromDateTime(dtPart + numPart); end else begin var dtPart := B.Value.AsDateTime; var numPart: Double := 0; case A.Kind of skInteger: numPart := A.Value.AsInteger; skInt64: numPart := A.Value.AsInt64; skUInt64: numPart := A.Value.AsUInt64; skSingle: numPart := A.Value.AsSingle; skDouble: numPart := A.Value.AsDouble; end; Result := TScalar.FromDateTime(dtPart + numPart); end; end else if (A.Kind = skDouble) or (B.Kind = skDouble) then begin var dblA: Double := 0; var dblB: Double := 0; case A.Kind of skInteger: dblA := A.Value.AsInteger; skInt64: dblA := A.Value.AsInt64; skUInt64: dblA := A.Value.AsUInt64; skSingle: dblA := A.Value.AsSingle; skDouble: dblA := A.Value.AsDouble; end; case B.Kind of skInteger: dblB := B.Value.AsInteger; skInt64: dblB := B.Value.AsInt64; skUInt64: dblB := B.Value.AsUInt64; skSingle: dblB := B.Value.AsSingle; skDouble: dblB := B.Value.AsDouble; end; Result := TScalar.FromDouble(dblA + dblB); end else if (A.Kind = skSingle) or (B.Kind = skSingle) then begin var sngA: Single := 0; var sngB: Single := 0; case A.Kind of skInteger: sngA := A.Value.AsInteger; skInt64: sngA := A.Value.AsInt64; skUInt64: sngA := A.Value.AsUInt64; skSingle: sngA := A.Value.AsSingle; end; case B.Kind of skInteger: sngB := B.Value.AsInteger; skInt64: sngB := B.Value.AsInt64; skUInt64: sngB := B.Value.AsUInt64; skSingle: sngB := B.Value.AsSingle; end; Result := TScalar.FromSingle(sngA + sngB); end else if (A.Kind = skUInt64) or (B.Kind = skUInt64) then begin var u64A: UInt64 := 0; var u64B: UInt64 := 0; case A.Kind of skInteger: u64A := UInt64(A.Value.AsInteger); skInt64: u64A := UInt64(A.Value.AsInt64); skUInt64: u64A := A.Value.AsUInt64; end; case B.Kind of skInteger: u64B := UInt64(B.Value.AsInteger); skInt64: u64B := UInt64(B.Value.AsInt64); skUInt64: u64B := B.Value.AsUInt64; end; Result := TScalar.FromUInt64(u64A + u64B); end else if (A.Kind = skInt64) or (B.Kind = skInt64) then begin var i64A: Int64 := 0; var i64B: Int64 := 0; case A.Kind of skInteger: i64A := A.Value.AsInteger; skInt64: i64A := A.Value.AsInt64; end; case B.Kind of skInteger: i64B := B.Value.AsInteger; skInt64: i64B := B.Value.AsInt64; end; Result := TScalar.FromInt64(i64A + i64B); end; end; class operator TScalar.Divide(const A, B: TScalar): TScalar; begin // Fast path for identical types if (A.Kind = B.Kind) then begin case A.Kind of skInteger: Result := TScalar.FromDouble(A.Value.AsInteger / B.Value.AsInteger); skInt64: Result := TScalar.FromDouble(A.Value.AsInt64 / B.Value.AsInt64); skUInt64: Result := TScalar.FromDouble(A.Value.AsUInt64 / B.Value.AsUInt64); skSingle: Result := TScalar.FromSingle(A.Value.AsSingle / B.Value.AsSingle); skDouble: Result := TScalar.FromDouble(A.Value.AsDouble / B.Value.AsDouble); skDecimal: Result := TScalar.FromDecimal(A.Value.AsDecimal / B.Value.AsDecimal); else raise EArgumentException.CreateFmt('Operator Divide not supported for type %s', [A.Kind.ToString]); end; exit; end; // Slow path for mixed types const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + [skInteger, skInt64, skUInt64, skDecimal]; if not ((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) then raise EArgumentException.CreateFmt('Operator Divide not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]); if (A.Kind = skDecimal) or (B.Kind = skDecimal) then begin if (A.Kind in FLOAT_KINDS) or (B.Kind in FLOAT_KINDS) then raise EArgumentException .CreateFmt('Operator Divide cannot mix Decimal and floating-point types (%s, %s)', [A.Kind.ToString, B.Kind.ToString]); var valA, valB: TDecimal; case A.Kind of skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); skDecimal: valA := A.Value.AsDecimal; end; case B.Kind of skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); skDecimal: valB := B.Value.AsDecimal; end; Result := TScalar.FromDecimal(valA / valB); end else begin var dblA: Double := 0.0; var dblB: Double := 0.0; case A.Kind of skInteger: dblA := A.Value.AsInteger; skInt64: dblA := A.Value.AsInt64; skUInt64: dblA := A.Value.AsUInt64; skSingle: dblA := A.Value.AsSingle; skDouble: dblA := A.Value.AsDouble; end; case B.Kind of skInteger: dblB := B.Value.AsInteger; skInt64: dblB := B.Value.AsInt64; skUInt64: dblB := B.Value.AsUInt64; skSingle: dblB := B.Value.AsSingle; skDouble: dblB := B.Value.AsDouble; end; Result := TScalar.FromDouble(dblA / dblB); end; end; class operator TScalar.Equal(const A, B: TScalar): Boolean; begin // Fast path for identical types if (A.Kind = B.Kind) then begin case A.Kind of skInteger: Result := (A.Value.AsInteger = B.Value.AsInteger); skInt64: Result := (A.Value.AsInt64 = B.Value.AsInt64); skUInt64: Result := (A.Value.AsUInt64 = B.Value.AsUInt64); skSingle: Result := (A.Value.AsSingle = B.Value.AsSingle); skDouble: Result := (A.Value.AsDouble = B.Value.AsDouble); skDateTime: Result := (A.Value.AsDateTime = B.Value.AsDateTime); skTimestamp: Result := (A.Value.AsTimestamp.Time = B.Value.AsTimestamp.Time) and (A.Value.AsTimestamp.Date = B.Value.AsTimestamp.Date); skBoolean: Result := (A.Value.AsBoolean = B.Value.AsBoolean); skChar: Result := (A.Value.AsChar = B.Value.AsChar); skPChar: Result := (StrComp(PChar(@A.Value.AsPChar), PChar(@B.Value.AsPChar)) = 0); skString: Result := (A.Value.AsString = B.Value.AsString); skBytes: Result := CompareMem(@A.Value.AsBytes, @B.Value.AsBytes, SizeOf(TScalarBytes)); skDecimal: Result := (A.Value.AsDecimal = B.Value.AsDecimal); else Result := False; end; exit; end; // Slow path for mixed, but compatible, types const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + [skInteger, skInt64, skUInt64, skDecimal]; if not ((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) then begin Result := False; exit; end; if ((A.Kind = skDecimal) and (B.Kind in FLOAT_KINDS)) or ((B.Kind = skDecimal) and (A.Kind in FLOAT_KINDS)) then begin Result := False; exit; end; if (A.Kind = skDecimal) or (B.Kind = skDecimal) then begin var valA, valB: TDecimal; case A.Kind of skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); skDecimal: valA := A.Value.AsDecimal; else Result := False; exit; end; case B.Kind of skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); skDecimal: valB := B.Value.AsDecimal; else Result := False; exit; end; Result := (valA = valB); end else begin var dblA: Double := 0.0; var dblB: Double := 0.0; case A.Kind of skInteger: dblA := A.Value.AsInteger; skInt64: dblA := A.Value.AsInt64; skUInt64: dblA := A.Value.AsUInt64; skSingle: dblA := A.Value.AsSingle; skDouble: dblA := A.Value.AsDouble; end; case B.Kind of skInteger: dblB := B.Value.AsInteger; skInt64: dblB := B.Value.AsInt64; skUInt64: dblB := B.Value.AsUInt64; skSingle: dblB := B.Value.AsSingle; skDouble: dblB := B.Value.AsDouble; end; Result := (dblA = dblB); end; end; class operator TScalar.GreaterThan(const A, B: TScalar): Boolean; begin // Fast path for identical types if (A.Kind = B.Kind) then begin case A.Kind of skInteger: Result := (A.Value.AsInteger > B.Value.AsInteger); skInt64: Result := (A.Value.AsInt64 > B.Value.AsInt64); skUInt64: Result := (A.Value.AsUInt64 > B.Value.AsUInt64); skSingle: Result := (A.Value.AsSingle > B.Value.AsSingle); skDouble: Result := (A.Value.AsDouble > B.Value.AsDouble); skDateTime: Result := (A.Value.AsDateTime > B.Value.AsDateTime); skDecimal: Result := (A.Value.AsDecimal > B.Value.AsDecimal); skChar: Result := (A.Value.AsChar > B.Value.AsChar); skString: Result := (A.Value.AsString > B.Value.AsString); else raise EArgumentException.CreateFmt('Operator GreaterThan not supported for type %s', [A.Kind.ToString]); end; exit; end; // Slow path for mixed, but compatible, types const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + [skInteger, skInt64, skUInt64, skDecimal]; if not ((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) then raise EArgumentException.CreateFmt('Cannot compare %s and %s', [A.Kind.ToString, B.Kind.ToString]); if ((A.Kind = skDecimal) and (B.Kind in FLOAT_KINDS)) or ((B.Kind = skDecimal) and (A.Kind in FLOAT_KINDS)) then raise EArgumentException.CreateFmt('Cannot compare Decimal and floating-point types (%s, %s)', [A.Kind.ToString, B.Kind.ToString]); if (A.Kind = skDecimal) or (B.Kind = skDecimal) then begin var valA, valB: TDecimal; case A.Kind of skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); skDecimal: valA := A.Value.AsDecimal; else raise EArgumentException.CreateFmt('Internal error comparing %s', [A.Kind.ToString]); end; case B.Kind of skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); skDecimal: valB := B.Value.AsDecimal; else raise EArgumentException.CreateFmt('Internal error comparing %s', [B.Kind.ToString]); end; Result := (valA > valB); end else begin var dblA, dblB: Double; dblA := 0.0; dblB := 0.0; case A.Kind of skInteger: dblA := A.Value.AsInteger; skInt64: dblA := A.Value.AsInt64; skUInt64: dblA := A.Value.AsUInt64; skSingle: dblA := A.Value.AsSingle; skDouble: dblA := A.Value.AsDouble; end; case B.Kind of skInteger: dblB := B.Value.AsInteger; skInt64: dblB := B.Value.AsInt64; skUInt64: dblB := B.Value.AsUInt64; skSingle: dblB := B.Value.AsSingle; skDouble: dblB := B.Value.AsDouble; end; Result := (dblA > dblB); end; end; class operator TScalar.GreaterThanOrEqual(const A, B: TScalar): Boolean; begin Result := (A > B) or (A = B); end; class operator TScalar.LessThan(const A, B: TScalar): Boolean; begin // Re-use GreaterThan logic to avoid code duplication Result := B > A; end; class operator TScalar.LessThanOrEqual(const A, B: TScalar): Boolean; begin Result := (A < B) or (A = B); end; class operator TScalar.Multiply(const A, B: TScalar): TScalar; begin // Fast path for identical types if (A.Kind = B.Kind) then begin Result.Kind := A.Kind; case A.Kind of skInteger: Result.Value.AsInteger := A.Value.AsInteger * B.Value.AsInteger; skInt64: Result.Value.AsInt64 := A.Value.AsInt64 * B.Value.AsInt64; skUInt64: Result.Value.AsUInt64 := A.Value.AsUInt64 * B.Value.AsUInt64; skSingle: Result.Value.AsSingle := A.Value.AsSingle * B.Value.AsSingle; skDouble: Result.Value.AsDouble := A.Value.AsDouble * B.Value.AsDouble; skDecimal: Result.Value.AsDecimal := A.Value.AsDecimal * B.Value.AsDecimal; else raise EArgumentException.CreateFmt('Operator Multiply not supported for type %s', [A.Kind.ToString]); end; exit; end; // Slow path for mixed types const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + [skInteger, skInt64, skUInt64, skDecimal]; if not ((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) then raise EArgumentException.CreateFmt('Operator Multiply not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]); if (A.Kind = skDecimal) or (B.Kind = skDecimal) then begin if (A.Kind in FLOAT_KINDS) or (B.Kind in FLOAT_KINDS) then raise EArgumentException .CreateFmt('Operator Multiply cannot mix Decimal and floating-point types (%s, %s)', [A.Kind.ToString, B.Kind.ToString]); var valA, valB: TDecimal; case A.Kind of skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); skDecimal: valA := A.Value.AsDecimal; end; case B.Kind of skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); skDecimal: valB := B.Value.AsDecimal; end; Result := TScalar.FromDecimal(valA * valB); end else if (A.Kind = skDouble) or (B.Kind = skDouble) then begin var dblA: Double := 0.0; var dblB: Double := 0.0; case A.Kind of skInteger: dblA := A.Value.AsInteger; skInt64: dblA := A.Value.AsInt64; skUInt64: dblA := A.Value.AsUInt64; skSingle: dblA := A.Value.AsSingle; skDouble: dblA := A.Value.AsDouble; end; case B.Kind of skInteger: dblB := B.Value.AsInteger; skInt64: dblB := B.Value.AsInt64; skUInt64: dblB := B.Value.AsUInt64; skSingle: dblB := B.Value.AsSingle; skDouble: dblB := B.Value.AsDouble; end; Result := TScalar.FromDouble(dblA * dblB); end else if (A.Kind = skSingle) or (B.Kind = skSingle) then begin var sngA: Single := 0.0; var sngB: Single := 0.0; case A.Kind of skInteger: sngA := A.Value.AsInteger; skInt64: sngA := A.Value.AsInt64; skUInt64: sngA := A.Value.AsUInt64; skSingle: sngA := A.Value.AsSingle; end; case B.Kind of skInteger: sngB := B.Value.AsInteger; skInt64: sngB := B.Value.AsInt64; skUInt64: sngB := B.Value.AsUInt64; skSingle: sngB := B.Value.AsSingle; end; Result := TScalar.FromSingle(sngA * sngB); end else if (A.Kind = skUInt64) or (B.Kind = skUInt64) then begin var u64A: UInt64 := 0; var u64B: UInt64 := 0; case A.Kind of skInteger: u64A := UInt64(A.Value.AsInteger); skInt64: u64A := UInt64(A.Value.AsInt64); skUInt64: u64A := A.Value.AsUInt64; end; case B.Kind of skInteger: u64B := UInt64(B.Value.AsInteger); skInt64: u64B := UInt64(B.Value.AsInt64); skUInt64: u64B := B.Value.AsUInt64; end; Result := TScalar.FromUInt64(u64A * u64B); end else if (A.Kind = skInt64) or (B.Kind = skInt64) then begin var i64A: Int64 := 0; var i64B: Int64 := 0; case A.Kind of skInteger: i64A := A.Value.AsInteger; skInt64: i64A := A.Value.AsInt64; end; case B.Kind of skInteger: i64B := B.Value.AsInteger; skInt64: i64B := B.Value.AsInt64; end; Result := TScalar.FromInt64(i64A * i64B); end else begin Result := TScalar.FromInteger(A.Value.AsInteger * B.Value.AsInteger); end; end; class operator TScalar.Negative(const A: TScalar): TScalar; begin Result.Kind := A.Kind; case A.Kind of skInteger: Result.Value.AsInteger := -A.Value.AsInteger; skInt64: Result.Value.AsInt64 := -A.Value.AsInt64; skSingle: Result.Value.AsSingle := -A.Value.AsSingle; skDouble: Result.Value.AsDouble := -A.Value.AsDouble; skDecimal: Result.Value.AsDecimal := -A.Value.AsDecimal; else raise EArgumentException.CreateFmt('Operator Negative not supported for type %s', [A.Kind.ToString]); end; end; class operator TScalar.NotEqual(const A, B: TScalar): Boolean; begin Result := not (A = B); end; class operator TScalar.Round(const A: TScalar): TScalar; begin case A.Kind of skSingle: Result := TScalar.FromInt64(System.Round(A.Value.AsSingle)); skDouble: Result := TScalar.FromInt64(System.Round(A.Value.AsDouble)); skDateTime: Result := TScalar.FromInt64(System.Round(A.Value.AsDateTime)); else raise EArgumentException.CreateFmt('Operator Round not supported for type %s', [A.Kind.ToString]); end; end; class operator TScalar.Subtract(const A, B: TScalar): TScalar; begin // Fast path for identical types if (A.Kind = B.Kind) then begin if (A.Kind = skDateTime) then begin Result := TScalar.FromDouble(A.Value.AsDateTime - B.Value.AsDateTime); exit; end; Result.Kind := A.Kind; case A.Kind of skInteger: Result.Value.AsInteger := A.Value.AsInteger - B.Value.AsInteger; skInt64: Result.Value.AsInt64 := A.Value.AsInt64 - B.Value.AsInt64; skUInt64: Result.Value.AsUInt64 := A.Value.AsUInt64 - B.Value.AsUInt64; skSingle: Result.Value.AsSingle := A.Value.AsSingle - B.Value.AsSingle; skDouble: Result.Value.AsDouble := A.Value.AsDouble - B.Value.AsDouble; skDecimal: Result.Value.AsDecimal := A.Value.AsDecimal - B.Value.AsDecimal; else raise EArgumentException.CreateFmt('Operator Subtract not supported for type %s', [A.Kind.ToString]); end; exit; end; // Slow path for mixed types const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; const ORDINAL_KINDS: set of TScalarKind = [skInteger, skInt64, skUInt64]; const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + ORDINAL_KINDS + [skDecimal]; if not (((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) or ((A.Kind = skDateTime) and (B.Kind in NUMERIC_KINDS + [skDateTime]))) then raise EArgumentException.CreateFmt('Operator Subtract not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]); if (A.Kind = skDecimal) or (B.Kind = skDecimal) then begin if (A.Kind in FLOAT_KINDS) or (B.Kind in FLOAT_KINDS) then raise EArgumentException .CreateFmt('Operator Subtract cannot mix Decimal and floating-point types (%s, %s)', [A.Kind.ToString, B.Kind.ToString]); var valA, valB: TDecimal; case A.Kind of skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); skDecimal: valA := A.Value.AsDecimal; end; case B.Kind of skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); skDecimal: valB := B.Value.AsDecimal; end; Result := TScalar.FromDecimal(valA - valB); end else if (A.Kind = skDateTime) then begin var numPart: Double := 0.0; case B.Kind of skInteger: numPart := B.Value.AsInteger; skInt64: numPart := B.Value.AsInt64; skUInt64: numPart := B.Value.AsUInt64; skSingle: numPart := B.Value.AsSingle; skDouble: numPart := B.Value.AsDouble; skDateTime: numPart := B.Value.AsDateTime; end; if B.Kind = skDateTime then Result := TScalar.FromDouble(A.Value.AsDateTime - numPart) else Result := TScalar.FromDateTime(A.Value.AsDateTime - numPart); end else if (B.Kind = skDateTime) then begin raise EArgumentException.CreateFmt('Operator Subtract not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]); end else if (A.Kind = skDouble) or (B.Kind = skDouble) then begin var dblA: Double := 0.0; var dblB: Double := 0.0; case A.Kind of skInteger: dblA := A.Value.AsInteger; skInt64: dblA := A.Value.AsInt64; skUInt64: dblA := A.Value.AsUInt64; skSingle: dblA := A.Value.AsSingle; skDouble: dblA := A.Value.AsDouble; end; case B.Kind of skInteger: dblB := B.Value.AsInteger; skInt64: dblB := B.Value.AsInt64; skUInt64: dblB := B.Value.AsUInt64; skSingle: dblB := B.Value.AsSingle; skDouble: dblB := B.Value.AsDouble; end; Result := TScalar.FromDouble(dblA - dblB); end else if (A.Kind = skSingle) or (B.Kind = skSingle) then begin var sngA: Single := 0.0; var sngB: Single := 0.0; case A.Kind of skInteger: sngA := A.Value.AsInteger; skInt64: sngA := A.Value.AsInt64; skUInt64: sngA := A.Value.AsUInt64; skSingle: sngA := A.Value.AsSingle; end; case B.Kind of skInteger: sngB := B.Value.AsInteger; skInt64: sngB := B.Value.AsInt64; skUInt64: sngB := B.Value.AsUInt64; skSingle: sngB := B.Value.AsSingle; end; Result := TScalar.FromSingle(sngA - sngB); end else if (A.Kind = skUInt64) or (B.Kind = skUInt64) then begin var u64A: UInt64 := 0; var u64B: UInt64 := 0; case A.Kind of skInteger: u64A := UInt64(A.Value.AsInteger); skInt64: u64A := UInt64(A.Value.AsInt64); skUInt64: u64A := A.Value.AsUInt64; end; case B.Kind of skInteger: u64B := UInt64(B.Value.AsInteger); skInt64: u64B := UInt64(B.Value.AsInt64); skUInt64: u64B := B.Value.AsUInt64; end; Result := TScalar.FromUInt64(u64A - u64B); end else if (A.Kind = skInt64) or (B.Kind = skInt64) then begin var i64A: Int64 := 0; var i64B: Int64 := 0; case A.Kind of skInteger: i64A := A.Value.AsInteger; skInt64: i64A := A.Value.AsInt64; end; case B.Kind of skInteger: i64B := B.Value.AsInteger; skInt64: i64B := B.Value.AsInt64; end; Result := TScalar.FromInt64(i64A - i64B); end else begin Result := TScalar.FromInteger(A.Value.AsInteger - B.Value.AsInteger); end; end; class operator TScalar.Trunc(const A: TScalar): TScalar; begin case A.Kind of skSingle: Result := TScalar.FromInt64(System.Trunc(A.Value.AsSingle)); skDouble: Result := TScalar.FromInt64(System.Trunc(A.Value.AsDouble)); skDateTime: Result := TScalar.FromInt64(System.Trunc(A.Value.AsDateTime)); else raise EArgumentException.CreateFmt('Operator Trunc not supported for type %s', [A.Kind.ToString]); 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; function TScalarKindHelper.ToString: string; begin case Self of skInteger: Result := 'integer'; skInt64: Result := 'int64'; skUInt64: Result := 'uint64'; skSingle: Result := 'single'; skDouble: Result := 'double'; skDateTime: Result := 'datetime'; skTimestamp: Result := 'timestamp'; skBoolean: Result := 'boolean'; skChar: Result := 'char'; skPChar: Result := 'pchar'; skString: Result := 'string'; skBytes: Result := 'bytes'; skDecimal: Result := 'decimal'; else Result := 'unknown'; end; end; initialization Assert(sizeof(TScalarValue) = 8); end.