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 private auto modJulianDayForIntYear(const int year, const ubyte month, const short day) pure 143 { 144 // Wikipedia magic: 145 146 const a = (14 - month) / 12; 147 const y = year + 4800 - a; 148 const m = month + a * 12 - 3; 149 150 const jd = day + (m*153+2)/5 + y*365 + y/4 - y/100 + y/400 - 32045; 151 152 return jd - 2_400_001; 153 } 154 unittest 155 { 156 assert(modJulianDayForIntYear(1858, 11, 17) == 0); 157 assert(modJulianDayForIntYear(2010, 8, 24) == 55_432); 158 assert(modJulianDayForIntYear(1999, 7, 6) == 51_365); 159 } 160 161 /++ 162 Constructs Value from DateTime 163 It uses Timestamp without TZ as a resulting PG type 164 +/ 165 Value toValue(T)(T v) 166 if (is(Unqual!T == DateTime)) 167 { 168 return TimeStamp(v).toValue; 169 } 170 171 /++ 172 Constructs Value from SysTime 173 Note that SysTime has a precision in hnsecs and PG TimeStamp in usecs. 174 It means that PG value will have 10 times lower precision. 175 And as both types are using long for internal storage it also means that PG TimeStamp can store greater range of values than SysTime. 176 +/ 177 Value toValue(T)(T v) 178 if (is(Unqual!T == SysTime)) 179 { 180 long us = (v - SysTime(POSTGRES_EPOCH_DATE, UTC())).total!"usecs"; 181 182 return Value(nativeToBigEndian(us).dup, OidType.TimeStampWithZone, false); 183 } 184 185 /// Constructs Value from UUID 186 Value toValue(T)(T v) 187 if (is(Unqual!T == UUID)) 188 { 189 return Value(v.data.dup, OidType.UUID); 190 } 191 192 /// Constructs Value from Json 193 Value toValue(T)(T v) 194 if (is(Unqual!T == Json)) 195 { 196 auto r = toValue(v.toString); 197 r.oidType = OidType.Json; 198 199 return r; 200 } 201 202 version(unittest) 203 import dpq2.conv.to_d_types : as; 204 205 unittest 206 { 207 Value v = toValue(cast(short) 123); 208 209 assert(v.oidType == OidType.Int2); 210 assert(v.as!short == 123); 211 } 212 213 unittest 214 { 215 Value v = toValue(-123.456); 216 217 assert(v.oidType == OidType.Float8); 218 assert(v.as!double == -123.456); 219 } 220 221 unittest 222 { 223 Value v = toValue("Test string"); 224 225 assert(v.oidType == OidType.Text); 226 assert(v.as!string == "Test string"); 227 } 228 229 // string Null values 230 @system unittest 231 { 232 { 233 import core.exception: AssertError; 234 import std.exception: assertThrown; 235 236 auto v = Nullable!string.init.toValue; 237 assert(v.oidType == OidType.Text); 238 assert(v.isNull); 239 240 assertThrown!AssertError(v.as!string); 241 assert(v.as!(Nullable!string).isNull); 242 } 243 244 { 245 string s; 246 auto v = s.toValue; 247 assert(v.oidType == OidType.Text); 248 assert(!v.isNull); 249 } 250 } 251 252 unittest 253 { 254 immutable ubyte[] buf = [0, 1, 2, 3, 4, 5]; 255 Value v = toValue(buf); 256 257 assert(v.oidType == OidType.ByteArray); 258 assert(v.as!(const ubyte[]) == buf); 259 } 260 261 unittest 262 { 263 Value t = toValue(true); 264 Value f = toValue(false); 265 266 assert(t.as!bool == true); 267 assert(f.as!bool == false); 268 } 269 270 unittest 271 { 272 Value v = toValue(Nullable!long(1)); 273 Value nv = toValue(Nullable!bool.init); 274 275 assert(!v.isNull); 276 assert(v.oidType == OidType.Int8); 277 assert(v.as!long == 1); 278 279 assert(nv.isNull); 280 assert(nv.oidType == OidType.Bool); 281 } 282 283 unittest 284 { 285 import std.datetime : DateTime; 286 287 Value v = toValue(Nullable!TimeStamp(TimeStamp(DateTime(2017, 1, 2)))); 288 289 assert(!v.isNull); 290 assert(v.oidType == OidType.TimeStamp); 291 } 292 293 unittest 294 { 295 // Date: '2018-1-15' 296 auto d = Date(2018, 1, 15); 297 auto v = toValue(d); 298 299 assert(v.oidType == OidType.Date); 300 assert(v.as!Date == d); 301 } 302 303 unittest 304 { 305 auto d = immutable Date(2018, 1, 15); 306 auto v = toValue(d); 307 308 assert(v.oidType == OidType.Date); 309 assert(v.as!Date == d); 310 } 311 312 unittest 313 { 314 // Date: '2000-1-1' 315 auto d = Date(2000, 1, 1); 316 auto v = toValue(d); 317 318 assert(v.oidType == OidType.Date); 319 assert(v.as!Date == d); 320 } 321 322 unittest 323 { 324 // Date: '0010-2-20' 325 auto d = Date(10, 2, 20); 326 auto v = toValue(d); 327 328 assert(v.oidType == OidType.Date); 329 assert(v.as!Date == d); 330 } 331 332 unittest 333 { 334 // Date: max (always fits into Postgres Date) 335 auto d = Date.max; 336 auto v = toValue(d); 337 338 assert(v.oidType == OidType.Date); 339 assert(v.as!Date == d); 340 } 341 342 unittest 343 { 344 // Date: min (overflow) 345 import std.exception: assertThrown; 346 import dpq2.value: ValueConvException; 347 348 auto d = Date.min; 349 assertThrown!ValueConvException(d.toValue); 350 } 351 352 unittest 353 { 354 // DateTime 355 auto d = const DateTime(2018, 2, 20, 1, 2, 3); 356 auto v = toValue(d); 357 358 assert(v.oidType == OidType.TimeStamp); 359 assert(v.as!DateTime == d); 360 } 361 362 unittest 363 { 364 // Nullable!DateTime 365 import std.typecons : nullable; 366 auto d = nullable(DateTime(2018, 2, 20, 1, 2, 3)); 367 auto v = toValue(d); 368 369 assert(v.oidType == OidType.TimeStamp); 370 assert(v.as!(Nullable!DateTime) == d); 371 372 d.nullify(); 373 v = toValue(d); 374 assert(v.oidType == OidType.TimeStamp); 375 assert(v.as!(Nullable!DateTime).isNull); 376 } 377 378 unittest 379 { 380 // TimeOfDay: '14:29:17' 381 auto tod = TimeOfDay(14, 29, 17); 382 auto v = toValue(tod); 383 384 assert(v.oidType == OidType.Time); 385 assert(v.as!TimeOfDay == tod); 386 } 387 388 unittest 389 { 390 // SysTime: '2017-11-13T14:29:17.075678Z' 391 auto t = SysTime.fromISOExtString("2017-11-13T14:29:17.075678Z"); 392 auto v = toValue(t); 393 394 assert(v.oidType == OidType.TimeStampWithZone); 395 assert(v.as!SysTime == t); 396 } 397 398 unittest 399 { 400 import core.time : usecs; 401 import std.datetime.date : DateTime; 402 403 // TimeStamp: '2017-11-13 14:29:17.075678' 404 auto t = TimeStamp(DateTime(2017, 11, 13, 14, 29, 17), 75_678.usecs); 405 auto v = toValue(t); 406 407 assert(v.oidType == OidType.TimeStamp); 408 assert(v.as!TimeStamp == t); 409 } 410 411 unittest 412 { 413 auto j = Json(["foo":Json("bar")]); 414 auto v = j.toValue; 415 416 assert(v.oidType == OidType.Json); 417 assert(v.as!Json == j); 418 419 auto nj = Nullable!Json(j); 420 auto nv = nj.toValue; 421 assert(nv.oidType == OidType.Json); 422 assert(!nv.as!(Nullable!Json).isNull); 423 assert(nv.as!(Nullable!Json).get == j); 424 }