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