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