1 /// 2 module dpq2.conv.to_d_types; 3 4 @safe: 5 6 import dpq2.value; 7 import dpq2.oids: OidType, isNativeInteger, isNativeFloat; 8 import dpq2.connection: Connection; 9 import dpq2.query: QueryParams; 10 import dpq2.result: msg_NOT_BINARY; 11 import dpq2.conv.from_d_types; 12 import dpq2.conv.numeric: rawValueToNumeric; 13 import dpq2.conv.time: binaryValueAs, TimeStamp, TimeStampUTC; 14 import dpq2.conv.geometric: binaryValueAs, Line; 15 import dpq2.conv.arrays : binaryValueAs; 16 17 import vibe.data.json: Json, parseJsonString; 18 import vibe.data.bson: Bson; 19 import std.traits; 20 import std.uuid; 21 import std.datetime; 22 import std.traits: isScalarType; 23 import std.typecons : Nullable; 24 import std.bitmanip: bigEndianToNative; 25 import std.conv: to; 26 version (unittest) import std.exception : assertThrown; 27 28 // Supported PostgreSQL binary types 29 alias PGboolean = bool; /// boolean 30 alias PGsmallint = short; /// smallint 31 alias PGinteger = int; /// integer 32 alias PGbigint = long; /// bigint 33 alias PGreal = float; /// real 34 alias PGdouble_precision = double; /// double precision 35 alias PGtext = string; /// text 36 alias PGnumeric = string; /// numeric represented as string 37 alias PGbytea = immutable(ubyte)[]; /// bytea 38 alias PGuuid = UUID; /// UUID 39 alias PGdate = Date; /// Date (no time of day) 40 alias PGtime_without_time_zone = TimeOfDay; /// Time of day (no date) 41 alias PGtimestamp = TimeStamp; /// Both date and time without time zone 42 alias PGtimestamptz = TimeStampUTC; /// Both date and time stored in UTC time zone 43 alias PGjson = Json; /// json or jsonb 44 alias PGline = Line; /// Line (geometric type) 45 46 private alias VF = ValueFormat; 47 private alias AE = ValueConvException; 48 private alias ET = ConvExceptionType; 49 50 /** 51 Returns cell value as a native string based type from text or binary formatted field. 52 Throws: AssertError if the db value is NULL and Nullable is not used to retrieve the value 53 */ 54 T as(T)(in Value v) pure @trusted 55 if(is(T : string)) 56 { 57 if(v.format == VF.BINARY) 58 { 59 if(!( 60 v.oidType == OidType.Text || 61 v.oidType == OidType.FixedString || 62 v.oidType == OidType.VariableString || 63 v.oidType == OidType.Numeric || 64 v.oidType == OidType.Json || 65 v.oidType == OidType.Jsonb || 66 v.oidType == OidType.Name 67 )) 68 throwTypeComplaint(v.oidType, "Text, FixedString, VariableString, Name, Numeric, Json or Jsonb", __FILE__, __LINE__); 69 } 70 71 static if(is(T == Nullable!R, R)) 72 { 73 alias Ret = R; 74 75 if (v.isNull) 76 return T.init; 77 } 78 else 79 alias Ret = T; 80 81 Ret r; 82 83 if(v.format == VF.BINARY && v.oidType == OidType.Numeric) 84 r = rawValueToNumeric(v.data); // special case for 'numeric' which represented in dpq2 as string 85 else 86 r = v.valueAsString; 87 88 static if(is(T == Nullable!R2, R2)) 89 return T(r); 90 else 91 return r; 92 } 93 94 @system unittest 95 { 96 import core.exception: AssertError; 97 98 auto v = Value(ValueFormat.BINARY, OidType.Text); 99 100 assert(v.isNull); 101 assertThrown!AssertError(v.as!string == ""); 102 assert(v.as!(Nullable!string).isNull == true); 103 } 104 105 /** 106 Returns value as D type value from binary formatted field. 107 Throws: AssertError if the db value is NULL and Nullable is not used to retrieve the value 108 */ 109 T as(T)(in Value v) 110 if(!is(T : string) && !is(T == Bson)) 111 { 112 if(!(v.format == VF.BINARY)) 113 throw new AE(ET.NOT_BINARY, 114 msg_NOT_BINARY, __FILE__, __LINE__); 115 116 static if (is(T == Nullable!R, R)) 117 { 118 if (v.isNull) 119 return T.init; 120 else 121 return T(binaryValueAs!R(v)); 122 } 123 else 124 return binaryValueAs!T(v); 125 } 126 127 @system unittest 128 { 129 auto v = Value([1], OidType.Int4, false, ValueFormat.TEXT); 130 assertThrown!AE(v.as!int); 131 } 132 133 package: 134 135 /* 136 * Something was broken in DMD64 D Compiler v2.079.0-rc.1 so I made this "tunnel" 137 * TODO: remove it and replace by direct binaryValueAs calls 138 */ 139 auto tunnelForBinaryValueAsCalls(T)(in Value v) 140 { 141 return binaryValueAs!T(v); 142 } 143 144 string valueAsString(in Value v) pure 145 { 146 return (cast(const(char[])) v.data).to!string; 147 } 148 149 /// Returns value as bytes from binary formatted field 150 T binaryValueAs(T)(in Value v) 151 if(is(T : const ubyte[])) 152 { 153 if(!(v.oidType == OidType.ByteArray)) 154 throwTypeComplaint(v.oidType, "immutable ubyte[]", __FILE__, __LINE__); 155 156 return v.data; 157 } 158 159 @system unittest 160 { 161 auto v = Value([1], OidType.Bool); 162 assertThrown!ValueConvException(v.binaryValueAs!(const ubyte[])); 163 } 164 165 /// Returns cell value as native integer or decimal values 166 /// 167 /// Postgres type "numeric" is oversized and not supported by now 168 T binaryValueAs(T)(in Value v) 169 if( isNumeric!(T) ) 170 { 171 static if(isIntegral!(T)) 172 if(!isNativeInteger(v.oidType)) 173 throwTypeComplaint(v.oidType, "integral types", __FILE__, __LINE__); 174 175 static if(isFloatingPoint!(T)) 176 if(!isNativeFloat(v.oidType)) 177 throwTypeComplaint(v.oidType, "floating point types", __FILE__, __LINE__); 178 179 if(!(v.data.length == T.sizeof)) 180 throw new AE(ET.SIZE_MISMATCH, 181 to!string(v.oidType)~" length ("~to!string(v.data.length)~") isn't equal to native D type "~ 182 to!string(typeid(T))~" size ("~to!string(T.sizeof)~")", 183 __FILE__, __LINE__); 184 185 ubyte[T.sizeof] s = v.data[0..T.sizeof]; 186 return bigEndianToNative!(T)(s); 187 } 188 189 @system unittest 190 { 191 auto v = Value([1], OidType.Bool); 192 assertThrown!ValueConvException(v.binaryValueAs!int); 193 assertThrown!ValueConvException(v.binaryValueAs!float); 194 195 v = Value([1], OidType.Int4); 196 assertThrown!ValueConvException(v.binaryValueAs!int); 197 } 198 199 /// Returns UUID as native UUID value 200 UUID binaryValueAs(T)(in Value v) 201 if( is( T == UUID ) ) 202 { 203 if(!(v.oidType == OidType.UUID)) 204 throwTypeComplaint(v.oidType, "UUID", __FILE__, __LINE__); 205 206 if(!(v.data.length == 16)) 207 throw new AE(ET.SIZE_MISMATCH, 208 "Value length isn't equal to Postgres UUID size", __FILE__, __LINE__); 209 210 UUID r; 211 r.data = v.data; 212 return r; 213 } 214 215 @system unittest 216 { 217 auto v = Value([1], OidType.Int4); 218 assertThrown!ValueConvException(v.binaryValueAs!UUID); 219 220 v = Value([1], OidType.UUID); 221 assertThrown!ValueConvException(v.binaryValueAs!UUID); 222 } 223 224 /// Returns boolean as native bool value 225 bool binaryValueAs(T : bool)(in Value v) 226 if (!is(T == Nullable!R, R)) 227 { 228 if(!(v.oidType == OidType.Bool)) 229 throwTypeComplaint(v.oidType, "bool", __FILE__, __LINE__); 230 231 if(!(v.data.length == 1)) 232 throw new AE(ET.SIZE_MISMATCH, 233 "Value length isn't equal to Postgres boolean size", __FILE__, __LINE__); 234 235 return v.data[0] != 0; 236 } 237 238 @system unittest 239 { 240 auto v = Value([1], OidType.Int4); 241 assertThrown!ValueConvException(v.binaryValueAs!bool); 242 243 v = Value([1,2], OidType.Bool); 244 assertThrown!ValueConvException(v.binaryValueAs!bool); 245 } 246 247 /// Returns Vibe.d's Json 248 Json binaryValueAs(T)(in Value v) @trusted 249 if( is( T == Json ) ) 250 { 251 import dpq2.conv.jsonb: jsonbValueToJson; 252 253 Json res; 254 255 switch(v.oidType) 256 { 257 case OidType.Json: 258 // represent value as text and parse it into Json 259 string t = v.valueAsString; 260 res = parseJsonString(t); 261 break; 262 263 case OidType.Jsonb: 264 res = v.jsonbValueToJson; 265 break; 266 267 default: 268 throwTypeComplaint(v.oidType, "json or jsonb", __FILE__, __LINE__); 269 } 270 271 return res; 272 } 273 274 @system unittest 275 { 276 auto v = Value([1], OidType.Int4); 277 assertThrown!ValueConvException(v.binaryValueAs!Json); 278 } 279 280 import money: currency, roundingMode; 281 282 /// Returns money type 283 /// 284 /// Caution: here is no check of fractional precision while conversion! 285 /// See also: PostgreSQL's "lc_monetary" description and "money" package description 286 T binaryValueAs(T)(in Value v) @trusted 287 if( isInstanceOf!(currency, T) && T.amount.sizeof == 8 ) 288 { 289 import std.format: format; 290 291 if(v.data.length != T.amount.sizeof) 292 throw new AE( 293 ET.SIZE_MISMATCH, 294 format( 295 "%s length (%d) isn't equal to D money type %s size (%d)", 296 v.oidType.to!string, 297 v.data.length, 298 typeid(T).to!string, 299 T.amount.sizeof 300 ) 301 ); 302 303 T r; 304 305 r.amount = v.data[0 .. T.amount.sizeof].bigEndianToNative!long; 306 307 return r; 308 } 309 310 package alias PGTestMoney = currency!("TEST_CURR", 2); //TODO: roundingMode.UNNECESSARY 311 312 unittest 313 { 314 auto v = Value([1], OidType.Money); 315 assertThrown!ValueConvException(v.binaryValueAs!PGTestMoney); 316 }