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