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, BitArray; 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 alias PGvarbit = BitArray; /// BitArray 46 47 private alias VF = ValueFormat; 48 private alias AE = ValueConvException; 49 private alias ET = ConvExceptionType; 50 51 /** 52 Returns cell value as a native string based type from text or binary formatted field. 53 Throws: AssertError if the db value is NULL and Nullable is not used to retrieve the value 54 */ 55 T as(T)(in Value v) pure @trusted 56 if(is(T : const(char)[])) 57 { 58 if(v.format == VF.BINARY) 59 { 60 if(!( 61 v.oidType == OidType.Text || 62 v.oidType == OidType.FixedString || 63 v.oidType == OidType.VariableString || 64 v.oidType == OidType.Numeric || 65 v.oidType == OidType.Json || 66 v.oidType == OidType.Jsonb || 67 v.oidType == OidType.Name 68 )) 69 throwTypeComplaint(v.oidType, "Text, FixedString, VariableString, Name, Numeric, Json or Jsonb", __FILE__, __LINE__); 70 } 71 72 static if(is(T == Nullable!R, R)) 73 { 74 alias Ret = R; 75 76 if (v.isNull) 77 return T.init; 78 } 79 else 80 alias Ret = T; 81 82 Ret r; 83 84 if(v.format == VF.BINARY && v.oidType == OidType.Numeric) 85 r = rawValueToNumeric(v.data); // special case for 'numeric' which represented in dpq2 as string 86 else 87 r = v.valueAsString; 88 89 static if(is(T == Nullable!R2, R2)) 90 return T(r); 91 else 92 return r; 93 } 94 95 @system unittest 96 { 97 import core.exception: AssertError; 98 99 auto v = Value(ValueFormat.BINARY, OidType.Text); 100 101 assert(v.isNull); 102 assertThrown!AssertError(v.as!string == ""); 103 assert(v.as!(Nullable!string).isNull == true); 104 } 105 106 /** 107 Returns value as D type value from binary formatted field. 108 Throws: AssertError if the db value is NULL and Nullable is not used to retrieve the value 109 */ 110 T as(T)(in Value v) 111 if(!is(T : const(char)[]) && !is(T == Bson)) 112 { 113 if(!(v.format == VF.BINARY)) 114 throw new AE(ET.NOT_BINARY, 115 msg_NOT_BINARY, __FILE__, __LINE__); 116 117 static if (is(T == Nullable!R, R)) 118 { 119 if (v.isNull) 120 return T.init; 121 else 122 return T(binaryValueAs!R(v)); 123 } 124 else 125 return binaryValueAs!T(v); 126 } 127 128 @system unittest 129 { 130 auto v = Value([1], OidType.Int4, false, ValueFormat.TEXT); 131 assertThrown!AE(v.as!int); 132 } 133 134 package: 135 136 /* 137 * Something was broken in DMD64 D Compiler v2.079.0-rc.1 so I made this "tunnel" 138 * TODO: remove it and replace by direct binaryValueAs calls 139 */ 140 auto tunnelForBinaryValueAsCalls(T)(in Value v) 141 { 142 return binaryValueAs!T(v); 143 } 144 145 char[] valueAsString(in Value v) pure 146 { 147 return (cast(const(char[])) v.data).to!(char[]); 148 } 149 150 /// Returns value as bytes from binary formatted field 151 T binaryValueAs(T)(in Value v) 152 if(is(T : const ubyte[])) 153 { 154 if(!(v.oidType == OidType.ByteArray)) 155 throwTypeComplaint(v.oidType, "immutable ubyte[]", __FILE__, __LINE__); 156 157 return v.data; 158 } 159 160 @system unittest 161 { 162 auto v = Value([1], OidType.Bool); 163 assertThrown!ValueConvException(v.binaryValueAs!(const ubyte[])); 164 } 165 166 /// Returns cell value as native integer or decimal values 167 /// 168 /// Postgres type "numeric" is oversized and not supported by now 169 T binaryValueAs(T)(in Value v) 170 if( isNumeric!(T) ) 171 { 172 static if(isIntegral!(T)) 173 if(!isNativeInteger(v.oidType)) 174 throwTypeComplaint(v.oidType, "integral types", __FILE__, __LINE__); 175 176 static if(isFloatingPoint!(T)) 177 if(!isNativeFloat(v.oidType)) 178 throwTypeComplaint(v.oidType, "floating point types", __FILE__, __LINE__); 179 180 if(!(v.data.length == T.sizeof)) 181 throw new AE(ET.SIZE_MISMATCH, 182 to!string(v.oidType)~" length ("~to!string(v.data.length)~") isn't equal to native D type "~ 183 to!string(typeid(T))~" size ("~to!string(T.sizeof)~")", 184 __FILE__, __LINE__); 185 186 ubyte[T.sizeof] s = v.data[0..T.sizeof]; 187 return bigEndianToNative!(T)(s); 188 } 189 190 @system unittest 191 { 192 auto v = Value([1], OidType.Bool); 193 assertThrown!ValueConvException(v.binaryValueAs!int); 194 assertThrown!ValueConvException(v.binaryValueAs!float); 195 196 v = Value([1], OidType.Int4); 197 assertThrown!ValueConvException(v.binaryValueAs!int); 198 } 199 200 /// Returns UUID as native UUID value 201 UUID binaryValueAs(T)(in Value v) 202 if( is( T == UUID ) ) 203 { 204 if(!(v.oidType == OidType.UUID)) 205 throwTypeComplaint(v.oidType, "UUID", __FILE__, __LINE__); 206 207 if(!(v.data.length == 16)) 208 throw new AE(ET.SIZE_MISMATCH, 209 "Value length isn't equal to Postgres UUID size", __FILE__, __LINE__); 210 211 UUID r; 212 r.data = v.data; 213 return r; 214 } 215 216 @system unittest 217 { 218 auto v = Value([1], OidType.Int4); 219 assertThrown!ValueConvException(v.binaryValueAs!UUID); 220 221 v = Value([1], OidType.UUID); 222 assertThrown!ValueConvException(v.binaryValueAs!UUID); 223 } 224 225 /// Returns boolean as native bool value 226 bool binaryValueAs(T : bool)(in Value v) 227 if (!is(T == Nullable!R, R)) 228 { 229 if(!(v.oidType == OidType.Bool)) 230 throwTypeComplaint(v.oidType, "bool", __FILE__, __LINE__); 231 232 if(!(v.data.length == 1)) 233 throw new AE(ET.SIZE_MISMATCH, 234 "Value length isn't equal to Postgres boolean size", __FILE__, __LINE__); 235 236 return v.data[0] != 0; 237 } 238 239 @system unittest 240 { 241 auto v = Value([1], OidType.Int4); 242 assertThrown!ValueConvException(v.binaryValueAs!bool); 243 244 v = Value([1,2], OidType.Bool); 245 assertThrown!ValueConvException(v.binaryValueAs!bool); 246 } 247 248 /// Returns Vibe.d's Json 249 Json binaryValueAs(T)(in Value v) @trusted 250 if( is( T == Json ) ) 251 { 252 import dpq2.conv.jsonb: jsonbValueToJson; 253 254 Json res; 255 256 switch(v.oidType) 257 { 258 case OidType.Json: 259 // represent value as text and parse it into Json 260 string t = v.valueAsString; 261 res = parseJsonString(t); 262 break; 263 264 case OidType.Jsonb: 265 res = v.jsonbValueToJson; 266 break; 267 268 default: 269 throwTypeComplaint(v.oidType, "json or jsonb", __FILE__, __LINE__); 270 } 271 272 return res; 273 } 274 275 @system unittest 276 { 277 auto v = Value([1], OidType.Int4); 278 assertThrown!ValueConvException(v.binaryValueAs!Json); 279 } 280 281 import money: currency, roundingMode; 282 283 /// Returns money type 284 /// 285 /// Caution: here is no check of fractional precision while conversion! 286 /// See also: PostgreSQL's "lc_monetary" description and "money" package description 287 T binaryValueAs(T)(in Value v) @trusted 288 if( isInstanceOf!(currency, T) && T.amount.sizeof == 8 ) 289 { 290 import std.format: format; 291 292 if(v.data.length != T.amount.sizeof) 293 throw new AE( 294 ET.SIZE_MISMATCH, 295 format( 296 "%s length (%d) isn't equal to D money type %s size (%d)", 297 v.oidType.to!string, 298 v.data.length, 299 typeid(T).to!string, 300 T.amount.sizeof 301 ) 302 ); 303 304 T r; 305 306 r.amount = v.data[0 .. T.amount.sizeof].bigEndianToNative!long; 307 308 return r; 309 } 310 311 package alias PGTestMoney = currency!("TEST_CURR", 2); //TODO: roundingMode.UNNECESSARY 312 313 unittest 314 { 315 auto v = Value([1], OidType.Money); 316 assertThrown!ValueConvException(v.binaryValueAs!PGTestMoney); 317 } 318 319 T binaryValueAs(T)(in Value v) @trusted 320 if( is(T == BitArray) ) 321 { 322 import core.bitop : bitswap; 323 import std.bitmanip; 324 import std.format: format; 325 import std.range : chunks; 326 327 if(v.data.length < int.sizeof) 328 throw new AE( 329 ET.SIZE_MISMATCH, 330 format( 331 "%s length (%d) is less than minimum int type size (%d)", 332 v.oidType.to!string, 333 v.data.length, 334 int.sizeof 335 ) 336 ); 337 338 auto data = v.data; 339 size_t len = data.read!int; 340 size_t[] newData; 341 foreach (ch; data.chunks(size_t.sizeof)) 342 { 343 ubyte[size_t.sizeof] tmpData; 344 tmpData[0 .. ch.length] = ch[]; 345 346 // DMD Issue 19693 347 version(DigitalMars) 348 auto re = softBitswap(bigEndianToNative!size_t(tmpData)); 349 else 350 auto re = bitswap(bigEndianToNative!size_t(tmpData)); 351 newData ~= re; 352 } 353 return T(newData, len); 354 } 355 356 unittest 357 { 358 auto v = Value([1], OidType.VariableBitString); 359 assertThrown!ValueConvException(v.binaryValueAs!BitArray); 360 }