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