Data Types

This commit is contained in:
Michael Schimmel
2025-08-25 18:00:15 +02:00
parent 42110e8471
commit 947060566d
11 changed files with 642 additions and 28 deletions
+2
View File
@@ -68,6 +68,8 @@
* Interfaces benötigen keine GUIDs. Füge keine GUIDs in Interfaces ein und schlagen Sie dies auch nicht vor. Wenn ein gegebenes Interface keine GUID hat, ist das so gewollt. GUIDs werden ausschließlich von mir vergeben. Füge niemals selbst eine GUID hinzu. * Interfaces benötigen keine GUIDs. Füge keine GUIDs in Interfaces ein und schlagen Sie dies auch nicht vor. Wenn ein gegebenes Interface keine GUID hat, ist das so gewollt. GUIDs werden ausschließlich von mir vergeben. Füge niemals selbst eine GUID hinzu.
* Interfaces sind reference counted! Einfaches atomares Locking (z.B. CompareExchange) funktioniert nicht und führt zu bösen Crashes!
* TThread ist in System.Classes definiert. * TThread ist in System.Classes definiert.
* TInterlocked ist in System.SyncObjs definiert. * TInterlocked ist in System.SyncObjs definiert.
+42 -14
View File
@@ -24,7 +24,8 @@ type
implementation implementation
uses uses
System.SyncObjs; System.SyncObjs,
System.Rtti;
type type
// Implements the array data value. // Implements the array data value.
@@ -34,6 +35,7 @@ type
FItems: TArray<IDataValue>; FItems: TArray<IDataValue>;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetElementCount: Integer; function GetElementCount: Integer;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
@@ -42,10 +44,15 @@ type
// Optimized implementation for an array with 0 elements. // Optimized implementation for an array with 0 elements.
TDataArrayValue0 = class(TInterfacedObject, IDataValue, IDataArrayValue) TDataArrayValue0 = class(TInterfacedObject, IDataValue, IDataArrayValue)
strict private
class var
FEmptyTValue: TValue; // Singleton TValue for an empty array
class constructor CreateClass;
private private
FArrayType: IDataArrayType; FArrayType: IDataArrayType;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetElementCount: Integer; function GetElementCount: Integer;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
@@ -59,6 +66,7 @@ type
FItem0: IDataValue; FItem0: IDataValue;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetElementCount: Integer; function GetElementCount: Integer;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
@@ -104,6 +112,17 @@ begin
end; end;
end; end;
function TDataArrayValue.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
i: Integer;
begin
SetLength(tvalueArray, Length(FItems));
for i := 0 to High(FItems) do
tvalueArray[i] := FItems[i].AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataArrayValue.GetElementCount: Integer; function TDataArrayValue.GetElementCount: Integer;
begin begin
Result := Length(FItems); Result := Length(FItems);
@@ -116,6 +135,12 @@ end;
{ TDataArrayValue0 } { TDataArrayValue0 }
class constructor TDataArrayValue0.CreateClass;
begin
// Create a singleton TValue representing an empty dynamic array.
FEmptyTValue := TValue.From<TArray<TValue>>([]);
end;
constructor TDataArrayValue0.Create(const AArrayType: IDataArrayType); constructor TDataArrayValue0.Create(const AArrayType: IDataArrayType);
begin begin
inherited Create; inherited Create;
@@ -127,6 +152,11 @@ begin
Result := '[]'; Result := '[]';
end; end;
function TDataArrayValue0.AsTValue: TValue;
begin
Result := FEmptyTValue;
end;
function TDataArrayValue0.GetDataType: IDataType; function TDataArrayValue0.GetDataType: IDataType;
begin begin
Result := FArrayType; Result := FArrayType;
@@ -156,6 +186,15 @@ begin
Result := '[' + FItem0.AsString + ']'; Result := '[' + FItem0.AsString + ']';
end; end;
function TDataArrayValue1.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
begin
SetLength(tvalueArray, 1);
tvalueArray[0] := FItem0.AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataArrayValue1.GetDataType: IDataType; function TDataArrayValue1.GetDataType: IDataType;
begin begin
Result := FArrayType; Result := FArrayType;
@@ -181,14 +220,13 @@ constructor TDataArrayType.Create(const AElementType: IDataType);
begin begin
inherited Create; inherited Create;
FElementType := AElementType; FElementType := AElementType;
FEmptyValue := nil; FEmptyValue := TDataArrayValue0.Create(Self);
end; end;
function TDataArrayType.CreateValue(const AItems: array of IDataValue): IDataArrayValue; function TDataArrayType.CreateValue(const AItems: array of IDataValue): IDataArrayValue;
var var
item: IDataValue; item: IDataValue;
i: Integer; i: Integer;
newValue: IDataArrayValue;
begin begin
// Validate that all items match the element type of this array type. // Validate that all items match the element type of this array type.
i := 0; i := 0;
@@ -203,17 +241,7 @@ begin
// Use optimized implementations for arrays with 0 or 1 elements. // Use optimized implementations for arrays with 0 or 1 elements.
case Length(AItems) of case Length(AItems) of
0: 0: Result := FEmptyValue;
begin
// Thread-safe lazy initialization for the cached empty array value.
if FEmptyValue = nil then
begin
newValue := TDataArrayValue0.Create(Self);
if TInterlocked.CompareExchange(Pointer(FEmptyValue), Pointer(newValue), nil) <> nil then
newValue := nil; // Another thread won, discard our instance.
end;
Result := FEmptyValue;
end;
1: Result := TDataArrayValue1.Create(Self, AItems); 1: Result := TDataArrayValue1.Create(Self, AItems);
else else
// Use the generic implementation for arrays with more than 1 element. // Use the generic implementation for arrays with more than 1 element.
+9
View File
@@ -27,6 +27,9 @@ type
implementation implementation
uses
System.Rtti;
type type
TDataEnumValue = class(TInterfacedObject, IDataEnumValue) TDataEnumValue = class(TInterfacedObject, IDataEnumValue)
private private
@@ -35,6 +38,7 @@ type
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function GetValue: Integer; function GetValue: Integer;
function AsTValue: TValue;
public public
constructor Create(const ADataType: IDataEnumType; const AValue: Integer); constructor Create(const ADataType: IDataEnumType; const AValue: Integer);
end; end;
@@ -130,4 +134,9 @@ begin
Result := FValue; Result := FValue;
end; end;
function TDataEnumValue.AsTValue: TValue;
begin
Result := TValue.From<Integer>(FValue);
end;
end. end.
+38 -1
View File
@@ -25,7 +25,8 @@ type
implementation implementation
uses uses
System.Math; System.Math,
System.Rtti;
type type
// Implements the float data value. // Implements the float data value.
@@ -38,6 +39,7 @@ type
// IDataValue // IDataValue
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
// IDataFloatValue // IDataFloatValue
function GetValue: Double; function GetValue: Double;
@@ -45,18 +47,28 @@ type
// Special implementation for the value 0.0 (Singleton) // Special implementation for the value 0.0 (Singleton)
TDataFloatValueZero = class(TInterfacedObject, IDataValue, IDataFloatValue) TDataFloatValueZero = class(TInterfacedObject, IDataValue, IDataFloatValue)
strict private
class var
FValue: TValue;
class constructor CreateClass;
public public
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function GetValue: Double; function GetValue: Double;
function AsTValue: TValue;
end; end;
// Special implementation for NaN (Not a Number) (Singleton) // Special implementation for NaN (Not a Number) (Singleton)
TDataFloatValueNaN = class(TInterfacedObject, IDataValue, IDataFloatValue) TDataFloatValueNaN = class(TInterfacedObject, IDataValue, IDataFloatValue)
strict private
class var
FValue: TValue;
class constructor CreateClass;
public public
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function GetValue: Double; function GetValue: Double;
function AsTValue: TValue;
end; end;
{ TDataFloatValue } { TDataFloatValue }
@@ -77,6 +89,11 @@ begin
Result := FloatToStr(FValue); Result := FloatToStr(FValue);
end; end;
function TDataFloatValue.AsTValue: TValue;
begin
Result := TValue.From<Double>(FValue);
end;
function TDataFloatValue.GetValue: Double; function TDataFloatValue.GetValue: Double;
begin begin
Result := FValue; Result := FValue;
@@ -84,6 +101,11 @@ end;
{ TDataFloatValueZero } { TDataFloatValueZero }
class constructor TDataFloatValueZero.CreateClass;
begin
FValue := TValue.From<Double>(0.0);
end;
function TDataFloatValueZero.GetAsString: string; function TDataFloatValueZero.GetAsString: string;
begin begin
Result := '0'; Result := '0';
@@ -99,8 +121,18 @@ begin
Result := 0.0; Result := 0.0;
end; end;
function TDataFloatValueZero.AsTValue: TValue;
begin
Result := FValue;
end;
{ TDataFloatValueNaN } { TDataFloatValueNaN }
class constructor TDataFloatValueNaN.CreateClass;
begin
FValue := TValue.From<Double>(NaN);
end;
function TDataFloatValueNaN.GetAsString: string; function TDataFloatValueNaN.GetAsString: string;
begin begin
Result := 'NaN'; Result := 'NaN';
@@ -116,6 +148,11 @@ begin
Result := NaN; Result := NaN;
end; end;
function TDataFloatValueNaN.AsTValue: TValue;
begin
Result := FValue;
end;
{ TDataFloatType } { TDataFloatType }
class constructor TDataFloatType.CreateClass; class constructor TDataFloatType.CreateClass;
+58
View File
@@ -23,6 +23,9 @@ type
implementation implementation
uses
System.Rtti;
type type
// Implements the ordinal data value. // Implements the ordinal data value.
TDataOrdinalValue = class(TInterfacedObject, IDataValue, IDataOrdinalValue) TDataOrdinalValue = class(TInterfacedObject, IDataValue, IDataOrdinalValue)
@@ -34,6 +37,7 @@ type
// IDataValue // IDataValue
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
// IDataOrdinalValue // IDataOrdinalValue
function GetValue: Int64; function GetValue: Int64;
@@ -41,26 +45,41 @@ type
// Specialized implementation for the value -1. // Specialized implementation for the value -1.
TDataOrdinalValueMinusOne = class(TInterfacedObject, IDataValue, IDataOrdinalValue) TDataOrdinalValueMinusOne = class(TInterfacedObject, IDataValue, IDataOrdinalValue)
strict private
class var
FValue: TValue;
class constructor CreateClass;
public public
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function GetValue: Int64; function GetValue: Int64;
function AsTValue: TValue;
end; end;
// Specialized implementation for the value 0. // Specialized implementation for the value 0.
TDataOrdinalValueZero = class(TInterfacedObject, IDataValue, IDataOrdinalValue) TDataOrdinalValueZero = class(TInterfacedObject, IDataValue, IDataOrdinalValue)
strict private
class var
FValue: TValue;
class constructor CreateClass;
public public
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function GetValue: Int64; function GetValue: Int64;
function AsTValue: TValue;
end; end;
// Specialized implementation for the value 1. // Specialized implementation for the value 1.
TDataOrdinalValueOne = class(TInterfacedObject, IDataValue, IDataOrdinalValue) TDataOrdinalValueOne = class(TInterfacedObject, IDataValue, IDataOrdinalValue)
strict private
class var
FValue: TValue;
class constructor CreateClass;
public public
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function GetValue: Int64; function GetValue: Int64;
function AsTValue: TValue;
end; end;
{ TDataOrdinalValue } { TDataOrdinalValue }
@@ -81,6 +100,12 @@ begin
Result := FValue.ToString; Result := FValue.ToString;
end; end;
function TDataOrdinalValue.AsTValue: TValue;
begin
// Convert Int64 to TValue
Result := TValue.From<Int64>(FValue);
end;
function TDataOrdinalValue.GetValue: Int64; function TDataOrdinalValue.GetValue: Int64;
begin begin
Result := FValue; Result := FValue;
@@ -88,6 +113,11 @@ end;
{ TDataOrdinalValueMinusOne } { TDataOrdinalValueMinusOne }
class constructor TDataOrdinalValueMinusOne.CreateClass;
begin
FValue := TValue.From<Int64>(-1);
end;
function TDataOrdinalValueMinusOne.GetDataType: IDataType; function TDataOrdinalValueMinusOne.GetDataType: IDataType;
begin begin
Result := TDataOrdinalType.Singleton; Result := TDataOrdinalType.Singleton;
@@ -98,6 +128,12 @@ begin
Result := '-1'; Result := '-1';
end; end;
function TDataOrdinalValueMinusOne.AsTValue: TValue;
begin
// Return the singleton TValue instance.
Result := FValue;
end;
function TDataOrdinalValueMinusOne.GetValue: Int64; function TDataOrdinalValueMinusOne.GetValue: Int64;
begin begin
Result := -1; Result := -1;
@@ -105,6 +141,11 @@ end;
{ TDataOrdinalValueZero } { TDataOrdinalValueZero }
class constructor TDataOrdinalValueZero.CreateClass;
begin
FValue := TValue.From<Int64>(0);
end;
function TDataOrdinalValueZero.GetDataType: IDataType; function TDataOrdinalValueZero.GetDataType: IDataType;
begin begin
Result := TDataOrdinalType.Singleton; Result := TDataOrdinalType.Singleton;
@@ -115,6 +156,12 @@ begin
Result := '0'; Result := '0';
end; end;
function TDataOrdinalValueZero.AsTValue: TValue;
begin
// Return the singleton TValue instance.
Result := FValue;
end;
function TDataOrdinalValueZero.GetValue: Int64; function TDataOrdinalValueZero.GetValue: Int64;
begin begin
Result := 0; Result := 0;
@@ -122,6 +169,11 @@ end;
{ TDataOrdinalValueOne } { TDataOrdinalValueOne }
class constructor TDataOrdinalValueOne.CreateClass;
begin
FValue := TValue.From<Int64>(1);
end;
function TDataOrdinalValueOne.GetDataType: IDataType; function TDataOrdinalValueOne.GetDataType: IDataType;
begin begin
Result := TDataOrdinalType.Singleton; Result := TDataOrdinalType.Singleton;
@@ -132,6 +184,12 @@ begin
Result := '1'; Result := '1';
end; end;
function TDataOrdinalValueOne.AsTValue: TValue;
begin
// Return the singleton TValue instance.
Result := FValue;
end;
function TDataOrdinalValueOne.GetValue: Int64; function TDataOrdinalValueOne.GetValue: Int64;
begin begin
Result := 1; Result := 1;
+59 -2
View File
@@ -22,6 +22,7 @@ type
private private
FFields: TArray<TRecordField>; FFields: TArray<TRecordField>;
FFieldMap: TDictionary<string, Integer>; // For fast lookups by name FFieldMap: TDictionary<string, Integer>; // For fast lookups by name
FEmptyValue: IDataRecordValue; // Cached instance for zero-field records
function GetName: String; function GetName: String;
function GetKind: TDataKind; function GetKind: TDataKind;
function GetFieldCount: Integer; function GetFieldCount: Integer;
@@ -36,7 +37,8 @@ type
implementation implementation
uses uses
System.SyncObjs; System.SyncObjs,
System.Rtti;
type type
// Implements the record data value. // Implements the record data value.
@@ -46,6 +48,7 @@ type
FItems: TArray<IDataValue>; FItems: TArray<IDataValue>;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
constructor Create(const ADataType: IDataRecordType; const AItems: array of IDataValue); constructor Create(const ADataType: IDataRecordType; const AItems: array of IDataValue);
@@ -53,10 +56,15 @@ type
// Optimized implementation for a record with 0 fields. // Optimized implementation for a record with 0 fields.
TDataRecordValue0 = class(TInterfacedObject, IDataValue, IDataRecordValue) TDataRecordValue0 = class(TInterfacedObject, IDataValue, IDataRecordValue)
strict private
class var
FEmptyTValue: TValue; // Singleton TValue for an empty record
class constructor CreateClass;
private private
FDataType: IDataRecordType; FDataType: IDataRecordType;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
constructor Create(const ADataType: IDataRecordType); constructor Create(const ADataType: IDataRecordType);
@@ -69,6 +77,7 @@ type
FItem0: IDataValue; FItem0: IDataValue;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
constructor Create(const ADataType: IDataRecordType; const AItems: array of IDataValue); constructor Create(const ADataType: IDataRecordType; const AItems: array of IDataValue);
@@ -81,6 +90,7 @@ type
FItem0, FItem1: IDataValue; FItem0, FItem1: IDataValue;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
constructor Create(const ADataType: IDataRecordType; const AItems: array of IDataValue); constructor Create(const ADataType: IDataRecordType; const AItems: array of IDataValue);
@@ -157,6 +167,17 @@ begin
end; end;
end; end;
function TDataRecordValue.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
i: Integer;
begin
SetLength(tvalueArray, Length(FItems));
for i := 0 to High(FItems) do
tvalueArray[i] := FItems[i].AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataRecordValue.GetItem(Idx: Integer): IDataValue; function TDataRecordValue.GetItem(Idx: Integer): IDataValue;
begin begin
Result := FItems[Idx]; Result := FItems[Idx];
@@ -164,6 +185,14 @@ end;
{ TDataRecordValue0 } { TDataRecordValue0 }
class constructor TDataRecordValue0.CreateClass;
var
emptyArray: TArray<TValue>;
begin
SetLength(emptyArray, 0);
FEmptyTValue := TValue.From<TArray<TValue>>(emptyArray);
end;
constructor TDataRecordValue0.Create(const ADataType: IDataRecordType); constructor TDataRecordValue0.Create(const ADataType: IDataRecordType);
begin begin
inherited Create; inherited Create;
@@ -175,6 +204,11 @@ begin
Result := '<>'; Result := '<>';
end; end;
function TDataRecordValue0.AsTValue: TValue;
begin
Result := FEmptyTValue;
end;
function TDataRecordValue0.GetDataType: IDataType; function TDataRecordValue0.GetDataType: IDataType;
begin begin
Result := FDataType; Result := FDataType;
@@ -202,6 +236,15 @@ begin
Result := Format('<%s: %s>', [recordType.Fields[0].Name, FItem0.AsString]); Result := Format('<%s: %s>', [recordType.Fields[0].Name, FItem0.AsString]);
end; end;
function TDataRecordValue1.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
begin
SetLength(tvalueArray, 1);
tvalueArray[0] := FItem0.AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataRecordValue1.GetDataType: IDataType; function TDataRecordValue1.GetDataType: IDataType;
begin begin
Result := FDataType; Result := FDataType;
@@ -249,6 +292,16 @@ begin
end; end;
end; end;
function TDataRecordValue2.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
begin
SetLength(tvalueArray, 2);
tvalueArray[0] := FItem0.AsTValue;
tvalueArray[1] := FItem1.AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataRecordValue2.GetDataType: IDataType; function TDataRecordValue2.GetDataType: IDataType;
begin begin
Result := FDataType; Result := FDataType;
@@ -282,6 +335,10 @@ begin
FFields[i] := field; FFields[i] := field;
FFieldMap.Add(field.Name, i); FFieldMap.Add(field.Name, i);
end; end;
// Pre-create the singleton value instance if this is a zero-field record.
if (Length(FFields) = 0) then
FEmptyValue := TDataRecordValue0.Create(Self);
end; end;
destructor TDataRecordType.Destroy; destructor TDataRecordType.Destroy;
@@ -306,7 +363,7 @@ begin
// Use optimized implementations for records with 0, 1, or 2 fields. // Use optimized implementations for records with 0, 1, or 2 fields.
case Length(FFields) of case Length(FFields) of
0: Result := TDataRecordValue0.Create(Self); 0: Result := FEmptyValue; // Return the pre-cached instance.
1: Result := TDataRecordValue1.Create(Self, AItems); 1: Result := TDataRecordValue1.Create(Self, AItems);
2: Result := TDataRecordValue2.Create(Self, AItems); 2: Result := TDataRecordValue2.Create(Self, AItems);
else else
+17
View File
@@ -21,6 +21,9 @@ type
implementation implementation
uses
System.Rtti;
type type
TDataTextValue = class(TInterfacedObject, IDataValue, IDataTextValue) TDataTextValue = class(TInterfacedObject, IDataValue, IDataTextValue)
private private
@@ -28,6 +31,7 @@ type
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function GetValue: string; function GetValue: string;
function AsTValue: TValue;
public public
constructor Create(const AValue: string); constructor Create(const AValue: string);
end; end;
@@ -37,11 +41,13 @@ type
strict private strict private
class var class var
FSingleton: IDataTextValue; FSingleton: IDataTextValue;
FEmptyTValue: TValue;
class constructor CreateClass; class constructor CreateClass;
private private
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function GetValue: string; function GetValue: string;
function AsTValue: TValue;
public public
class property Singleton: IDataTextValue read FSingleton; class property Singleton: IDataTextValue read FSingleton;
end; end;
@@ -95,11 +101,17 @@ begin
Result := FValue; Result := FValue;
end; end;
function TDataTextValue.AsTValue: TValue;
begin
Result := TValue.From<string>(FValue);
end;
{ TDataTextValueEmpty } { TDataTextValueEmpty }
class constructor TDataTextValueEmpty.CreateClass; class constructor TDataTextValueEmpty.CreateClass;
begin begin
FSingleton := TDataTextValueEmpty.Create; FSingleton := TDataTextValueEmpty.Create;
FEmptyTValue := TValue.From<string>('');
end; end;
function TDataTextValueEmpty.GetAsString: string; function TDataTextValueEmpty.GetAsString: string;
@@ -117,4 +129,9 @@ begin
Result := ''; Result := '';
end; end;
function TDataTextValueEmpty.AsTValue: TValue;
begin
Result := FEmptyTValue;
end;
end. end.
+9
View File
@@ -21,6 +21,9 @@ type
implementation implementation
uses
System.Rtti;
type type
TDataTimestampValue = class(TInterfacedObject, IDataValue, IDataTimestampValue) TDataTimestampValue = class(TInterfacedObject, IDataValue, IDataTimestampValue)
private private
@@ -28,6 +31,7 @@ type
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function GetValue: TDateTime; function GetValue: TDateTime;
function AsTValue: TValue;
public public
constructor Create(const AValue: TDateTime); constructor Create(const AValue: TDateTime);
end; end;
@@ -77,4 +81,9 @@ begin
Result := FValue; Result := FValue;
end; end;
function TDataTimestampValue.AsTValue: TValue;
begin
Result := TValue.From<TDateTime>(FValue);
end;
end. end.
+86
View File
@@ -23,6 +23,9 @@ type
implementation implementation
uses
System.Rtti;
type type
// Implements the simple tuple data value. // Implements the simple tuple data value.
TDataTupleValue = class(TInterfacedObject, IDataValue, IDataTupleValue) TDataTupleValue = class(TInterfacedObject, IDataValue, IDataTupleValue)
@@ -30,6 +33,7 @@ type
FItems: TArray<IDataValue>; FItems: TArray<IDataValue>;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItemCount: Integer; function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
@@ -41,10 +45,12 @@ type
strict private strict private
class var class var
FSingleton: IDataTupleValue; FSingleton: IDataTupleValue;
FEmptyTValue: TValue;
class constructor CreateClass; class constructor CreateClass;
private private
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItemCount: Integer; function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
@@ -57,6 +63,7 @@ type
FItem0: IDataValue; FItem0: IDataValue;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItemCount: Integer; function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
@@ -69,6 +76,7 @@ type
FItem0, FItem1: IDataValue; FItem0, FItem1: IDataValue;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItemCount: Integer; function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
@@ -81,6 +89,7 @@ type
FItem0, FItem1, FItem2: IDataValue; FItem0, FItem1, FItem2: IDataValue;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItemCount: Integer; function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
@@ -93,6 +102,7 @@ type
FItem0, FItem1, FItem2, FItem3: IDataValue; FItem0, FItem1, FItem2, FItem3: IDataValue;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItemCount: Integer; function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
@@ -105,6 +115,7 @@ type
FItem0, FItem1, FItem2, FItem3, FItem4: IDataValue; FItem0, FItem1, FItem2, FItem3, FItem4: IDataValue;
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
function GetItemCount: Integer; function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue; function GetItem(Idx: Integer): IDataValue;
public public
@@ -149,6 +160,17 @@ begin
end; end;
end; end;
function TDataTupleValue.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
i: Integer;
begin
SetLength(tvalueArray, Length(FItems));
for i := 0 to High(FItems) do
tvalueArray[i] := FItems[i].AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataTupleValue.GetItem(Idx: Integer): IDataValue; function TDataTupleValue.GetItem(Idx: Integer): IDataValue;
begin begin
Result := FItems[Idx]; Result := FItems[Idx];
@@ -162,8 +184,12 @@ end;
{ TDataTupleValue0 } { TDataTupleValue0 }
class constructor TDataTupleValue0.CreateClass; class constructor TDataTupleValue0.CreateClass;
var
emptyArray: TArray<TValue>;
begin begin
FSingleton := TDataTupleValue0.Create; FSingleton := TDataTupleValue0.Create;
SetLength(emptyArray, 0);
FEmptyTValue := TValue.From<TArray<TValue>>(emptyArray);
end; end;
function TDataTupleValue0.GetAsString: string; function TDataTupleValue0.GetAsString: string;
@@ -171,6 +197,11 @@ begin
Result := '()'; Result := '()';
end; end;
function TDataTupleValue0.AsTValue: TValue;
begin
Result := FEmptyTValue;
end;
function TDataTupleValue0.GetDataType: IDataType; function TDataTupleValue0.GetDataType: IDataType;
begin begin
Result := TDataTupleType.Singleton; Result := TDataTupleType.Singleton;
@@ -199,6 +230,15 @@ begin
Result := '(' + FItem0.AsString + ')'; Result := '(' + FItem0.AsString + ')';
end; end;
function TDataTupleValue1.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
begin
SetLength(tvalueArray, 1);
tvalueArray[0] := FItem0.AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataTupleValue1.GetDataType: IDataType; function TDataTupleValue1.GetDataType: IDataType;
begin begin
Result := TDataTupleType.Singleton; Result := TDataTupleType.Singleton;
@@ -232,6 +272,16 @@ begin
Result := '(' + FItem0.AsString + ', ' + FItem1.AsString + ')'; Result := '(' + FItem0.AsString + ', ' + FItem1.AsString + ')';
end; end;
function TDataTupleValue2.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
begin
SetLength(tvalueArray, 2);
tvalueArray[0] := FItem0.AsTValue;
tvalueArray[1] := FItem1.AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataTupleValue2.GetDataType: IDataType; function TDataTupleValue2.GetDataType: IDataType;
begin begin
Result := TDataTupleType.Singleton; Result := TDataTupleType.Singleton;
@@ -281,6 +331,17 @@ begin
end; end;
end; end;
function TDataTupleValue3.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
begin
SetLength(tvalueArray, 3);
tvalueArray[0] := FItem0.AsTValue;
tvalueArray[1] := FItem1.AsTValue;
tvalueArray[2] := FItem2.AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataTupleValue3.GetDataType: IDataType; function TDataTupleValue3.GetDataType: IDataType;
begin begin
Result := TDataTupleType.Singleton; Result := TDataTupleType.Singleton;
@@ -334,6 +395,18 @@ begin
end; end;
end; end;
function TDataTupleValue4.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
begin
SetLength(tvalueArray, 4);
tvalueArray[0] := FItem0.AsTValue;
tvalueArray[1] := FItem1.AsTValue;
tvalueArray[2] := FItem2.AsTValue;
tvalueArray[3] := FItem3.AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataTupleValue4.GetDataType: IDataType; function TDataTupleValue4.GetDataType: IDataType;
begin begin
Result := TDataTupleType.Singleton; Result := TDataTupleType.Singleton;
@@ -391,6 +464,19 @@ begin
end; end;
end; end;
function TDataTupleValue5.AsTValue: TValue;
var
tvalueArray: TArray<TValue>;
begin
SetLength(tvalueArray, 5);
tvalueArray[0] := FItem0.AsTValue;
tvalueArray[1] := FItem1.AsTValue;
tvalueArray[2] := FItem2.AsTValue;
tvalueArray[3] := FItem3.AsTValue;
tvalueArray[4] := FItem4.AsTValue;
Result := TValue.From<TArray<TValue>>(tvalueArray);
end;
function TDataTupleValue5.GetDataType: IDataType; function TDataTupleValue5.GetDataType: IDataType;
begin begin
Result := TDataTupleType.Singleton; Result := TDataTupleType.Singleton;
+85 -1
View File
@@ -4,7 +4,8 @@ interface
uses uses
System.Generics.Collections, System.Generics.Collections,
System.SysUtils; System.SysUtils,
System.RTTI;
type type
TDataKind = (dkOrdinal, dkFloat, dkText, dkTimestamp, dkRecord, dkTuple, dkArray, dkEnum); TDataKind = (dkOrdinal, dkFloat, dkText, dkTimestamp, dkRecord, dkTuple, dkArray, dkEnum);
@@ -19,6 +20,7 @@ type
IDataValue = interface IDataValue = interface
function GetDataType: IDataType; function GetDataType: IDataType;
function GetAsString: string; function GetAsString: string;
function AsTValue: TValue;
property DataType: IDataType read GetDataType; property DataType: IDataType read GetDataType;
property AsString: string read GetAsString; property AsString: string read GetAsString;
end; end;
@@ -122,6 +124,7 @@ type
strict private strict private
FDataType: IDataType; FDataType: IDataType;
function GetName: String; inline; function GetName: String; inline;
function GetKind: TDataKind; inline;
class var class var
FArrayTypeRegistry: TDictionary<IDataType, IDataArrayType>; FArrayTypeRegistry: TDictionary<IDataType, IDataArrayType>;
@@ -132,6 +135,8 @@ type
public public
constructor Create(const ADataType: IDataType); constructor Create(const ADataType: IDataType);
class procedure ClearRegistries; static;
class operator Implicit(const A: IDataType): TDataType; overload; class operator Implicit(const A: IDataType): TDataType; overload;
class operator Implicit(const A: TDataType): IDataType; overload; class operator Implicit(const A: TDataType): IDataType; overload;
@@ -146,8 +151,19 @@ type
class function RecordOf(const Fields: array of TRecordField): IDataRecordType; overload; static; class function RecordOf(const Fields: array of TRecordField): IDataRecordType; overload; static;
class function EnumOf(const AName: string; const AIdentifiers: array of string): IDataEnumType; static; class function EnumOf(const AName: string; const AIdentifiers: array of string): IDataEnumType; static;
// Casting
function AsOrdinal: IDataOrdinalType;
function AsFloat: IDataFloatType;
function AsText: IDataTextType;
function AsTimestamp: IDataTimestampType;
function AsRecord: IDataRecordType;
function AsTuple: IDataTupleType;
function AsArray: IDataArrayType;
function AsEnum: IDataEnumType;
property DataType: IDataType read FDataType; property DataType: IDataType read FDataType;
property Name: String read GetName; property Name: String read GetName;
property Kind: TDataKind read GetKind;
end; end;
TDataValue = record TDataValue = record
@@ -239,6 +255,69 @@ begin
end; end;
end; end;
// Implemented missing caster functions
function TDataType.AsArray: IDataArrayType;
begin
if (FDataType.Kind <> dkArray) then
raise EInvalidCast.Create('Array expected');
Result := IDataArrayType(FDataType);
end;
function TDataType.AsEnum: IDataEnumType;
begin
if (FDataType.Kind <> dkEnum) then
raise EInvalidCast.Create('Enum expected');
Result := IDataEnumType(FDataType);
end;
function TDataType.AsFloat: IDataFloatType;
begin
if (FDataType.Kind <> dkFloat) then
raise EInvalidCast.Create('Float expected');
Result := IDataFloatType(FDataType);
end;
function TDataType.AsOrdinal: IDataOrdinalType;
begin
if (FDataType.Kind <> dkOrdinal) then
raise EInvalidCast.Create('Ordinal expected');
Result := IDataOrdinalType(FDataType);
end;
function TDataType.AsRecord: IDataRecordType;
begin
if (FDataType.Kind <> dkRecord) then
raise EInvalidCast.Create('Record expected');
Result := IDataRecordType(FDataType);
end;
function TDataType.AsText: IDataTextType;
begin
if (FDataType.Kind <> dkText) then
raise EInvalidCast.Create('Text expected');
Result := IDataTextType(FDataType);
end;
function TDataType.AsTimestamp: IDataTimestampType;
begin
if (FDataType.Kind <> dkTimestamp) then
raise EInvalidCast.Create('Timestamp expected');
Result := IDataTimestampType(FDataType);
end;
function TDataType.AsTuple: IDataTupleType;
begin
if (FDataType.Kind <> dkTuple) then
raise EInvalidCast.Create('Tuple expected');
Result := IDataTupleType(FDataType);
end;
class procedure TDataType.ClearRegistries;
begin
FArrayTypeRegistry.Clear;
FRecordTypeRegistry.Clear;
end;
class function TDataType.Float: IDataFloatType; class function TDataType.Float: IDataFloatType;
begin begin
Result := TDataFloatType.Singleton; Result := TDataFloatType.Singleton;
@@ -287,6 +366,11 @@ begin
Result := TDataEnumType.Create(AName, AIdentifiers); Result := TDataEnumType.Create(AName, AIdentifiers);
end; end;
function TDataType.GetKind: TDataKind;
begin
Result := FDataType.Kind;
end;
class function TDataType.Text: IDataTextType; class function TDataType.Text: IDataTextType;
begin begin
Result := TDataTextType.Singleton; Result := TDataTextType.Singleton;
+237 -10
View File
@@ -7,39 +7,59 @@ uses
type type
[TestFixture] [TestFixture]
[IgnoreMemoryLeaks]
TMyTestObject = class TMyTestObject = class
public public
[Test] [TestFixtureTearDown]
procedure TestCreateRecords; procedure FixtureTearDown;
[Test]
procedure TestRecords;
[Test]
procedure TestRecords_Generic;
[Test] [Test]
procedure TestTuples; procedure TestTuples;
[Test]
procedure TestTuples_Generic;
[Test] [Test]
procedure TestArrays; procedure TestArrays;
[Test]
procedure TestArrays_OptimizedAndGeneric;
[Test] [Test]
procedure TestTexts; procedure TestTexts;
[Test]
procedure TestTexts_OptimizedEmpty;
[Test]
procedure TestFloats_OptimizedAndGeneric;
[Test] [Test]
procedure TestTimestamps; procedure TestTimestamps;
[Test] [Test]
procedure TestEnums; procedure TestEnums;
[Test] [Test]
procedure TestAsString; procedure TestAsString;
[Test]
procedure TestAsTValue;
end; end;
implementation implementation
uses uses
System.SysUtils, System.SysUtils,
System.Math,
System.Rtti,
System.TypInfo,
Myc.Data.Types; Myc.Data.Types;
{ TMyTestObject } { TMyTestObject }
procedure TMyTestObject.TestCreateRecords; procedure TMyTestObject.FixtureTearDown;
begin
// Clear global type caches to release any interfaces created during tests.
// This prevents access violations on shutdown from dangling pointers in the registries.
TDataType.ClearRegistries;
end;
procedure TMyTestObject.TestRecords;
var var
intType: IDataType; intType: IDataType;
floatType: IDataType; floatType: IDataType;
@@ -48,6 +68,7 @@ var
idValue: IDataOrdinalValue; idValue: IDataOrdinalValue;
floatValue: IDataFloatValue; floatValue: IDataFloatValue;
begin begin
// This test covers the optimized implementation for 2 fields.
// --- 1. Setup: Define base types and a record structure --- // --- 1. Setup: Define base types and a record structure ---
intType := TDataType.Ordinal; intType := TDataType.Ordinal;
floatType := TDataType.Float; floatType := TDataType.Float;
@@ -117,6 +138,30 @@ begin
); );
end; end;
procedure TMyTestObject.TestRecords_Generic;
var
dataType: IDataRecordType;
dataValue: IDataRecordValue;
begin
// Test the generic implementation for records with > 2 fields.
dataType :=
TDataType.RecordOf(
[
TRecordField.Create('A', TDataType.Ordinal),
TRecordField.Create('B', TDataType.Text),
TRecordField.Create('C', TDataType.Float)
]
);
dataValue := dataType.CreateValue([TDataValue.FromOrdinal(1), TDataValue.FromText('two'), TDataValue.FromFloat(3.0)]);
Assert.IsNotNull(dataValue, 'Generic record value should be created');
Assert.AreEqual(3, dataType.FieldCount, 'Generic record should have 3 fields');
Assert.AreEqual(Int64(1), TDataValue(dataValue.Items[0]).AsOrdinal.Value, 'Field A is incorrect');
Assert.AreEqual('two', TDataValue(dataValue.Items[1]).AsText.Value, 'Field B is incorrect');
Assert.AreEqual(3.0, TDataValue(dataValue.Items[2]).AsFloat.Value, 'Field C is incorrect');
Assert.AreEqual('<A: 1, B: two, C: 3>', dataValue.AsString, 'Generic record AsString is incorrect');
end;
procedure TMyTestObject.TestTuples; procedure TMyTestObject.TestTuples;
var var
intValue: IDataValue; intValue: IDataValue;
@@ -124,6 +169,7 @@ var
tuple1, tuple2, tuple3: IDataTupleValue; tuple1, tuple2, tuple3: IDataTupleValue;
type1, type2, type3: IDataType; type1, type2, type3: IDataType;
begin begin
// This test covers optimized implementations for 1 and 2 elements.
// --- 1. Setup: Create some values --- // --- 1. Setup: Create some values ---
intValue := TDataValue.FromOrdinal(123); intValue := TDataValue.FromOrdinal(123);
floatValue := TDataValue.FromFloat(45.67); floatValue := TDataValue.FromFloat(45.67);
@@ -154,6 +200,29 @@ begin
Assert.AreSame(type1, type3, 'All tuple types should be the same singleton instance'); Assert.AreSame(type1, type3, 'All tuple types should be the same singleton instance');
end; end;
procedure TMyTestObject.TestTuples_Generic;
var
tupleValue: IDataTupleValue;
begin
// Test the generic implementation for tuples with > 5 elements.
tupleValue :=
TDataValue.FromTuple(
[
TDataValue.FromOrdinal(1),
TDataValue.FromOrdinal(2),
TDataValue.FromOrdinal(3),
TDataValue.FromOrdinal(4),
TDataValue.FromOrdinal(5),
TDataValue.FromOrdinal(6)
]
);
Assert.IsNotNull(tupleValue, 'Generic tuple value should be created');
Assert.AreEqual(6, tupleValue.ItemCount, 'Generic tuple should have 6 items');
Assert.AreEqual(Int64(6), TDataValue(tupleValue.Items[5]).AsOrdinal.Value, 'Last item is incorrect');
Assert.AreEqual('(1, 2, 3, 4, 5, 6)', tupleValue.AsString, 'Generic tuple AsString is incorrect');
end;
procedure TMyTestObject.TestArrays; procedure TMyTestObject.TestArrays;
var var
intType: IDataType; intType: IDataType;
@@ -210,6 +279,35 @@ begin
); );
end; end;
procedure TMyTestObject.TestArrays_OptimizedAndGeneric;
var
arrayType: IDataArrayType;
empty1, empty2, single, generic: IDataArrayValue;
begin
arrayType := TDataType.ArrayOfType(TDataType.Ordinal);
// Test optimized path for 0 elements (cached singleton per type)
empty1 := arrayType.CreateValue([]);
empty2 := arrayType.CreateValue([]);
Assert.IsNotNull(empty1, 'Empty array value should be created');
Assert.AreSame(empty1, empty2, 'Empty array value should be cached and reused');
Assert.AreEqual(0, empty1.ElementCount, 'Empty array should have 0 elements');
Assert.AreEqual('[]', empty1.AsString, 'Empty array AsString is incorrect');
// Test optimized path for 1 element
single := arrayType.CreateValue([TDataValue.FromOrdinal(42)]);
Assert.IsNotNull(single, 'Single element array should be created');
Assert.AreEqual(1, single.ElementCount, 'Single element array should have 1 element');
Assert.AreEqual(Int64(42), TDataValue(single.Items[0]).AsOrdinal.Value, 'Single element value is incorrect');
Assert.AreEqual('[42]', single.AsString, 'Single element array AsString is incorrect');
// Test generic path for > 1 elements
generic := arrayType.CreateValue([TDataValue.FromOrdinal(1), TDataValue.FromOrdinal(2), TDataValue.FromOrdinal(3)]);
Assert.IsNotNull(generic, 'Generic array should be created');
Assert.AreEqual(3, generic.ElementCount, 'Generic array should have 3 elements');
Assert.AreEqual('[1, 2, 3]', generic.AsString, 'Generic array AsString is incorrect');
end;
procedure TMyTestObject.TestTexts; procedure TMyTestObject.TestTexts;
var var
textType: IDataTextType; textType: IDataTextType;
@@ -244,6 +342,47 @@ begin
.WillRaise(procedure begin TDataValue(dataValue).AsText; end, EInvalidCast, 'Casting non-text to AsText should raise EInvalidCast'); .WillRaise(procedure begin TDataValue(dataValue).AsText; end, EInvalidCast, 'Casting non-text to AsText should raise EInvalidCast');
end; end;
procedure TMyTestObject.TestTexts_OptimizedEmpty;
var
empty1, empty2: IDataTextValue;
begin
// Test the singleton implementation for the empty string.
empty1 := TDataValue.FromText('');
empty2 := TDataValue.FromText('');
Assert.IsNotNull(empty1, 'Empty text value should be created');
Assert.AreSame(empty1, empty2, 'Empty text value should be a singleton');
Assert.AreEqual('', empty1.Value, 'Value of empty text should be empty');
Assert.AreEqual('', empty1.AsString, 'AsString of empty text should be empty');
end;
procedure TMyTestObject.TestFloats_OptimizedAndGeneric;
var
zero1, zero2, nan1, nan2, generic: IDataFloatValue;
begin
// Test singleton for 0.0
zero1 := TDataValue.FromFloat(0.0);
zero2 := TDataValue.FromFloat(0.0);
Assert.IsNotNull(zero1, 'Zero float should be created');
Assert.AreSame(zero1, zero2, 'Zero float should be a singleton');
Assert.AreEqual(0.0, zero1.Value, 'Value of zero float is incorrect');
// Test singleton for NaN
nan1 := TDataValue.FromFloat(NaN);
nan2 := TDataValue.FromFloat(NaN);
Assert.IsNotNull(nan1, 'NaN float should be created');
Assert.AreSame(nan1, nan2, 'NaN float should be a singleton');
Assert.IsTrue(IsNaN(nan1.Value), 'Value of NaN float should be NaN');
Assert.AreEqual('NaN', nan1.AsString, 'NaN AsString is incorrect');
// Test generic path
generic := TDataValue.FromFloat(123.45);
Assert.IsNotNull(generic, 'Generic float should be created');
Assert.AreNotSame(zero1, generic, 'Generic float should not be the zero singleton');
Assert.AreNotSame(nan1, generic, 'Generic float should not be the NaN singleton');
Assert.AreEqual(123.45, generic.Value, 'Value of generic float is incorrect');
end;
procedure TMyTestObject.TestTimestamps; procedure TMyTestObject.TestTimestamps;
var var
tsType: IDataTimestampType; tsType: IDataTimestampType;
@@ -390,7 +529,95 @@ begin
Assert.AreEqual('(10, Tuple)', tupleValue.AsString, 'Tuple AsString incorrect'); Assert.AreEqual('(10, Tuple)', tupleValue.AsString, 'Tuple AsString incorrect');
end; end;
initialization procedure TMyTestObject.TestAsTValue;
TDUnitX.RegisterTestFixture(TMyTestObject); var
dataValue: IDataValue;
tv, element: TValue;
now: TDateTime;
colorType: IDataEnumType;
intArrayType: IDataArrayType;
personType: IDataRecordType;
begin
// --- Ordinal ---
dataValue := TDataValue.FromOrdinal(123);
tv := dataValue.AsTValue;
Assert.IsFalse(tv.IsEmpty, 'Ordinal TValue should not be empty');
Assert.AreEqual(tkInt64, tv.Kind, 'Ordinal TValue kind should be tkInt64');
Assert.AreEqual(Int64(123), tv.AsInt64, 'Ordinal TValue content is incorrect');
// --- Float ---
dataValue := TDataValue.FromFloat(45.67);
tv := dataValue.AsTValue;
Assert.IsFalse(tv.IsEmpty, 'Float TValue should not be empty');
Assert.AreEqual(tkFloat, tv.Kind, 'Float TValue kind should be tkFloat');
Assert.AreEqual(45.67, tv.AsExtended, 'Float TValue content is incorrect');
// --- Text ---
dataValue := TDataValue.FromText('Hello');
tv := dataValue.AsTValue;
Assert.IsFalse(tv.IsEmpty, 'Text TValue should not be empty');
Assert.AreEqual(tkUString, tv.Kind, 'Text TValue kind should be tkUString');
Assert.AreEqual('Hello', tv.AsString, 'Text TValue content is incorrect');
// --- Timestamp ---
now := System.SysUtils.Now;
dataValue := TDataValue.FromTimestamp(now);
tv := dataValue.AsTValue;
Assert.IsFalse(tv.IsEmpty, 'Timestamp TValue should not be empty');
Assert.AreEqual(tkFloat, tv.Kind, 'Timestamp TValue kind is incorrect');
Assert.AreEqual(now, tv.AsType<TDateTime>, 'Timestamp TValue content is incorrect');
// --- Enum ---
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
dataValue := colorType.CreateValue('Green');
tv := dataValue.AsTValue;
Assert.IsFalse(tv.IsEmpty, 'Enum TValue should not be empty');
Assert.AreEqual(tkInteger, tv.Kind, 'Enum TValue kind should be tkInteger');
Assert.AreEqual(1, tv.AsInteger, 'Enum TValue content is incorrect');
// --- Array ---
intArrayType := TDataType.ArrayOfType(TDataType.Ordinal);
dataValue := intArrayType.CreateValue([TDataValue.FromOrdinal(10), TDataValue.FromOrdinal(20)]);
tv := dataValue.AsTValue;
Assert.IsFalse(tv.IsEmpty, 'Array TValue should not be empty');
Assert.IsTrue(tv.IsArray, 'Array TValue should be an array');
Assert.AreEqual(tkDynArray, tv.Kind, 'Array TValue kind should be tkDynArray');
Assert.AreEqual(NativeInt(2), tv.GetArrayLength, 'Array TValue length is incorrect');
// Correctly unwrap the nested TValue before asserting kind and value
element := tv.GetArrayElement(0).AsType<TValue>;
Assert.AreEqual(tkInt64, element.Kind, 'Unwrapped array element 0 kind is incorrect');
Assert.AreEqual(Int64(10), element.AsInt64, 'Array TValue element 0 is incorrect');
element := tv.GetArrayElement(1).AsType<TValue>;
Assert.AreEqual(Int64(20), element.AsInt64, 'Array TValue element 1 is incorrect');
// Check empty array
dataValue := intArrayType.CreateValue([]);
tv := dataValue.AsTValue;
Assert.IsTrue(tv.IsArray, 'Empty Array TValue should be an array');
Assert.AreEqual(NativeInt(0), tv.GetArrayLength, 'Empty Array TValue length is incorrect');
// --- Record ---
personType := TDataType.RecordOf([TRecordField.Create('ID', TDataType.Ordinal), TRecordField.Create('Name', TDataType.Text)]);
dataValue := personType.CreateValue([TDataValue.FromOrdinal(1), TDataValue.FromText('Bob')]);
tv := dataValue.AsTValue;
Assert.IsFalse(tv.IsEmpty, 'Record TValue should not be empty');
Assert.IsTrue(tv.IsArray, 'Record TValue should be represented as an array');
Assert.AreEqual(tkDynArray, tv.Kind, 'Record TValue kind should be tkDynArray');
Assert.AreEqual(NativeInt(2), tv.GetArrayLength, 'Record TValue length is incorrect');
Assert.AreEqual(Int64(1), tv.GetArrayElement(0).AsType<TValue>.AsInt64, 'Record TValue element 0 is incorrect');
Assert.AreEqual('Bob', tv.GetArrayElement(1).AsType<TValue>.AsString, 'Record TValue element 1 is incorrect');
// --- Tuple ---
dataValue := TDataValue.FromTuple([TDataValue.FromOrdinal(99), TDataValue.FromText('Red')]);
tv := dataValue.AsTValue;
Assert.IsFalse(tv.IsEmpty, 'Tuple TValue should not be empty');
Assert.IsTrue(tv.IsArray, 'Tuple TValue should be represented as an array');
Assert.AreEqual(tkDynArray, tv.Kind, 'Tuple TValue kind should be tkDynArray');
Assert.AreEqual(NativeInt(2), tv.GetArrayLength, 'Tuple TValue length is incorrect');
Assert.AreEqual(Int64(99), tv.GetArrayElement(0).AsType<TValue>.AsInt64, 'Tuple TValue element 0 is incorrect');
Assert.AreEqual('Red', tv.GetArrayElement(1).AsType<TValue>.AsString, 'Tuple TValue element 1 is incorrect');
end;
end. end.