1 /// 2 module dpq2.conv.from_d_types; 3 4 @safe: 5 6 public import dpq2.conv.arrays : isArrayType, toValue; 7 public import dpq2.conv.geometric : toValue; 8 import dpq2.conv.time : POSTGRES_EPOCH_DATE, TimeStamp, TimeStampUTC; 9 import dpq2.oids : detectOidTypeFromNative, oidConvTo, OidType; 10 import dpq2.value : Value, ValueFormat; 11 12 import std.bitmanip: nativeToBigEndian; 13 import std.datetime.date: Date, DateTime, TimeOfDay; 14 import std.datetime.systime: SysTime; 15 import std.datetime.timezone: LocalTime, TimeZone, UTC; 16 import std.traits: isImplicitlyConvertible, isNumeric, OriginalType, Unqual; 17 import std.typecons : Nullable; 18 import std.uuid: UUID; 19 import vibe.data.json: Json; 20 21 /// Converts Nullable!T to Value 22 Value toValue(T)(T v) 23 if (is(T == Nullable!R, R) && !(isArrayType!(typeof(v.get)))) 24 { 25 if (v.isNull) 26 return Value(ValueFormat.BINARY, detectOidTypeFromNative!T); 27 else 28 return toValue(v.get); 29 } 30 31 /// ditto 32 Value toValue(T)(T v) 33 if (is(T == Nullable!R, R) && (isArrayType!(typeof(v.get)))) 34 { 35 import dpq2.conv.arrays : arrToValue = toValue; // deprecation import workaround 36 import std.range : ElementType; 37 38 if (v.isNull) 39 return Value(ValueFormat.BINARY, detectOidTypeFromNative!(ElementType!(typeof(v.get))).oidConvTo!"array"); 40 else 41 return arrToValue(v.get); 42 } 43 44 /// 45 Value toValue(T)(T v) 46 if(isNumeric!(T)) 47 { 48 return Value(v.nativeToBigEndian.dup, detectOidTypeFromNative!T, false, ValueFormat.BINARY); 49 } 50 51 /** 52 Converts types implicitly convertible to string to PG Value. 53 Note that if string is null it is written as an empty string. 54 If NULL is a desired DB value, Nullable!string can be used instead. 55 */ 56 Value toValue(T)(T v, ValueFormat valueFormat = ValueFormat.BINARY) @trusted 57 if(is(T : string)) 58 { 59 import std..string : representation; 60 61 static assert(isImplicitlyConvertible!(T, string)); 62 auto buf = (cast(string) v).representation; 63 64 if(valueFormat == ValueFormat.TEXT) buf ~= 0; // for prepareArgs only 65 66 return Value(buf, OidType.Text, false, valueFormat); 67 } 68 69 /// Constructs Value from array of bytes 70 Value toValue(T)(T v) 71 if(is(T : immutable(ubyte)[])) 72 { 73 return Value(v, detectOidTypeFromNative!(ubyte[]), false, ValueFormat.BINARY); 74 } 75 76 /// 77 Value toValue(T : bool)(T v) @trusted 78 if (!is(T == Nullable!R, R)) 79 { 80 immutable ubyte[] buf = [ v ? 1 : 0 ]; 81 82 return Value(buf, detectOidTypeFromNative!T, false, ValueFormat.BINARY); 83 } 84 85 /// Constructs Value from Date 86 Value toValue(T)(T v) 87 if (is(Unqual!T == Date)) 88 { 89 import std.conv: to; 90 import dpq2.value; 91 import dpq2.conv.time: POSTGRES_EPOCH_JDATE; 92 93 long mj_day = v.modJulianDay; 94 95 // max days isn't checked because Phobos Date days value always fits into Postgres Date 96 if (mj_day < -POSTGRES_EPOCH_JDATE) 97 throw new ValueConvException( 98 ConvExceptionType.DATE_VALUE_OVERFLOW, 99 "Date value doesn't fit into Postgres binary Date", 100 __FILE__, __LINE__ 101 ); 102 103 enum mj_pg_epoch = POSTGRES_EPOCH_DATE.modJulianDay; 104 long days = mj_day - mj_pg_epoch; 105 106 return Value(nativeToBigEndian(days.to!int).dup, OidType.Date, false); 107 } 108 109 /// Constructs Value from TimeOfDay 110 Value toValue(T)(T v) 111 if (is(Unqual!T == TimeOfDay)) 112 { 113 long us = ((60L * v.hour + v.minute) * 60 + v.second) * 1_000_000; 114 115 return Value(nativeToBigEndian(us).dup, OidType.Time, false); 116 } 117 118 /// Constructs Value from TimeStamp or from TimeStampUTC 119 Value toValue(T)(T v) 120 if (is(Unqual!T == TimeStamp) || is(Unqual!T == TimeStampUTC)) 121 { 122 long us; /// microseconds 123 124 if(v.isLater) // infinity 125 us = us.max; 126 else if(v.isEarlier) // -infinity 127 us = us.min; 128 else 129 { 130 enum mj_pg_epoch = POSTGRES_EPOCH_DATE.modJulianDay; 131 long j = modJulianDayForIntYear(v.date.year, v.date.month, v.date.day) - mj_pg_epoch; 132 us = (((j * 24 + v.time.hour) * 60 + v.time.minute) * 60 + v.time.second) * 1_000_000 + v.fracSec.total!"usecs"; 133 } 134 135 return Value( 136 nativeToBigEndian(us).dup, 137 is(Unqual!T == TimeStamp) ? OidType.TimeStamp : OidType.TimeStampWithZone, 138 false 139 ); 140 } 141 142 // Wikipedia's magic 143 private auto modJulianDayForIntYear(const int year, const ubyte month, const short day) pure 144 { 145 const a = (14 - month) / 12; 146 const y = year + 4800 - a; 147 const m = month + a * 12 - 3; 148 149 const jd = day + (m*153+2)/5 + y*365 + y/4 - y/100 + y/400 - 32045; 150 151 return jd - 2_400_001; 152 } 153 unittest 154 { 155 assert(modJulianDayForIntYear(1858, 11, 17) == 0); 156 assert(modJulianDayForIntYear(2010, 8, 24) == 55_432); 157 assert(modJulianDayForIntYear(1999, 7, 6) == 51_365); 158 } 159 160 /++ 161 Constructs Value from DateTime 162 It uses Timestamp without TZ as a resulting PG type 163 +/ 164 Value toValue(T)(T v) 165 if (is(Unqual!T == DateTime)) 166 { 167 return TimeStamp(v).toValue; 168 } 169 170 /++ 171 Constructs Value from SysTime 172 Note that SysTime has a precision in hnsecs and PG TimeStamp in usecs. 173 It means that PG value will have 10 times lower precision. 174 And as both types are using long for internal storage it also means that PG TimeStamp can store greater range of values than SysTime. 175 +/ 176 Value toValue(T)(T v) 177 if (is(Unqual!T == SysTime)) 178 { 179 long us = (v - SysTime(POSTGRES_EPOCH_DATE, UTC())).total!"usecs"; 180 181 return Value(nativeToBigEndian(us).dup, OidType.TimeStampWithZone, false); 182 } 183 184 /// Constructs Value from UUID 185 Value toValue(T)(T v) 186 if (is(Unqual!T == UUID)) 187 { 188 return Value(v.data.dup, OidType.UUID); 189 } 190 191 /// Constructs Value from Json 192 Value toValue(T)(T v) 193 if (is(Unqual!T == Json)) 194 { 195 auto r = toValue(v.toString); 196 r.oidType = OidType.Json; 197 198 return r; 199 } 200 201 version(unittest) 202 import dpq2.conv.to_d_types : as; 203 204 unittest 205 { 206 Value v = toValue(cast(short) 123); 207 208 assert(v.oidType == OidType.Int2); 209 assert(v.as!short == 123); 210 } 211 212 unittest 213 { 214 Value v = toValue(-123.456); 215 216 assert(v.oidType == OidType.Float8); 217 assert(v.as!double == -123.456); 218 } 219 220 unittest 221 { 222 Value v = toValue("Test string"); 223 224 assert(v.oidType == OidType.Text); 225 assert(v.as!string == "Test string"); 226 } 227 228 // string Null values 229 @system unittest 230 { 231 { 232 import core.exception: AssertError; 233 import std.exception: assertThrown; 234 235 auto v = Nullable!string.init.toValue; 236 assert(v.oidType == OidType.Text); 237 assert(v.isNull); 238 239 assertThrown!AssertError(v.as!string); 240 assert(v.as!(Nullable!string).isNull); 241 } 242 243 { 244 string s; 245 auto v = s.toValue; 246 assert(v.oidType == OidType.Text); 247 assert(!v.isNull); 248 } 249 } 250 251 unittest 252 { 253 immutable ubyte[] buf = [0, 1, 2, 3, 4, 5]; 254 Value v = toValue(buf); 255 256 assert(v.oidType == OidType.ByteArray); 257 assert(v.as!(const ubyte[]) == buf); 258 } 259 260 unittest 261 { 262 Value t = toValue(true); 263 Value f = toValue(false); 264 265 assert(t.as!bool == true); 266 assert(f.as!bool == false); 267 } 268 269 unittest 270 { 271 Value v = toValue(Nullable!long(1)); 272 Value nv = toValue(Nullable!bool.init); 273 274 assert(!v.isNull); 275 assert(v.oidType == OidType.Int8); 276 assert(v.as!long == 1); 277 278 assert(nv.isNull); 279 assert(nv.oidType == OidType.Bool); 280 } 281 282 unittest 283 { 284 import std.datetime : DateTime; 285 286 Value v = toValue(Nullable!TimeStamp(TimeStamp(DateTime(2017, 1, 2)))); 287 288 assert(!v.isNull); 289 assert(v.oidType == OidType.TimeStamp); 290 } 291 292 unittest 293 { 294 // Date: '2018-1-15' 295 auto d = Date(2018, 1, 15); 296 auto v = toValue(d); 297 298 assert(v.oidType == OidType.Date); 299 assert(v.as!Date == d); 300 } 301 302 unittest 303 { 304 auto d = immutable Date(2018, 1, 15); 305 auto v = toValue(d); 306 307 assert(v.oidType == OidType.Date); 308 assert(v.as!Date == d); 309 } 310 311 unittest 312 { 313 // Date: '2000-1-1' 314 auto d = Date(2000, 1, 1); 315 auto v = toValue(d); 316 317 assert(v.oidType == OidType.Date); 318 assert(v.as!Date == d); 319 } 320 321 unittest 322 { 323 // Date: '0010-2-20' 324 auto d = Date(10, 2, 20); 325 auto v = toValue(d); 326 327 assert(v.oidType == OidType.Date); 328 assert(v.as!Date == d); 329 } 330 331 unittest 332 { 333 // Date: max (always fits into Postgres Date) 334 auto d = Date.max; 335 auto v = toValue(d); 336 337 assert(v.oidType == OidType.Date); 338 assert(v.as!Date == d); 339 } 340 341 unittest 342 { 343 // Date: min (overflow) 344 import std.exception: assertThrown; 345 import dpq2.value: ValueConvException; 346 347 auto d = Date.min; 348 assertThrown!ValueConvException(d.toValue); 349 } 350 351 unittest 352 { 353 // DateTime 354 auto d = const DateTime(2018, 2, 20, 1, 2, 3); 355 auto v = toValue(d); 356 357 assert(v.oidType == OidType.TimeStamp); 358 assert(v.as!DateTime == d); 359 } 360 361 unittest 362 { 363 // TimeOfDay: '14:29:17' 364 auto tod = TimeOfDay(14, 29, 17); 365 auto v = toValue(tod); 366 367 assert(v.oidType == OidType.Time); 368 assert(v.as!TimeOfDay == tod); 369 } 370 371 unittest 372 { 373 // SysTime: '2017-11-13T14:29:17.075678Z' 374 auto t = SysTime.fromISOExtString("2017-11-13T14:29:17.075678Z"); 375 auto v = toValue(t); 376 377 assert(v.oidType == OidType.TimeStampWithZone); 378 assert(v.as!SysTime == t); 379 } 380 381 unittest 382 { 383 import core.time : usecs; 384 import std.datetime.date : DateTime; 385 386 // TimeStamp: '2017-11-13 14:29:17.075678' 387 auto t = TimeStamp(DateTime(2017, 11, 13, 14, 29, 17), 75_678.usecs); 388 auto v = toValue(t); 389 390 assert(v.oidType == OidType.TimeStamp); 391 assert(v.as!TimeStamp == t); 392 } 393 394 unittest 395 { 396 auto j = Json(["foo":Json("bar")]); 397 auto v = j.toValue; 398 399 assert(v.oidType == OidType.Json); 400 assert(v.as!Json == j); 401 402 auto nj = Nullable!Json(j); 403 auto nv = nj.toValue; 404 assert(nv.oidType == OidType.Json); 405 assert(!nv.as!(Nullable!Json).isNull); 406 assert(nv.as!(Nullable!Json).get == j); 407 }