Data Types
This commit is contained in:
@@ -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 sind reference counted! Einfaches atomares Locking (z.B. CompareExchange) funktioniert nicht und führt zu bösen Crashes!
|
||||
|
||||
* TThread ist in System.Classes definiert.
|
||||
* TInterlocked ist in System.SyncObjs definiert.
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ type
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.SyncObjs;
|
||||
System.SyncObjs,
|
||||
System.Rtti;
|
||||
|
||||
type
|
||||
// Implements the array data value.
|
||||
@@ -34,6 +35,7 @@ type
|
||||
FItems: TArray<IDataValue>;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetElementCount: Integer;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
@@ -42,10 +44,15 @@ type
|
||||
|
||||
// Optimized implementation for an array with 0 elements.
|
||||
TDataArrayValue0 = class(TInterfacedObject, IDataValue, IDataArrayValue)
|
||||
strict private
|
||||
class var
|
||||
FEmptyTValue: TValue; // Singleton TValue for an empty array
|
||||
class constructor CreateClass;
|
||||
private
|
||||
FArrayType: IDataArrayType;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetElementCount: Integer;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
@@ -59,6 +66,7 @@ type
|
||||
FItem0: IDataValue;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetElementCount: Integer;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
@@ -104,6 +112,17 @@ begin
|
||||
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;
|
||||
begin
|
||||
Result := Length(FItems);
|
||||
@@ -116,6 +135,12 @@ end;
|
||||
|
||||
{ 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);
|
||||
begin
|
||||
inherited Create;
|
||||
@@ -127,6 +152,11 @@ begin
|
||||
Result := '[]';
|
||||
end;
|
||||
|
||||
function TDataArrayValue0.AsTValue: TValue;
|
||||
begin
|
||||
Result := FEmptyTValue;
|
||||
end;
|
||||
|
||||
function TDataArrayValue0.GetDataType: IDataType;
|
||||
begin
|
||||
Result := FArrayType;
|
||||
@@ -156,6 +186,15 @@ begin
|
||||
Result := '[' + FItem0.AsString + ']';
|
||||
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;
|
||||
begin
|
||||
Result := FArrayType;
|
||||
@@ -181,14 +220,13 @@ constructor TDataArrayType.Create(const AElementType: IDataType);
|
||||
begin
|
||||
inherited Create;
|
||||
FElementType := AElementType;
|
||||
FEmptyValue := nil;
|
||||
FEmptyValue := TDataArrayValue0.Create(Self);
|
||||
end;
|
||||
|
||||
function TDataArrayType.CreateValue(const AItems: array of IDataValue): IDataArrayValue;
|
||||
var
|
||||
item: IDataValue;
|
||||
i: Integer;
|
||||
newValue: IDataArrayValue;
|
||||
begin
|
||||
// Validate that all items match the element type of this array type.
|
||||
i := 0;
|
||||
@@ -203,17 +241,7 @@ begin
|
||||
|
||||
// Use optimized implementations for arrays with 0 or 1 elements.
|
||||
case Length(AItems) of
|
||||
0:
|
||||
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;
|
||||
0: Result := FEmptyValue;
|
||||
1: Result := TDataArrayValue1.Create(Self, AItems);
|
||||
else
|
||||
// Use the generic implementation for arrays with more than 1 element.
|
||||
|
||||
@@ -27,6 +27,9 @@ type
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Rtti;
|
||||
|
||||
type
|
||||
TDataEnumValue = class(TInterfacedObject, IDataEnumValue)
|
||||
private
|
||||
@@ -35,6 +38,7 @@ type
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function GetValue: Integer;
|
||||
function AsTValue: TValue;
|
||||
public
|
||||
constructor Create(const ADataType: IDataEnumType; const AValue: Integer);
|
||||
end;
|
||||
@@ -130,4 +134,9 @@ begin
|
||||
Result := FValue;
|
||||
end;
|
||||
|
||||
function TDataEnumValue.AsTValue: TValue;
|
||||
begin
|
||||
Result := TValue.From<Integer>(FValue);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
@@ -25,7 +25,8 @@ type
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Math;
|
||||
System.Math,
|
||||
System.Rtti;
|
||||
|
||||
type
|
||||
// Implements the float data value.
|
||||
@@ -38,6 +39,7 @@ type
|
||||
// IDataValue
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
|
||||
// IDataFloatValue
|
||||
function GetValue: Double;
|
||||
@@ -45,18 +47,28 @@ type
|
||||
|
||||
// Special implementation for the value 0.0 (Singleton)
|
||||
TDataFloatValueZero = class(TInterfacedObject, IDataValue, IDataFloatValue)
|
||||
strict private
|
||||
class var
|
||||
FValue: TValue;
|
||||
class constructor CreateClass;
|
||||
public
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function GetValue: Double;
|
||||
function AsTValue: TValue;
|
||||
end;
|
||||
|
||||
// Special implementation for NaN (Not a Number) (Singleton)
|
||||
TDataFloatValueNaN = class(TInterfacedObject, IDataValue, IDataFloatValue)
|
||||
strict private
|
||||
class var
|
||||
FValue: TValue;
|
||||
class constructor CreateClass;
|
||||
public
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function GetValue: Double;
|
||||
function AsTValue: TValue;
|
||||
end;
|
||||
|
||||
{ TDataFloatValue }
|
||||
@@ -77,6 +89,11 @@ begin
|
||||
Result := FloatToStr(FValue);
|
||||
end;
|
||||
|
||||
function TDataFloatValue.AsTValue: TValue;
|
||||
begin
|
||||
Result := TValue.From<Double>(FValue);
|
||||
end;
|
||||
|
||||
function TDataFloatValue.GetValue: Double;
|
||||
begin
|
||||
Result := FValue;
|
||||
@@ -84,6 +101,11 @@ end;
|
||||
|
||||
{ TDataFloatValueZero }
|
||||
|
||||
class constructor TDataFloatValueZero.CreateClass;
|
||||
begin
|
||||
FValue := TValue.From<Double>(0.0);
|
||||
end;
|
||||
|
||||
function TDataFloatValueZero.GetAsString: string;
|
||||
begin
|
||||
Result := '0';
|
||||
@@ -99,8 +121,18 @@ begin
|
||||
Result := 0.0;
|
||||
end;
|
||||
|
||||
function TDataFloatValueZero.AsTValue: TValue;
|
||||
begin
|
||||
Result := FValue;
|
||||
end;
|
||||
|
||||
{ TDataFloatValueNaN }
|
||||
|
||||
class constructor TDataFloatValueNaN.CreateClass;
|
||||
begin
|
||||
FValue := TValue.From<Double>(NaN);
|
||||
end;
|
||||
|
||||
function TDataFloatValueNaN.GetAsString: string;
|
||||
begin
|
||||
Result := 'NaN';
|
||||
@@ -116,6 +148,11 @@ begin
|
||||
Result := NaN;
|
||||
end;
|
||||
|
||||
function TDataFloatValueNaN.AsTValue: TValue;
|
||||
begin
|
||||
Result := FValue;
|
||||
end;
|
||||
|
||||
{ TDataFloatType }
|
||||
|
||||
class constructor TDataFloatType.CreateClass;
|
||||
|
||||
@@ -23,6 +23,9 @@ type
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Rtti;
|
||||
|
||||
type
|
||||
// Implements the ordinal data value.
|
||||
TDataOrdinalValue = class(TInterfacedObject, IDataValue, IDataOrdinalValue)
|
||||
@@ -34,6 +37,7 @@ type
|
||||
// IDataValue
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
|
||||
// IDataOrdinalValue
|
||||
function GetValue: Int64;
|
||||
@@ -41,26 +45,41 @@ type
|
||||
|
||||
// Specialized implementation for the value -1.
|
||||
TDataOrdinalValueMinusOne = class(TInterfacedObject, IDataValue, IDataOrdinalValue)
|
||||
strict private
|
||||
class var
|
||||
FValue: TValue;
|
||||
class constructor CreateClass;
|
||||
public
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function GetValue: Int64;
|
||||
function AsTValue: TValue;
|
||||
end;
|
||||
|
||||
// Specialized implementation for the value 0.
|
||||
TDataOrdinalValueZero = class(TInterfacedObject, IDataValue, IDataOrdinalValue)
|
||||
strict private
|
||||
class var
|
||||
FValue: TValue;
|
||||
class constructor CreateClass;
|
||||
public
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function GetValue: Int64;
|
||||
function AsTValue: TValue;
|
||||
end;
|
||||
|
||||
// Specialized implementation for the value 1.
|
||||
TDataOrdinalValueOne = class(TInterfacedObject, IDataValue, IDataOrdinalValue)
|
||||
strict private
|
||||
class var
|
||||
FValue: TValue;
|
||||
class constructor CreateClass;
|
||||
public
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function GetValue: Int64;
|
||||
function AsTValue: TValue;
|
||||
end;
|
||||
|
||||
{ TDataOrdinalValue }
|
||||
@@ -81,6 +100,12 @@ begin
|
||||
Result := FValue.ToString;
|
||||
end;
|
||||
|
||||
function TDataOrdinalValue.AsTValue: TValue;
|
||||
begin
|
||||
// Convert Int64 to TValue
|
||||
Result := TValue.From<Int64>(FValue);
|
||||
end;
|
||||
|
||||
function TDataOrdinalValue.GetValue: Int64;
|
||||
begin
|
||||
Result := FValue;
|
||||
@@ -88,6 +113,11 @@ end;
|
||||
|
||||
{ TDataOrdinalValueMinusOne }
|
||||
|
||||
class constructor TDataOrdinalValueMinusOne.CreateClass;
|
||||
begin
|
||||
FValue := TValue.From<Int64>(-1);
|
||||
end;
|
||||
|
||||
function TDataOrdinalValueMinusOne.GetDataType: IDataType;
|
||||
begin
|
||||
Result := TDataOrdinalType.Singleton;
|
||||
@@ -98,6 +128,12 @@ begin
|
||||
Result := '-1';
|
||||
end;
|
||||
|
||||
function TDataOrdinalValueMinusOne.AsTValue: TValue;
|
||||
begin
|
||||
// Return the singleton TValue instance.
|
||||
Result := FValue;
|
||||
end;
|
||||
|
||||
function TDataOrdinalValueMinusOne.GetValue: Int64;
|
||||
begin
|
||||
Result := -1;
|
||||
@@ -105,6 +141,11 @@ end;
|
||||
|
||||
{ TDataOrdinalValueZero }
|
||||
|
||||
class constructor TDataOrdinalValueZero.CreateClass;
|
||||
begin
|
||||
FValue := TValue.From<Int64>(0);
|
||||
end;
|
||||
|
||||
function TDataOrdinalValueZero.GetDataType: IDataType;
|
||||
begin
|
||||
Result := TDataOrdinalType.Singleton;
|
||||
@@ -115,6 +156,12 @@ begin
|
||||
Result := '0';
|
||||
end;
|
||||
|
||||
function TDataOrdinalValueZero.AsTValue: TValue;
|
||||
begin
|
||||
// Return the singleton TValue instance.
|
||||
Result := FValue;
|
||||
end;
|
||||
|
||||
function TDataOrdinalValueZero.GetValue: Int64;
|
||||
begin
|
||||
Result := 0;
|
||||
@@ -122,6 +169,11 @@ end;
|
||||
|
||||
{ TDataOrdinalValueOne }
|
||||
|
||||
class constructor TDataOrdinalValueOne.CreateClass;
|
||||
begin
|
||||
FValue := TValue.From<Int64>(1);
|
||||
end;
|
||||
|
||||
function TDataOrdinalValueOne.GetDataType: IDataType;
|
||||
begin
|
||||
Result := TDataOrdinalType.Singleton;
|
||||
@@ -132,6 +184,12 @@ begin
|
||||
Result := '1';
|
||||
end;
|
||||
|
||||
function TDataOrdinalValueOne.AsTValue: TValue;
|
||||
begin
|
||||
// Return the singleton TValue instance.
|
||||
Result := FValue;
|
||||
end;
|
||||
|
||||
function TDataOrdinalValueOne.GetValue: Int64;
|
||||
begin
|
||||
Result := 1;
|
||||
|
||||
@@ -22,6 +22,7 @@ type
|
||||
private
|
||||
FFields: TArray<TRecordField>;
|
||||
FFieldMap: TDictionary<string, Integer>; // For fast lookups by name
|
||||
FEmptyValue: IDataRecordValue; // Cached instance for zero-field records
|
||||
function GetName: String;
|
||||
function GetKind: TDataKind;
|
||||
function GetFieldCount: Integer;
|
||||
@@ -36,7 +37,8 @@ type
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.SyncObjs;
|
||||
System.SyncObjs,
|
||||
System.Rtti;
|
||||
|
||||
type
|
||||
// Implements the record data value.
|
||||
@@ -46,6 +48,7 @@ type
|
||||
FItems: TArray<IDataValue>;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
constructor Create(const ADataType: IDataRecordType; const AItems: array of IDataValue);
|
||||
@@ -53,10 +56,15 @@ type
|
||||
|
||||
// Optimized implementation for a record with 0 fields.
|
||||
TDataRecordValue0 = class(TInterfacedObject, IDataValue, IDataRecordValue)
|
||||
strict private
|
||||
class var
|
||||
FEmptyTValue: TValue; // Singleton TValue for an empty record
|
||||
class constructor CreateClass;
|
||||
private
|
||||
FDataType: IDataRecordType;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
constructor Create(const ADataType: IDataRecordType);
|
||||
@@ -69,6 +77,7 @@ type
|
||||
FItem0: IDataValue;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
constructor Create(const ADataType: IDataRecordType; const AItems: array of IDataValue);
|
||||
@@ -81,6 +90,7 @@ type
|
||||
FItem0, FItem1: IDataValue;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
constructor Create(const ADataType: IDataRecordType; const AItems: array of IDataValue);
|
||||
@@ -157,6 +167,17 @@ begin
|
||||
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;
|
||||
begin
|
||||
Result := FItems[Idx];
|
||||
@@ -164,6 +185,14 @@ end;
|
||||
|
||||
{ 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);
|
||||
begin
|
||||
inherited Create;
|
||||
@@ -175,6 +204,11 @@ begin
|
||||
Result := '<>';
|
||||
end;
|
||||
|
||||
function TDataRecordValue0.AsTValue: TValue;
|
||||
begin
|
||||
Result := FEmptyTValue;
|
||||
end;
|
||||
|
||||
function TDataRecordValue0.GetDataType: IDataType;
|
||||
begin
|
||||
Result := FDataType;
|
||||
@@ -202,6 +236,15 @@ begin
|
||||
Result := Format('<%s: %s>', [recordType.Fields[0].Name, FItem0.AsString]);
|
||||
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;
|
||||
begin
|
||||
Result := FDataType;
|
||||
@@ -249,6 +292,16 @@ begin
|
||||
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;
|
||||
begin
|
||||
Result := FDataType;
|
||||
@@ -282,6 +335,10 @@ begin
|
||||
FFields[i] := field;
|
||||
FFieldMap.Add(field.Name, i);
|
||||
end;
|
||||
|
||||
// Pre-create the singleton value instance if this is a zero-field record.
|
||||
if (Length(FFields) = 0) then
|
||||
FEmptyValue := TDataRecordValue0.Create(Self);
|
||||
end;
|
||||
|
||||
destructor TDataRecordType.Destroy;
|
||||
@@ -306,7 +363,7 @@ begin
|
||||
|
||||
// Use optimized implementations for records with 0, 1, or 2 fields.
|
||||
case Length(FFields) of
|
||||
0: Result := TDataRecordValue0.Create(Self);
|
||||
0: Result := FEmptyValue; // Return the pre-cached instance.
|
||||
1: Result := TDataRecordValue1.Create(Self, AItems);
|
||||
2: Result := TDataRecordValue2.Create(Self, AItems);
|
||||
else
|
||||
|
||||
@@ -21,6 +21,9 @@ type
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Rtti;
|
||||
|
||||
type
|
||||
TDataTextValue = class(TInterfacedObject, IDataValue, IDataTextValue)
|
||||
private
|
||||
@@ -28,6 +31,7 @@ type
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function GetValue: string;
|
||||
function AsTValue: TValue;
|
||||
public
|
||||
constructor Create(const AValue: string);
|
||||
end;
|
||||
@@ -37,11 +41,13 @@ type
|
||||
strict private
|
||||
class var
|
||||
FSingleton: IDataTextValue;
|
||||
FEmptyTValue: TValue;
|
||||
class constructor CreateClass;
|
||||
private
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function GetValue: string;
|
||||
function AsTValue: TValue;
|
||||
public
|
||||
class property Singleton: IDataTextValue read FSingleton;
|
||||
end;
|
||||
@@ -95,11 +101,17 @@ begin
|
||||
Result := FValue;
|
||||
end;
|
||||
|
||||
function TDataTextValue.AsTValue: TValue;
|
||||
begin
|
||||
Result := TValue.From<string>(FValue);
|
||||
end;
|
||||
|
||||
{ TDataTextValueEmpty }
|
||||
|
||||
class constructor TDataTextValueEmpty.CreateClass;
|
||||
begin
|
||||
FSingleton := TDataTextValueEmpty.Create;
|
||||
FEmptyTValue := TValue.From<string>('');
|
||||
end;
|
||||
|
||||
function TDataTextValueEmpty.GetAsString: string;
|
||||
@@ -117,4 +129,9 @@ begin
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
function TDataTextValueEmpty.AsTValue: TValue;
|
||||
begin
|
||||
Result := FEmptyTValue;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
@@ -21,6 +21,9 @@ type
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Rtti;
|
||||
|
||||
type
|
||||
TDataTimestampValue = class(TInterfacedObject, IDataValue, IDataTimestampValue)
|
||||
private
|
||||
@@ -28,6 +31,7 @@ type
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function GetValue: TDateTime;
|
||||
function AsTValue: TValue;
|
||||
public
|
||||
constructor Create(const AValue: TDateTime);
|
||||
end;
|
||||
@@ -77,4 +81,9 @@ begin
|
||||
Result := FValue;
|
||||
end;
|
||||
|
||||
function TDataTimestampValue.AsTValue: TValue;
|
||||
begin
|
||||
Result := TValue.From<TDateTime>(FValue);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
@@ -23,6 +23,9 @@ type
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Rtti;
|
||||
|
||||
type
|
||||
// Implements the simple tuple data value.
|
||||
TDataTupleValue = class(TInterfacedObject, IDataValue, IDataTupleValue)
|
||||
@@ -30,6 +33,7 @@ type
|
||||
FItems: TArray<IDataValue>;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItemCount: Integer;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
@@ -41,10 +45,12 @@ type
|
||||
strict private
|
||||
class var
|
||||
FSingleton: IDataTupleValue;
|
||||
FEmptyTValue: TValue;
|
||||
class constructor CreateClass;
|
||||
private
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItemCount: Integer;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
@@ -57,6 +63,7 @@ type
|
||||
FItem0: IDataValue;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItemCount: Integer;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
@@ -69,6 +76,7 @@ type
|
||||
FItem0, FItem1: IDataValue;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItemCount: Integer;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
@@ -81,6 +89,7 @@ type
|
||||
FItem0, FItem1, FItem2: IDataValue;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItemCount: Integer;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
@@ -93,6 +102,7 @@ type
|
||||
FItem0, FItem1, FItem2, FItem3: IDataValue;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItemCount: Integer;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
@@ -105,6 +115,7 @@ type
|
||||
FItem0, FItem1, FItem2, FItem3, FItem4: IDataValue;
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
function GetItemCount: Integer;
|
||||
function GetItem(Idx: Integer): IDataValue;
|
||||
public
|
||||
@@ -149,6 +160,17 @@ begin
|
||||
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;
|
||||
begin
|
||||
Result := FItems[Idx];
|
||||
@@ -162,8 +184,12 @@ end;
|
||||
{ TDataTupleValue0 }
|
||||
|
||||
class constructor TDataTupleValue0.CreateClass;
|
||||
var
|
||||
emptyArray: TArray<TValue>;
|
||||
begin
|
||||
FSingleton := TDataTupleValue0.Create;
|
||||
SetLength(emptyArray, 0);
|
||||
FEmptyTValue := TValue.From<TArray<TValue>>(emptyArray);
|
||||
end;
|
||||
|
||||
function TDataTupleValue0.GetAsString: string;
|
||||
@@ -171,6 +197,11 @@ begin
|
||||
Result := '()';
|
||||
end;
|
||||
|
||||
function TDataTupleValue0.AsTValue: TValue;
|
||||
begin
|
||||
Result := FEmptyTValue;
|
||||
end;
|
||||
|
||||
function TDataTupleValue0.GetDataType: IDataType;
|
||||
begin
|
||||
Result := TDataTupleType.Singleton;
|
||||
@@ -199,6 +230,15 @@ begin
|
||||
Result := '(' + FItem0.AsString + ')';
|
||||
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;
|
||||
begin
|
||||
Result := TDataTupleType.Singleton;
|
||||
@@ -232,6 +272,16 @@ begin
|
||||
Result := '(' + FItem0.AsString + ', ' + FItem1.AsString + ')';
|
||||
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;
|
||||
begin
|
||||
Result := TDataTupleType.Singleton;
|
||||
@@ -281,6 +331,17 @@ begin
|
||||
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;
|
||||
begin
|
||||
Result := TDataTupleType.Singleton;
|
||||
@@ -334,6 +395,18 @@ begin
|
||||
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;
|
||||
begin
|
||||
Result := TDataTupleType.Singleton;
|
||||
@@ -391,6 +464,19 @@ begin
|
||||
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;
|
||||
begin
|
||||
Result := TDataTupleType.Singleton;
|
||||
|
||||
@@ -4,7 +4,8 @@ interface
|
||||
|
||||
uses
|
||||
System.Generics.Collections,
|
||||
System.SysUtils;
|
||||
System.SysUtils,
|
||||
System.RTTI;
|
||||
|
||||
type
|
||||
TDataKind = (dkOrdinal, dkFloat, dkText, dkTimestamp, dkRecord, dkTuple, dkArray, dkEnum);
|
||||
@@ -19,6 +20,7 @@ type
|
||||
IDataValue = interface
|
||||
function GetDataType: IDataType;
|
||||
function GetAsString: string;
|
||||
function AsTValue: TValue;
|
||||
property DataType: IDataType read GetDataType;
|
||||
property AsString: string read GetAsString;
|
||||
end;
|
||||
@@ -122,6 +124,7 @@ type
|
||||
strict private
|
||||
FDataType: IDataType;
|
||||
function GetName: String; inline;
|
||||
function GetKind: TDataKind; inline;
|
||||
|
||||
class var
|
||||
FArrayTypeRegistry: TDictionary<IDataType, IDataArrayType>;
|
||||
@@ -132,6 +135,8 @@ type
|
||||
public
|
||||
constructor Create(const ADataType: IDataType);
|
||||
|
||||
class procedure ClearRegistries; static;
|
||||
|
||||
class operator Implicit(const A: IDataType): TDataType; 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 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 Name: String read GetName;
|
||||
property Kind: TDataKind read GetKind;
|
||||
end;
|
||||
|
||||
TDataValue = record
|
||||
@@ -239,6 +255,69 @@ begin
|
||||
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;
|
||||
begin
|
||||
Result := TDataFloatType.Singleton;
|
||||
@@ -287,6 +366,11 @@ begin
|
||||
Result := TDataEnumType.Create(AName, AIdentifiers);
|
||||
end;
|
||||
|
||||
function TDataType.GetKind: TDataKind;
|
||||
begin
|
||||
Result := FDataType.Kind;
|
||||
end;
|
||||
|
||||
class function TDataType.Text: IDataTextType;
|
||||
begin
|
||||
Result := TDataTextType.Singleton;
|
||||
|
||||
+237
-10
@@ -7,39 +7,59 @@ uses
|
||||
|
||||
type
|
||||
[TestFixture]
|
||||
[IgnoreMemoryLeaks]
|
||||
TMyTestObject = class
|
||||
public
|
||||
[Test]
|
||||
procedure TestCreateRecords;
|
||||
[TestFixtureTearDown]
|
||||
procedure FixtureTearDown;
|
||||
|
||||
[Test]
|
||||
procedure TestRecords;
|
||||
[Test]
|
||||
procedure TestRecords_Generic;
|
||||
[Test]
|
||||
procedure TestTuples;
|
||||
|
||||
[Test]
|
||||
procedure TestTuples_Generic;
|
||||
[Test]
|
||||
procedure TestArrays;
|
||||
|
||||
[Test]
|
||||
procedure TestArrays_OptimizedAndGeneric;
|
||||
[Test]
|
||||
procedure TestTexts;
|
||||
|
||||
[Test]
|
||||
procedure TestTexts_OptimizedEmpty;
|
||||
[Test]
|
||||
procedure TestFloats_OptimizedAndGeneric;
|
||||
[Test]
|
||||
procedure TestTimestamps;
|
||||
|
||||
[Test]
|
||||
procedure TestEnums;
|
||||
|
||||
[Test]
|
||||
procedure TestAsString;
|
||||
[Test]
|
||||
procedure TestAsTValue;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Math,
|
||||
System.Rtti,
|
||||
System.TypInfo,
|
||||
Myc.Data.Types;
|
||||
|
||||
{ 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
|
||||
intType: IDataType;
|
||||
floatType: IDataType;
|
||||
@@ -48,6 +68,7 @@ var
|
||||
idValue: IDataOrdinalValue;
|
||||
floatValue: IDataFloatValue;
|
||||
begin
|
||||
// This test covers the optimized implementation for 2 fields.
|
||||
// --- 1. Setup: Define base types and a record structure ---
|
||||
intType := TDataType.Ordinal;
|
||||
floatType := TDataType.Float;
|
||||
@@ -117,6 +138,30 @@ begin
|
||||
);
|
||||
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;
|
||||
var
|
||||
intValue: IDataValue;
|
||||
@@ -124,6 +169,7 @@ var
|
||||
tuple1, tuple2, tuple3: IDataTupleValue;
|
||||
type1, type2, type3: IDataType;
|
||||
begin
|
||||
// This test covers optimized implementations for 1 and 2 elements.
|
||||
// --- 1. Setup: Create some values ---
|
||||
intValue := TDataValue.FromOrdinal(123);
|
||||
floatValue := TDataValue.FromFloat(45.67);
|
||||
@@ -154,6 +200,29 @@ begin
|
||||
Assert.AreSame(type1, type3, 'All tuple types should be the same singleton instance');
|
||||
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;
|
||||
var
|
||||
intType: IDataType;
|
||||
@@ -210,6 +279,35 @@ begin
|
||||
);
|
||||
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;
|
||||
var
|
||||
textType: IDataTextType;
|
||||
@@ -244,6 +342,47 @@ begin
|
||||
.WillRaise(procedure begin TDataValue(dataValue).AsText; end, EInvalidCast, 'Casting non-text to AsText should raise EInvalidCast');
|
||||
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;
|
||||
var
|
||||
tsType: IDataTimestampType;
|
||||
@@ -390,7 +529,95 @@ begin
|
||||
Assert.AreEqual('(10, Tuple)', tupleValue.AsString, 'Tuple AsString incorrect');
|
||||
end;
|
||||
|
||||
initialization
|
||||
TDUnitX.RegisterTestFixture(TMyTestObject);
|
||||
procedure TMyTestObject.TestAsTValue;
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user