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