diff --git a/src/dpq2/conv/from_d_types.d b/src/dpq2/conv/from_d_types.d index e49bc7e4..d58eecbf 100644 --- a/src/dpq2/conv/from_d_types.d +++ b/src/dpq2/conv/from_d_types.d @@ -4,7 +4,7 @@ module dpq2.conv.from_d_types; @safe: public import dpq2.conv.arrays : isArrayType, toValue, isStaticArrayString; -public import dpq2.conv.geometric : toValue; +public import dpq2.conv.geometric : isGeometricType, toValue; import dpq2.conv.time : POSTGRES_EPOCH_DATE, TimeStamp, TimeStampUTC; import dpq2.oids : detectOidTypeFromNative, oidConvTo, OidType; import dpq2.value : Value, ValueFormat; @@ -21,7 +21,7 @@ import money: currency; /// Converts Nullable!T to Value Value toValue(T)(T v) -if (is(T == Nullable!R, R) && !(isArrayType!(typeof(v.get)))) +if (is(T == Nullable!R, R) && !(isArrayType!(typeof(v.get))) && !isGeometricType!(typeof(v.get))) { if (v.isNull) return Value(ValueFormat.BINARY, detectOidTypeFromNative!T); @@ -42,6 +42,18 @@ if (is(T == Nullable!R, R) && (isArrayType!(typeof(v.get)))) return arrToValue(v.get); } +/// ditto +Value toValue(T)(T v) +if (is(T == Nullable!R, R) && isGeometricType!(typeof(v.get))) +{ + import dpq2.conv.geometric : geoToValue = toValue; // deprecation import workaround + + if (v.isNull) + return Value(ValueFormat.BINARY, detectOidTypeFromNative!T); + else + return geoToValue(v.get); +} + /// Value toValue(T)(T v) if(isNumeric!(T)) diff --git a/src/dpq2/conv/geometric.d b/src/dpq2/conv/geometric.d index 38802b63..5d47355e 100644 --- a/src/dpq2/conv/geometric.d +++ b/src/dpq2/conv/geometric.d @@ -5,6 +5,7 @@ import dpq2.oids: OidType; import dpq2.value: ConvExceptionType, throwTypeComplaint, Value, ValueConvException, ValueFormat; import std.bitmanip: bigEndianToNative, nativeToBigEndian; import std.traits; +import std.typecons : Nullable; import std.range.primitives: ElementType; @safe: @@ -21,17 +22,30 @@ private template GetRvalueOfMember(T, string memberName) alias GetRvalueOfMember = R; } +template isGeometricType(T) if(!is(T == Nullable!N, N)) +{ + enum isGeometricType = + isValidPointType!T + || isValidLineType!T + || isValidPathType!T + || isValidPolygon!T + || isValidCircleType!T + || isValidLineSegmentType!T + || isValidBoxType!T; +} + /// Checks that type have "x" and "y" members of returning type "double" -bool isValidPointType(T)() +template isValidPointType(T) { - static if(__traits(compiles, typeof(T.x)) && __traits(compiles, typeof(T.y))) + static if (is(T == Nullable!R, R)) enum isValidPointType = false; + else static if(__traits(compiles, typeof(T.x)) && __traits(compiles, typeof(T.y))) { - return + enum isValidPointType = is(GetRvalueOfMember!(T, "x") == double) && is(GetRvalueOfMember!(T, "y") == double); } else - return false; + enum isValidPointType = false; } unittest @@ -48,35 +62,55 @@ unittest } /// Checks that type have "min" and "max" members of suitable returning type of point -bool isValidBoxType(T)() +template isValidBoxType(T) { - static if(__traits(compiles, typeof(T.min)) && __traits(compiles, typeof(T.max))) + static if (is(T == Nullable!R, R)) enum isValidBoxType = false; + else static if(__traits(compiles, typeof(T.min)) && __traits(compiles, typeof(T.max))) { - return + enum isValidBoxType = isValidPointType!(GetRvalueOfMember!(T, "min")) && isValidPointType!(GetRvalueOfMember!(T, "max")); } else - return false; + enum isValidBoxType = false; +} + +template isValidLineType(T) +{ + enum isValidLineType = is(T == Line); +} + +template isValidPathType(T) +{ + enum isValidPathType = isInstanceOf!(Path, T); +} + +template isValidCircleType(T) +{ + enum isValidCircleType = isInstanceOf!(Circle, T); } /// -bool isValidLineSegmentType(T)() +template isValidLineSegmentType(T) { - static if(__traits(compiles, typeof(T.start)) && __traits(compiles, typeof(T.end))) + static if (is(T == Nullable!R, R)) enum isValidLineSegmentType = false; + else static if(__traits(compiles, typeof(T.start)) && __traits(compiles, typeof(T.end))) { - return + enum isValidLineSegmentType = isValidPointType!(GetRvalueOfMember!(T, "start")) && isValidPointType!(GetRvalueOfMember!(T, "end")); } else - return false; + enum isValidLineSegmentType = false; } /// -bool isValidPolygon(T)() +template isValidPolygon(T) { - return isArray!T && isValidPointType!(ElementType!T); + static if (is(T == Nullable!R, R)) + enum isValidPolygon = false; + else + enum isValidPolygon = isArray!T && isValidPointType!(ElementType!T); } unittest @@ -148,7 +182,7 @@ if(isValidPointType!Point) } Value toValue(T)(T line) -if(is(T == Line)) +if(isValidLineType!T) { import std.algorithm : copy; @@ -173,7 +207,7 @@ if(isValidLineSegmentType!LineSegment) } Value toValue(T)(T path) -if(isInstanceOf!(Path, T)) +if(isValidPathType!T) { import std.algorithm : copy; @@ -214,7 +248,7 @@ if(isValidPolygon!Polygon) } Value toValue(T)(T c) -if(isInstanceOf!(Circle, T)) +if(isValidCircleType!T) { import std.algorithm : copy; @@ -400,8 +434,8 @@ package mixin template GeometricInstancesForIntegrationTest() seg2d seg; alias seg this; - ref Point start(){ return a; } - ref Point end(){ return b; } + ref Point start() return { return a; } + ref Point end() return { return b; } this(Point a, Point b) { diff --git a/src/dpq2/conv/native_tests.d b/src/dpq2/conv/native_tests.d index 3b11cb3d..c0dbdef7 100644 --- a/src/dpq2/conv/native_tests.d +++ b/src/dpq2/conv/native_tests.d @@ -56,6 +56,20 @@ public void _integration_test( string connParam ) @system import std.algorithm : strip; import std.string : representation; + static string formatValue(T val) + { + import std.algorithm : joiner, map, strip; + import std.conv : text, to; + import std.range : chain, ElementType; + + // Nullable format deprecation workaround + static if (is(T == Nullable!R, R)) + return val.isNull ? "null" : val.get.to!string; + else static if (isArrayType!T && is(ElementType!T == Nullable!E, E)) + return chain("[", val.map!(a => a.isNull ? "null" : a.to!string).joiner(", "), "]").text; + else return val.to!string; + } + // test string to native conversion params.sqlCommand = format("SELECT %s::%s as d_type_test_value", pgValue is null ? "NULL" : pgValue, pgType); params.args = null; @@ -69,16 +83,15 @@ public void _integration_test( string connParam ) @system else const bool assertResult = result == nativeValue; - assert( - assertResult, + 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, nativeValue, result) + v.oidType, typeid(T), pgValue, formatValue(nativeValue), formatValue(result)) ); { // test binary to text conversion params.sqlCommand = "SELECT $1::text"; - params.args = [nativeValue.toValue]; + params.args = [toValue(nativeValue)]; auto answer2 = conn.execParams(params); auto v2 = answer2[0][0]; @@ -102,7 +115,7 @@ public void _integration_test( string connParam ) @system assert(textResult == pgValue, format("Native to PG conv: received unexpected value\nreceived pgType=%s\nsent nativeType=%s\nsent nativeValue=%s\nexpected pgValue=%s\nresult=%s\nexpectedRepresentation=%s\nreceivedRepresentation=%s", - v.oidType, typeid(T), nativeValue, pgValue, textResult, pgValue.representation, textResult.representation) + v.oidType, typeid(T), formatValue(nativeValue), pgValue, textResult, pgValue.representation, textResult.representation) ); } } @@ -186,6 +199,7 @@ public void _integration_test( string connParam ) @system // SysTime testing auto testTZ = new immutable SimpleTimeZone(2.dur!"hours"); // custom TZ C!SysTime(SysTime(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12), testTZ), "timestamptz", "'1997-12-17 07:37:16.000012+02'"); + C!(Nullable!SysTime)(Nullable!SysTime(SysTime(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12), testTZ)), "timestamptz", "'1997-12-17 07:37:16.000012+02'"); // json C!PGjson(Json(["float_value": Json(123.456), "text_str": Json("text string")]), "json", `'{"float_value": 123.456,"text_str": "text string"}'`); @@ -210,6 +224,7 @@ public void _integration_test( string connParam ) @system C!TestPath(TestPath(false, [Point(1,1), Point(2,2), Point(3,3)]), "path", "'[(1,1),(2,2),(3,3)]'"); C!Polygon(([Point(1,1), Point(2,2), Point(3,3)]), "polygon", "'((1,1),(2,2),(3,3))'"); C!TestCircle(TestCircle(Point(1,2), 10), "circle", "'<(1,2),10>'"); + C!(Nullable!Point)(Nullable!Point(Point(1,2)), "point", "'(1,2)'"); //Arrays C!(int[][])([[1,2],[3,4]], "int[]", "'{{1,2},{3,4}}'"); diff --git a/src/dpq2/oids.d b/src/dpq2/oids.d index 5fe1b8d3..becb5272 100644 --- a/src/dpq2/oids.d +++ b/src/dpq2/oids.d @@ -172,6 +172,7 @@ private OidType detectOidTypeNotCareAboutNullable(T)() import std.datetime.systime : SysTime; import std.traits : Unqual, isSomeString; import std.uuid : StdUUID = UUID; + static import dpq2.conv.geometric; static import dpq2.conv.time; import vibe.data.json : VibeJson = Json; @@ -196,6 +197,13 @@ private OidType detectOidTypeNotCareAboutNullable(T)() static if(is(UT == VibeJson)){ return Json; } else static if(is(UT == StdUUID)){ return UUID; } else static if(is(UT == BitArray)){ return VariableBitString; } else + static if(dpq2.conv.geometric.isValidPointType!UT){ return Point; } else + static if(dpq2.conv.geometric.isValidLineType!UT){ return Line; } else + static if(dpq2.conv.geometric.isValidPathType!UT){ return Path; } else + static if(dpq2.conv.geometric.isValidPolygon!UT){ return Polygon; } else + static if(dpq2.conv.geometric.isValidCircleType!UT){ return Circle; } else + static if(dpq2.conv.geometric.isValidLineSegmentType!UT){ return LineSegment; } else + static if(dpq2.conv.geometric.isValidBoxType!UT){ return Box; } else static assert(false, "Unsupported D type: "~T.stringof); }