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 @property 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 @property 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 @property 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 @property 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 @property 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 @property 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 @property bool binaryValueAs(T)(in Value v) 140 if( is( T == bool ) ) 141 { 142 if(!(v.oidType == OidType.Bool)) 143 throwTypeComplaint(v.oidType, "bool", __FILE__, __LINE__); 144 145 if(!(v.data.length == 1)) 146 throw new AE(ET.SIZE_MISMATCH, 147 "Value length isn't equal to Postgres boolean size", __FILE__, __LINE__); 148 149 return v.data[0] != 0; 150 } 151 152 /// Returns Vibe.d's Json 153 @property Json binaryValueAs(T)(in Value v) @trusted 154 if( is( T == Json ) ) 155 { 156 Json res; 157 158 switch(v.oidType) 159 { 160 case OidType.Json: 161 // represent value as text and parse it into Json 162 string t = v.valueAsString; 163 res = parseJsonString(t); 164 break; 165 166 case OidType.Jsonb: 167 assert(false, "Is not implemented"); 168 //break; 169 170 default: 171 throwTypeComplaint(v.oidType, "json or jsonb", __FILE__, __LINE__); 172 } 173 174 return res; 175 } 176 177 public void _integration_test( string connParam ) @system 178 { 179 auto conn = new Connection(connParam); 180 181 QueryParams params; 182 params.resultFormat = ValueFormat.BINARY; 183 184 { 185 void testIt(T)(T nativeValue, string pgType, string pgValue) 186 { 187 params.sqlCommand = "SELECT "~pgValue~"::"~pgType~" as d_type_test_value"; 188 auto answer = conn.execParams(params); 189 immutable Value v = answer[0][0]; 190 auto result = v.as!T; 191 192 assert(result == nativeValue, "Received unexpected value\nreceived pgType="~to!string(v.oidType)~"\nexpected nativeType="~to!string(typeid(T))~ 193 "\nsent pgValue="~pgValue~"\nexpected nativeValue="~to!string(nativeValue)~"\nresult="~to!string(result)); 194 } 195 196 alias C = testIt; // "C" means "case" 197 198 C!PGboolean(true, "boolean", "true"); 199 C!PGboolean(false, "boolean", "false"); 200 C!PGsmallint(-32_761, "smallint", "-32761"); 201 C!PGinteger(-2_147_483_646, "integer", "-2147483646"); 202 C!PGbigint(-9_223_372_036_854_775_806, "bigint", "-9223372036854775806"); 203 C!PGreal(-12.3456f, "real", "-12.3456"); 204 C!PGdouble_precision(-1234.56789012345, "double precision", "-1234.56789012345"); 205 C!PGtext("first line\nsecond line", "text", "'first line\nsecond line'"); 206 C!PGtext("12345 ", "char(6)", "'12345'"); 207 C!PGbytea([0x44, 0x20, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x00, 0x21], 208 "bytea", r"E'\\x44 20 72 75 6c 65 73 00 21'"); // "D rules\x00!" (ASCII) 209 C!PGuuid(UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640"), "uuid", "'8b9ab33a-96e9-499b-9c36-aad1fe86d640'"); 210 211 // numeric testing 212 C!PGnumeric("NaN", "numeric", "'NaN'"); 213 214 const string[] numericTests = [ 215 "42", 216 "-42", 217 "0", 218 "0.0146328", 219 "0.0007", 220 "0.007", 221 "0.07", 222 "0.7", 223 "7", 224 "70", 225 "700", 226 "7000", 227 "70000", 228 229 "7.0", 230 "70.0", 231 "700.0", 232 "7000.0", 233 "70000.000", 234 235 "2354877787627192443", 236 "2354877787627192443.0", 237 "2354877787627192443.00000", 238 "-2354877787627192443.00000" 239 ]; 240 241 foreach(i, s; numericTests) 242 C!PGnumeric(s, "numeric", s); 243 244 // date and time testing 245 C!PGdate(Date(2016, 01, 8), "date", "'January 8, 2016'"); 246 C!PGtime_without_time_zone(TimeOfDay(12, 34, 56), "time without time zone", "'12:34:56'"); 247 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'"); 248 C!PGtimestamp_without_time_zone(TimeStampWithoutTZ.max, "timestamp without time zone", "'infinity'"); 249 C!PGtimestamp_without_time_zone(TimeStampWithoutTZ.min, "timestamp without time zone", "'-infinity'"); 250 251 // json 252 C!PGjson(Json(["float_value": Json(123.456), "text_str": Json("text string")]), "json", "'{\"float_value\": 123.456,\"text_str\": \"text string\"}'"); 253 254 // json as string 255 C!string("{\"float_value\": 123.456}", "json", "'{\"float_value\": 123.456}'"); 256 257 // jsonb 258 //C!PGjson(Json(["integer": Json(123), "float": Json(123.456), "text_string": Json("This is a text string")]), "jsonb", 259 //"'{\"integer\": 123, \"float\": 123.456,\"text_string\": \"This is a text string\"}'"); 260 } 261 }