1 module dpq2.conv.to_d_types; 2 3 @safe: 4 5 import dpq2; 6 7 import dpq2.conv.numeric: rawValueToNumeric; 8 import dpq2.conv.time: binaryValueAs, TimeStampWithoutTZ; 9 import dpq2.exception; 10 11 import vibe.data.json: Json, parseJsonString; 12 import vibe.data.bson: Bson; 13 import std.traits; 14 import std.uuid; 15 import std.datetime; 16 import std.traits: isScalarType; 17 import std.bitmanip: bigEndianToNative; 18 import std.conv: to; 19 20 // Supported PostgreSQL binary types 21 alias PGboolean = bool; /// boolean 22 alias PGsmallint = short; /// smallint 23 alias PGinteger = int; /// integer 24 alias PGbigint = long; /// bigint 25 alias PGreal = float; /// real 26 alias PGdouble_precision = double; /// double precision 27 alias PGtext = string; /// text 28 alias PGnumeric = string; /// numeric represented as string 29 alias PGbytea = const ubyte[]; /// bytea 30 alias PGuuid = UUID; /// UUID 31 alias PGdate = Date; /// Date (no time of day) 32 alias PGtime_without_time_zone = TimeOfDay; /// Time of day (no date) 33 alias PGtimestamp_without_time_zone = TimeStampWithoutTZ; /// Both date and time (no time zone) 34 alias PGjson = Json; /// json or jsonb 35 36 package void throwTypeComplaint(OidType receivedType, string expectedType, string file, size_t line) pure 37 { 38 throw new AnswerConvException( 39 ConvExceptionType.NOT_IMPLEMENTED, 40 "Format of the column ("~to!string(receivedType)~") doesn't match to D native "~expectedType, 41 file, line 42 ); 43 } 44 45 private alias VF = ValueFormat; 46 private alias AE = AnswerConvException; 47 private alias ET = ConvExceptionType; 48 49 /// Returns cell value as native string type from text or binary formatted field 50 string as(T)(in Value v) pure @trusted 51 if(is(T == string)) 52 { 53 if(v.format == VF.BINARY) 54 { 55 if(!( 56 v.oidType == OidType.Text || 57 v.oidType == OidType.FixedString || 58 v.oidType == OidType.Numeric || 59 v.oidType == OidType.Json 60 )) 61 throwTypeComplaint(v.oidType, "Text, FixedString, Numeric or Json", __FILE__, __LINE__); 62 63 if(v.oidType == OidType.Numeric) 64 return rawValueToNumeric(v.data); 65 } 66 67 return valueAsString(v); 68 } 69 70 /// Returns value as D type value from binary formatted field 71 T as(T)(in Value v) 72 if(!is(T == string) && !is(T == Bson)) 73 { 74 if(!(v.format == VF.BINARY)) 75 throw new AE(ET.NOT_BINARY, 76 msg_NOT_BINARY, __FILE__, __LINE__); 77 78 return binaryValueAs!T(v); 79 } 80 81 package: 82 83 string valueAsString(in Value v) pure 84 { 85 return (cast(const(char[])) v.data).to!string; 86 } 87 88 /// Returns value as bytes from binary formatted field 89 T binaryValueAs(T)(in Value v) 90 if( is( T == const(ubyte[]) ) ) 91 { 92 if(!(v.oidType == OidType.ByteArray)) 93 throwTypeComplaint(v.oidType, "ubyte[] or string", __FILE__, __LINE__); 94 95 return v.data; 96 } 97 98 /// Returns cell value as native integer or decimal values 99 /// 100 /// Postgres type "numeric" is oversized and not supported by now 101 T binaryValueAs(T)(in Value v) 102 if( isNumeric!(T) ) 103 { 104 static if(isIntegral!(T)) 105 if(!isNativeInteger(v.oidType)) 106 throwTypeComplaint(v.oidType, "integral types", __FILE__, __LINE__); 107 108 static if(isFloatingPoint!(T)) 109 if(!isNativeFloat(v.oidType)) 110 throwTypeComplaint(v.oidType, "floating point types", __FILE__, __LINE__); 111 112 if(!(v.data.length == T.sizeof)) 113 throw new AE(ET.SIZE_MISMATCH, 114 to!string(v.oidType)~" length ("~to!string(v.data.length)~") isn't equal to native D type "~ 115 to!string(typeid(T))~" size ("~to!string(T.sizeof)~")", 116 __FILE__, __LINE__); 117 118 ubyte[T.sizeof] s = v.data[0..T.sizeof]; 119 return bigEndianToNative!(T)(s); 120 } 121 122 /// Returns UUID as native UUID value 123 UUID binaryValueAs(T)(in Value v) 124 if( is( T == UUID ) ) 125 { 126 if(!(v.oidType == OidType.UUID)) 127 throwTypeComplaint(v.oidType, "UUID", __FILE__, __LINE__); 128 129 if(!(v.data.length == 16)) 130 throw new AE(ET.SIZE_MISMATCH, 131 "Value length isn't equal to Postgres UUID size", __FILE__, __LINE__); 132 133 UUID r; 134 r.data = v.data; 135 return r; 136 } 137 138 /// Returns boolean as native bool value 139 bool binaryValueAs(T : bool)(in Value v) 140 { 141 if(!(v.oidType == OidType.Bool)) 142 throwTypeComplaint(v.oidType, "bool", __FILE__, __LINE__); 143 144 if(!(v.data.length == 1)) 145 throw new AE(ET.SIZE_MISMATCH, 146 "Value length isn't equal to Postgres boolean size", __FILE__, __LINE__); 147 148 return v.data[0] != 0; 149 } 150 151 /// Returns Vibe.d's Json 152 Json binaryValueAs(T)(in Value v) @trusted 153 if( is( T == Json ) ) 154 { 155 import dpq2.conv.jsonb: jsonbValueToJson; 156 157 Json res; 158 159 switch(v.oidType) 160 { 161 case OidType.Json: 162 // represent value as text and parse it into Json 163 string t = v.valueAsString; 164 res = parseJsonString(t); 165 break; 166 167 case OidType.Jsonb: 168 res = v.jsonbValueToJson; 169 break; 170 171 default: 172 throwTypeComplaint(v.oidType, "json or jsonb", __FILE__, __LINE__); 173 } 174 175 return res; 176 } 177 178 public void _integration_test( string connParam ) @system 179 { 180 auto conn = new Connection(connParam); 181 182 QueryParams params; 183 params.resultFormat = ValueFormat.BINARY; 184 185 { 186 void testIt(T)(T nativeValue, string pgType, string pgValue) 187 { 188 params.sqlCommand = "SELECT "~pgValue~"::"~pgType~" as d_type_test_value"; 189 auto answer = conn.execParams(params); 190 immutable Value v = answer[0][0]; 191 auto result = v.as!T; 192 193 assert(result == nativeValue, "Received unexpected value\nreceived pgType="~to!string(v.oidType)~"\nexpected nativeType="~to!string(typeid(T))~ 194 "\nsent pgValue="~pgValue~"\nexpected nativeValue="~to!string(nativeValue)~"\nresult="~to!string(result)); 195 } 196 197 alias C = testIt; // "C" means "case" 198 199 C!PGboolean(true, "boolean", "true"); 200 C!PGboolean(false, "boolean", "false"); 201 C!PGsmallint(-32_761, "smallint", "-32761"); 202 C!PGinteger(-2_147_483_646, "integer", "-2147483646"); 203 C!PGbigint(-9_223_372_036_854_775_806, "bigint", "-9223372036854775806"); 204 C!PGreal(-12.3456f, "real", "-12.3456"); 205 C!PGdouble_precision(-1234.56789012345, "double precision", "-1234.56789012345"); 206 C!PGtext("first line\nsecond line", "text", "'first line\nsecond line'"); 207 C!PGtext("12345 ", "char(6)", "'12345'"); 208 C!PGbytea([0x44, 0x20, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x00, 0x21], 209 "bytea", r"E'\\x44 20 72 75 6c 65 73 00 21'"); // "D rules\x00!" (ASCII) 210 C!PGuuid(UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640"), "uuid", "'8b9ab33a-96e9-499b-9c36-aad1fe86d640'"); 211 212 // numeric testing 213 C!PGnumeric("NaN", "numeric", "'NaN'"); 214 215 const string[] numericTests = [ 216 "42", 217 "-42", 218 "0", 219 "0.0146328", 220 "0.0007", 221 "0.007", 222 "0.07", 223 "0.7", 224 "7", 225 "70", 226 "700", 227 "7000", 228 "70000", 229 230 "7.0", 231 "70.0", 232 "700.0", 233 "7000.0", 234 "70000.000", 235 236 "2354877787627192443", 237 "2354877787627192443.0", 238 "2354877787627192443.00000", 239 "-2354877787627192443.00000" 240 ]; 241 242 foreach(i, s; numericTests) 243 C!PGnumeric(s, "numeric", s); 244 245 // date and time testing 246 C!PGdate(Date(2016, 01, 8), "date", "'January 8, 2016'"); 247 C!PGtime_without_time_zone(TimeOfDay(12, 34, 56), "time without time zone", "'12:34:56'"); 248 C!PGtimestamp_without_time_zone(TimeStampWithoutTZ(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12)), "timestamp without time zone", "'1997-12-17 07:37:16.000012'"); 249 C!PGtimestamp_without_time_zone(TimeStampWithoutTZ.max, "timestamp without time zone", "'infinity'"); 250 C!PGtimestamp_without_time_zone(TimeStampWithoutTZ.min, "timestamp without time zone", "'-infinity'"); 251 252 // json 253 C!PGjson(Json(["float_value": Json(123.456), "text_str": Json("text string")]), "json", "'{\"float_value\": 123.456,\"text_str\": \"text string\"}'"); 254 255 // json as string 256 C!string("{\"float_value\": 123.456}", "json", "'{\"float_value\": 123.456}'"); 257 258 // jsonb 259 C!PGjson(Json(["float_value": Json(123.456), "text_str": Json("text string"), "abc": Json(["key": Json("value")])]), "jsonb", 260 "'{\"float_value\": 123.456, \"text_str\": \"text string\", \"abc\": {\"key\": \"value\"}}'"); 261 } 262 }