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