Skip to content

Commit

Permalink
Merge pull request #159 from denizzzka/adds_std_variant_support
Browse files Browse the repository at this point in the history
Adds std.variant.Variant support
  • Loading branch information
denizzzka committed Nov 15, 2023
2 parents 93bc967 + 6e47d4d commit c7c745c
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 19 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/compilers.json
Expand Up @@ -2,9 +2,9 @@
"dmd-master",
"dmd-latest",
"dmd-beta",
"dmd-2.098.1",
"dmd-2.105.3",
"ldc-master",
"ldc-latest",
"ldc-beta",
"ldc-1.28.1"
"ldc-1.35.0"
]
2 changes: 1 addition & 1 deletion src/dpq2/conv/from_d_types.d
Expand Up @@ -96,7 +96,7 @@ if(is(Unqual!T == BitArray))
buffer.append!uint(cast(uint)v.length);
foreach (d; data[0 .. v.dim])
{
// DMD Issue 19693
//FIXME: DMD Issue 19693
version(DigitalMars)
auto ntb = nativeToBigEndian(softBitswap(d));
else
Expand Down
64 changes: 54 additions & 10 deletions src/dpq2/conv/native_tests.d
Expand Up @@ -7,7 +7,7 @@ import std.bitmanip : BitArray;
import std.datetime;
import std.typecons: Nullable;
import std.uuid: UUID;
import vibe.data.bson: Bson, deserializeBson;
import std.variant: Variant;
import vibe.data.json: Json, parseJsonString;

version (integration_tests)
Expand Down Expand Up @@ -54,14 +54,18 @@ public void _integration_test( string connParam ) @system
params.resultFormat = ValueFormat.BINARY;

{
void testIt(T)(T nativeValue, string pgType, string pgValue)
import dpq2.conv.geometric: GeometricInstancesForIntegrationTest;
mixin GeometricInstancesForIntegrationTest;

void testIt(T)(T nativeValue, in string pgType, string pgValue)
{
import std.algorithm : strip;
import std.string : representation;
import std.meta: AliasSeq, anySatisfy;

static string formatValue(T val)
{
import std.algorithm : joiner, map, strip;
import std.algorithm : joiner, map;
import std.conv : text, to;
import std.range : chain, ElementType;

Expand All @@ -81,15 +85,56 @@ public void _integration_test( string connParam ) @system

auto result = v.as!T;

enum disabledForStdVariant = (
is(T == Nullable!string[]) || // Variant haven't heuristics to understand what array elements can contain NULLs
is(T == Nullable!(int[])) || // Same reason, but here is all values are Nullable and thus incompatible for comparison with original values
is(T == SysTime) || is(T == Nullable!SysTime) || // Can't be supported by toVariant because TimeStampWithZone converted to PGtimestamptz
is(T == LineSegment) || // Impossible to support: LineSegment struct must be provided by user
is(T == PGTestMoney) || // ditto
is(T == BitArray) || //TODO: Format of the column (VariableBitString) doesn't supported by Value to Variant converter
is(T == Nullable!BitArray) || // ditto
is(T == Point) || // Impossible to support: LineSegment struct must be provided by user
is(T == Nullable!Point) || // ditto
is(T == Box) || // ditto
is(T == TestPath) || // ditto
is(T == Polygon) || // ditto
is(T == TestCircle) // ditto
);

static if(!disabledForStdVariant)
{
static if (is(T == Nullable!R, R))
auto stdVariantResult = v.as!(Variant, true);
else
auto stdVariantResult = v.as!(Variant, false);
}

string formatMsg(string varType)
{
return format(
"PG to %s conv: received unexpected value\nreceived pgType=%s\nexpected nativeType=%s\nsent pgValue=%s\nexpected nativeValue=%s\nresult=%s",
varType, v.oidType, typeid(T), pgValue, formatValue(nativeValue), formatValue(result)
);
}

static if(isArrayType!T)
const bool assertResult = compareArraysWithCareAboutNullables(result, nativeValue);
else
{
const bool assertResult = result == nativeValue;

assert(assertResult,
format("PG to native conv: received unexpected value\nreceived pgType=%s\nexpected nativeType=%s\nsent pgValue=%s\nexpected nativeValue=%s\nresult=%s",
v.oidType, typeid(T), pgValue, formatValue(nativeValue), formatValue(result))
);
//Variant:
static if(!disabledForStdVariant)
{
// Ignores "json as string" test case with Json sent natively as string
if(!(is(T == string) && v.oidType == OidType.Json))
{
assert(stdVariantResult == nativeValue, formatMsg("std.variant.Variant (type: %s)".format(stdVariantResult.type)));
}
}
}

assert(assertResult, formatMsg("native"));

{
// test binary to text conversion
Expand Down Expand Up @@ -148,6 +193,7 @@ public void _integration_test( string connParam ) @system
C!PGvarbit(BitArray([1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1]), "varbit", "'101011010110101'");
C!PGvarbit(BitArray([0, 0, 1, 0, 1]), "varbit", "'00101'");
C!PGvarbit(BitArray([1, 0, 1, 0, 0]), "varbit", "'10100'");
C!(Nullable!PGvarbit)(Nullable!PGvarbit.init, "varbit", "NULL");

// numeric testing
C!PGnumeric("NaN", "numeric", "'NaN'");
Expand Down Expand Up @@ -224,9 +270,6 @@ public void _integration_test( string connParam ) @system
`'{"float_value": 123.456, "text_str": "text string", "abc": {"key": "value"}}'`);

// Geometric
import dpq2.conv.geometric: GeometricInstancesForIntegrationTest;
mixin GeometricInstancesForIntegrationTest;

C!Point(Point(1,2), "point", "'(1,2)'");
C!PGline(Line(1,2,3), "line", "'{1,2,3}'");
C!LineSegment(LineSegment(Point(1,2), Point(3,4)), "lseg", "'[(1,2),(3,4)]'");
Expand All @@ -244,6 +287,7 @@ public void _integration_test( string connParam ) @system
C!(string[])(["foo","bar", "baz"], "text[]", "'{foo,bar,baz}'");
C!(PGjson[])([Json(["foo": Json(42)])], "json[]", `'{"{\"foo\":42}"}'`);
C!(PGuuid[])([UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640")], "uuid[]", "'{8b9ab33a-96e9-499b-9c36-aad1fe86d640}'");
C!(PGline[])([Line(1,2,3), Line(4,5,6)], "line[]", `'{"{1,2,3}","{4,5,6}"}'`);
C!(Nullable!(int[]))(Nullable!(int[]).init, "int[]", "NULL");
C!(Nullable!(int[]))(Nullable!(int[])([1,2,3]), "int[]", "'{1,2,3}'");
}
Expand Down
17 changes: 15 additions & 2 deletions src/dpq2/conv/to_d_types.d
Expand Up @@ -20,6 +20,7 @@ import std.traits;
import std.uuid;
import std.datetime;
import std.traits: isScalarType;
import std.variant: Variant;
import std.typecons : Nullable;
import std.bitmanip: bigEndianToNative, BitArray;
import std.conv: to;
Expand Down Expand Up @@ -50,6 +51,16 @@ private alias VF = ValueFormat;
private alias AE = ValueConvException;
private alias ET = ConvExceptionType;

/**
Returns cell value as a Variant type.
*/
T as(T : Variant, bool isNullablePayload = true)(in Value v)
{
import dpq2.conv.to_variant;

return v.toVariant!isNullablePayload;
}

/**
Returns cell value as a Nullable type using the underlying type conversion after null check.
*/
Expand Down Expand Up @@ -97,14 +108,16 @@ if(is(T : const(char)[]) && !is(T == Nullable!R, R))
assert(v.isNull);
assertThrown!AssertError(v.as!string == "");
assert(v.as!(Nullable!string).isNull == true);

assert(v.as!Variant.get!(Nullable!string).isNull == true);
}

/**
Returns value as D type value from binary formatted field.
Throws: AssertError if the db value is NULL.
*/
T as(T)(in Value v)
if(!is(T : const(char)[]) && !is(T == Bson) && !is(T == Nullable!R,R))
if(!is(T : const(char)[]) && !is(T == Bson) && !is(T == Variant) && !is(T == Nullable!R,R))
{
if(!(v.format == VF.BINARY))
throw new AE(ET.NOT_BINARY,
Expand Down Expand Up @@ -377,7 +390,7 @@ if( is(T == BitArray) )
ubyte[size_t.sizeof] tmpData;
tmpData[0 .. ch.length] = ch[];

// DMD Issue 19693
//FIXME: DMD Issue 19693
version(DigitalMars)
auto re = softBitswap(bigEndianToNative!size_t(tmpData));
else
Expand Down
133 changes: 133 additions & 0 deletions src/dpq2/conv/to_variant.d
@@ -0,0 +1,133 @@
///
module dpq2.conv.to_variant;

import dpq2.value;
import dpq2.oids: OidType;
import dpq2.result: ArrayProperties;
import dpq2.conv.to_d_types;
import dpq2.conv.numeric: rawValueToNumeric;
import dpq2.conv.time: TimeStampUTC;
static import geom = dpq2.conv.geometric;
import std.bitmanip: bigEndianToNative, BitArray;
import std.datetime: SysTime, dur, TimeZone, UTC;
import std.conv: to;
import std.typecons: Nullable;
import std.uuid;
import std.variant: Variant;
import vibe.data.json: VibeJson = Json;

///
Variant toVariant(bool isNullablePayload = true)(in Value v) @safe
{
auto getNative(T)()
if(!is(T == Variant))
{
static if(isNullablePayload)
{
Nullable!T ret;

if (v.isNull)
return ret;

ret = v.as!T;

return ret;
}
else
{
return v.as!T;
}
}

Variant retVariant(T)() @trusted
{
return Variant(getNative!T);
}

if(v.format == ValueFormat.TEXT)
return retVariant!string;

template retArray__(NativeT)
{
static if(isNullablePayload)
alias arrType = Nullable!NativeT[];
else
alias arrType = NativeT[];

alias retArray__ = retVariant!arrType;
}

with(OidType)
switch(v.oidType)
{
case Bool: return retVariant!PGboolean;
case BoolArray: return retArray__!PGboolean;

case Int2: return retVariant!short;
case Int2Array: return retArray__!short;

case Int4: return retVariant!int;
case Int4Array: return retArray__!int;

case Int8: return retVariant!long;
case Int8Array: return retArray__!long;

case Float4: return retVariant!float;
case Float4Array: return retArray__!float;

case Float8: return retVariant!double;
case Float8Array: return retArray__!double;

case Numeric:
case Text:
case FixedString:
case VariableString:
return retVariant!string;

case NumericArray:
case TextArray:
case FixedStringArray:
case VariableStringArray:
return retArray__!string;

case ByteArray: return retVariant!PGbytea;

case UUID: return retVariant!PGuuid;
case UUIDArray: return retArray__!PGuuid;

case Date: return retVariant!PGdate;
case DateArray: return retArray__!PGdate;

case Time: return retVariant!PGtime_without_time_zone;
case TimeArray: return retArray__!PGtime_without_time_zone;

case TimeWithZone: return retVariant!PGtime_with_time_zone;
case TimeWithZoneArray: return retArray__!PGtime_with_time_zone;

case TimeStamp: return retVariant!PGtimestamp;
case TimeStampArray: return retArray__!PGtimestamp;

case TimeStampWithZone: return retVariant!PGtimestamptz;
case TimeStampWithZoneArray: return retArray__!PGtimestamptz;

case TimeInterval: return retVariant!PGinterval;

case Json:
case Jsonb:
return retVariant!VibeJson;

case JsonArray:
case JsonbArray:
return retArray__!VibeJson;

case Line: return retVariant!(geom.Line);
case LineArray: return retArray__!(geom.Line);

default:
throw new ValueConvException(
ConvExceptionType.NOT_IMPLEMENTED,
"Format of the column ("~to!(immutable(char)[])(v.oidType)~") doesn't supported by Value to Variant converter",
__FILE__, __LINE__
);
}
}
3 changes: 3 additions & 0 deletions src/dpq2/oids.d
Expand Up @@ -117,6 +117,7 @@ shared static this()
A(TimeWithZone, TimeWithZoneArray),
A(TimeStampWithZone, TimeStampWithZoneArray),
A(TimeStamp, TimeStampArray),
A(Line, LineArray),
A(Json, JsonArray),
A(UUID, UUIDArray)
];
Expand Down Expand Up @@ -149,6 +150,7 @@ bool isSupportedArray(OidType t) pure nothrow @nogc
case TimeWithZoneArray:
case NumericArray:
case UUIDArray:
case LineArray:
case JsonArray:
case JsonbArray:
return true;
Expand Down Expand Up @@ -307,6 +309,7 @@ public enum OidType : Oid
XmlArray = 143, ///
JsonbArray = 3807, ///
JsonArray = 199, ///
LineArray = 629, ///
BoolArray = 1000, ///
ByteArrayArray = 1001, ///
CharArray = 1002, ///
Expand Down
2 changes: 1 addition & 1 deletion src/dpq2/result.d
Expand Up @@ -832,7 +832,7 @@ void _integration_test( string connParam )
"array[11,22,NULL,44]::integer[] as small_array, "~
"array['1','23',NULL,'789A']::text[] as text_array, "~
"array[]::text[] as empty_array, "~
"'2022-02-23'::date as unsupported_toString_output_type";
"'((1, 2), (3, 4))'::lseg as unsupported_toString_output_type";

auto r = conn.execParams(p);

Expand Down
6 changes: 3 additions & 3 deletions src/dpq2/value.d
Expand Up @@ -98,11 +98,11 @@ struct Value
///
string toString() const @trusted
{
import vibe.data.bson: Bson;
import dpq2.conv.to_bson;
import dpq2.conv.to_d_types;
import std.conv: to;
import std.variant;

return this.as!Bson.toString~"::"~oidType.to!string~"("~(format == ValueFormat.TEXT? "t" : "b")~")";
return this.as!Variant.toString~"::"~oidType.to!string~"("~(format == ValueFormat.TEXT? "t" : "b")~")";
}
}

Expand Down

0 comments on commit c7c745c

Please sign in to comment.