1 module dpq2.conv.native_tests; 2 3 import dpq2; 4 import dpq2.conv.arrays : isArrayType; 5 import dpq2.conv.geometric: Line; 6 import std.datetime; 7 import std.typecons: Nullable; 8 import std.uuid: UUID; 9 import vibe.data.bson: Bson, deserializeBson; 10 import vibe.data.json: Json, parseJsonString; 11 12 version (integration_tests) 13 private bool compareArrays(A, B)(A _a, B _b) 14 { 15 static assert(is(A == B)); 16 17 import std.algorithm.comparison : equal; 18 import std.traits: isInstanceOf; 19 20 return equal!( 21 (a, b) 22 { 23 static if(isInstanceOf!(Nullable, A)) 24 { 25 if(a.isNull != b.isNull) 26 return false; 27 28 if(a.isNull) 29 return true; 30 } 31 32 return a == b; 33 } 34 )(_a, _b); 35 } 36 37 version (integration_tests) 38 public void _integration_test( string connParam ) @system 39 { 40 import std.format: format; 41 42 auto conn = new Connection(connParam); 43 44 // to return times in other than UTC time zone but fixed time zone so make the test reproducible in databases with other TZ 45 conn.exec("SET TIMEZONE TO +02"); 46 47 QueryParams params; 48 params.resultFormat = ValueFormat.BINARY; 49 50 { 51 void testIt(T)(T nativeValue, string pgType, string pgValue) 52 { 53 import std.algorithm : strip; 54 import std..string : representation; 55 56 // test string to native conversion 57 params.sqlCommand = format("SELECT %s::%s as d_type_test_value", pgValue is null ? "NULL" : pgValue, pgType); 58 params.args = null; 59 auto answer = conn.execParams(params); 60 immutable Value v = answer[0][0]; 61 static if (isArrayType!T || (is(T==Nullable!R, R) && isArrayType!R)) 62 auto result = v.as!Bson.deserializeBson!T; //HACK: There is no direct way to read back the array values using as!.. yet 63 else 64 auto result = v.as!T; 65 66 static if(isArrayType!T) 67 const bool assertResult = compareArrays(result, nativeValue); 68 else 69 const bool assertResult = result == nativeValue; 70 71 assert( 72 assertResult, 73 format("PG to native conv: received unexpected value\nreceived pgType=%s\nexpected nativeType=%s\nsent pgValue=%s\nexpected nativeValue=%s\nresult=%s", 74 v.oidType, typeid(T), pgValue, nativeValue, result) 75 ); 76 77 { 78 // test binary to text conversion 79 params.sqlCommand = "SELECT $1::text"; 80 params.args = [nativeValue.toValue]; 81 82 auto answer2 = conn.execParams(params); 83 auto v2 = answer2[0][0]; 84 85 string textResult = v2.isNull 86 ? "NULL" 87 : v2.as!string.strip(' '); 88 89 pgValue = pgValue.strip('\''); 90 91 // Special cases: 92 static if(is(T == PGbytea)) 93 pgValue = `\x442072756c65730021`; // Server formats its reply slightly different from the passed argument 94 95 static if(is(T == Json)) 96 { 97 // Reformatting by same way in the hope that the data will be sorted same in both cases 98 pgValue = pgValue.parseJsonString.toString; 99 textResult = textResult.parseJsonString.toString; 100 } 101 102 assert(textResult == pgValue, 103 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", 104 v.oidType, typeid(T), nativeValue, pgValue, textResult, pgValue.representation, textResult.representation) 105 ); 106 } 107 } 108 109 alias C = testIt; // "C" means "case" 110 111 C!PGboolean(true, "boolean", "true"); 112 C!PGboolean(false, "boolean", "false"); 113 C!(Nullable!PGboolean)(Nullable!PGboolean.init, "boolean", "NULL"); 114 C!(Nullable!PGboolean)(Nullable!PGboolean(true), "boolean", "true"); 115 C!PGsmallint(-32_761, "smallint", "-32761"); 116 C!PGinteger(-2_147_483_646, "integer", "-2147483646"); 117 C!PGbigint(-9_223_372_036_854_775_806, "bigint", "-9223372036854775806"); 118 C!PGreal(-12.3456f, "real", "-12.3456"); 119 C!PGdouble_precision(-1234.56789012345, "double precision", "-1234.56789012345"); 120 C!PGtext("first line\nsecond line", "text", "'first line\nsecond line'"); 121 C!PGtext("12345 ", "char(6)", "'12345'"); 122 C!PGtext("12345", "varchar(6)", "'12345'"); 123 C!(Nullable!PGtext)(Nullable!PGtext.init, "text", "NULL"); 124 C!PGbytea([0x44, 0x20, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x00, 0x21], 125 "bytea", r"E'\\x44 20 72 75 6c 65 73 00 21'"); // "D rules\x00!" (ASCII) 126 C!PGuuid(UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640"), "uuid", "'8b9ab33a-96e9-499b-9c36-aad1fe86d640'"); 127 C!(Nullable!PGuuid)(Nullable!UUID(UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640")), "uuid", "'8b9ab33a-96e9-499b-9c36-aad1fe86d640'"); 128 129 // numeric testing 130 C!PGnumeric("NaN", "numeric", "'NaN'"); 131 132 const string[] numericTests = [ 133 "42", 134 "-42", 135 "0", 136 "0.0146328", 137 "0.0007", 138 "0.007", 139 "0.07", 140 "0.7", 141 "7", 142 "70", 143 "700", 144 "7000", 145 "70000", 146 147 "7.0", 148 "70.0", 149 "700.0", 150 "7000.0", 151 "70000.000", 152 153 "2354877787627192443", 154 "2354877787627192443.0", 155 "2354877787627192443.00000", 156 "-2354877787627192443.00000" 157 ]; 158 159 foreach(i, s; numericTests) 160 C!PGnumeric(s, "numeric", s); 161 162 // date and time testing 163 C!PGdate(Date(2016, 01, 8), "date", "'2016-01-08'"); 164 { 165 import std.exception : assertThrown; 166 167 assertThrown!ValueConvException( 168 C!PGdate(Date(0001, 01, 8), "date", "'5874897-12-31'") 169 ); 170 } 171 C!PGtime_without_time_zone(TimeOfDay(12, 34, 56), "time without time zone", "'12:34:56'"); 172 C!PGtimestamp(PGtimestamp(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12)), "timestamp without time zone", "'1997-12-17 07:37:16.000012'"); 173 C!PGtimestamptz(PGtimestamptz(DateTime(1997, 12, 17, 5, 37, 16), dur!"usecs"(12)), "timestamp with time zone", "'1997-12-17 07:37:16.000012+02'"); 174 C!PGtimestamp(PGtimestamp.earlier, "timestamp", "'-infinity'"); 175 C!PGtimestamp(PGtimestamp.later, "timestamp", "'infinity'"); 176 C!PGtimestamp(PGtimestamp.min, "timestamp", `'4713-01-01 00:00:00 BC'`); 177 C!PGtimestamp(PGtimestamp.max, "timestamp", `'294276-12-31 23:59:59.999999'`); 178 179 // SysTime testing 180 auto testTZ = new immutable SimpleTimeZone(2.dur!"hours"); // custom TZ 181 C!SysTime(SysTime(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12), testTZ), "timestamptz", "'1997-12-17 07:37:16.000012+02'"); 182 183 // json 184 C!PGjson(Json(["float_value": Json(123.456), "text_str": Json("text string")]), "json", `'{"float_value": 123.456,"text_str": "text string"}'`); 185 C!(Nullable!PGjson)(Nullable!Json(Json(["foo": Json("bar")])), "json", `'{"foo":"bar"}'`); 186 187 // json as string 188 C!string(`{"float_value": 123.456}`, "json", `'{"float_value": 123.456}'`); 189 190 // jsonb 191 C!PGjson(Json(["float_value": Json(123.456), "text_str": Json("text string"), "abc": Json(["key": Json("value")])]), "jsonb", 192 `'{"float_value": 123.456, "text_str": "text string", "abc": {"key": "value"}}'`); 193 194 // Geometric 195 import dpq2.conv.geometric: GeometricInstancesForIntegrationTest; 196 mixin GeometricInstancesForIntegrationTest; 197 198 C!Point(Point(1,2), "point", "'(1,2)'"); 199 C!PGline(Line(1,2,3), "line", "'{1,2,3}'"); 200 C!LineSegment(LineSegment(Point(1,2), Point(3,4)), "lseg", "'[(1,2),(3,4)]'"); 201 C!Box(Box(Point(1,2),Point(3,4)), "box", "'(3,4),(1,2)'"); // PG handles box ordered as upper right first and lower left next 202 C!TestPath(TestPath(true, [Point(1,1), Point(2,2), Point(3,3)]), "path", "'((1,1),(2,2),(3,3))'"); 203 C!TestPath(TestPath(false, [Point(1,1), Point(2,2), Point(3,3)]), "path", "'[(1,1),(2,2),(3,3)]'"); 204 C!Polygon(([Point(1,1), Point(2,2), Point(3,3)]), "polygon", "'((1,1),(2,2),(3,3))'"); 205 C!TestCircle(TestCircle(Point(1,2), 10), "circle", "'<(1,2),10>'"); 206 207 //Arrays 208 C!(int[][])([[1,2],[3,4]], "int[]", "'{{1,2},{3,4}}'"); 209 C!(int[])([], "int[]", "'{}'"); // empty array test 210 C!((Nullable!string)[])([Nullable!string("foo"), Nullable!string.init], "text[]", "'{foo,NULL}'"); 211 C!(string[])(["foo","bar", "baz"], "text[]", "'{foo,bar,baz}'"); 212 C!(PGjson[])([Json(["foo": Json(42)])], "json[]", `'{"{\"foo\":42}"}'`); 213 C!(PGuuid[])([UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640")], "uuid[]", "'{8b9ab33a-96e9-499b-9c36-aad1fe86d640}'"); 214 C!(Nullable!(int[]))(Nullable!(int[]).init, "int[]", "NULL"); 215 C!(Nullable!(int[]))(Nullable!(int[])([1,2,3]), "int[]", "'{1,2,3}'"); 216 } 217 }