1 module dpq2.conv.from_d_types; 2 3 @safe: 4 5 import dpq2.conv.time : POSTGRES_EPOCH_DATE, TimeStamp, TimeStampUTC; 6 import dpq2.oids : detectOidTypeFromNative, OidType; 7 import dpq2.value : Value, ValueFormat; 8 9 import std.bitmanip: nativeToBigEndian; 10 import std.datetime.date : Date, DateTime, TimeOfDay; 11 import std.datetime.systime : LocalTime, SysTime, TimeZone, UTC; 12 import std.traits: isNumeric, TemplateArgsOf, Unqual; 13 import std.typecons : Nullable; 14 15 /// Converts Nullable!T to Value 16 Value toValue(T)(T v) 17 if (is(T == Nullable!R, R)) 18 { 19 if (v.isNull) 20 return Value(ValueFormat.BINARY, detectOidTypeFromNative!(TemplateArgsOf!T[0])); 21 else 22 return toValue(v.get); 23 } 24 25 Value toValue(T)(T v) 26 if(isNumeric!(T)) 27 { 28 return Value(v.nativeToBigEndian.dup, detectOidTypeFromNative!T, false, ValueFormat.BINARY); 29 } 30 31 Value toValue(T)(T v, ValueFormat valueFormat = ValueFormat.BINARY) @trusted 32 if(is(T == string)) 33 { 34 if(valueFormat == ValueFormat.TEXT) v = v~'\0'; // for prepareArgs only 35 36 ubyte[] buf = cast(ubyte[]) v; 37 38 return Value(buf, detectOidTypeFromNative!T, false, valueFormat); 39 } 40 41 Value toValue(T)(T v) 42 if(is(T == ubyte[])) 43 { 44 return Value(v, detectOidTypeFromNative!T, false, ValueFormat.BINARY); 45 } 46 47 Value toValue(T : bool)(T v) @trusted 48 if (!is(T == Nullable!R, R)) 49 { 50 ubyte[] buf; 51 buf.length = 1; 52 buf[0] = (v ? 1 : 0); 53 54 return Value(buf, detectOidTypeFromNative!T, false, ValueFormat.BINARY); 55 } 56 57 /// Constructs Value from Date 58 Value toValue(T)(T v) 59 if (is(Unqual!T == Date)) 60 { 61 import std.conv: to; 62 import dpq2.value; 63 import dpq2.conv.time: POSTGRES_EPOCH_JDATE; 64 65 long mj_day = v.modJulianDay; 66 67 // max days isn't checked because Phobos Date days value always fits into Postgres Date 68 if (mj_day < -POSTGRES_EPOCH_JDATE) 69 throw new ValueConvException( 70 ConvExceptionType.DATE_VALUE_OVERFLOW, 71 "Date value doesn't fit into Postgres binary Date", 72 __FILE__, __LINE__ 73 ); 74 75 enum mj_pg_epoch = POSTGRES_EPOCH_DATE.modJulianDay; 76 long days = mj_day - mj_pg_epoch; 77 78 return Value(nativeToBigEndian(days.to!int).dup, OidType.Date, false); 79 } 80 81 /// Constructs Value from TimeOfDay 82 Value toValue(T)(T v) 83 if (is(Unqual!T == TimeOfDay)) 84 { 85 long us = ((60L * v.hour + v.minute) * 60 + v.second) * 1_000_000; 86 87 return Value(nativeToBigEndian(us).dup, OidType.Time, false); 88 } 89 90 /// Constructs Value from TimeStamp or from TimeStampUTC 91 Value toValue(T)(T v) 92 if (is(Unqual!T == TimeStamp) || is(Unqual!T == TimeStampUTC)) 93 { 94 enum mj_pg_epoch = POSTGRES_EPOCH_DATE.modJulianDay; 95 long j = v.dateTime.modJulianDay - mj_pg_epoch; 96 long us = (((j * 24 + v.hour) * 60 + v.minute) * 60 + v.second) * 1_000_000 + v.fracSec.total!"usecs"; 97 98 return Value( 99 nativeToBigEndian(us).dup, 100 is(Unqual!T == TimeStamp) ? OidType.TimeStamp : OidType.TimeStampWithZone, 101 false 102 ); 103 } 104 105 /++ 106 Constructs Value from DateTime 107 It uses Timestamp without TZ as a resulting PG type 108 +/ 109 Value toValue(T)(T v) 110 if (is(Unqual!T == DateTime)) 111 { 112 return TimeStamp(v).toValue; 113 } 114 115 /++ 116 Constructs Value from SysTime 117 Note that SysTime has a precision in hnsecs and PG TimeStamp in usecs. 118 It means that PG value will have 10 times lower precision. 119 And as both types are using long for internal storage it also means that PG TimeStamp can store greater range of values than SysTime. 120 +/ 121 Value toValue(T)(T v) 122 if (is(Unqual!T == SysTime)) 123 { 124 long us = (v - SysTime(POSTGRES_EPOCH_DATE, UTC())).total!"usecs"; 125 126 return Value(nativeToBigEndian(us).dup, OidType.TimeStampWithZone, false); 127 } 128 129 unittest 130 { 131 import dpq2.conv.to_d_types : as; 132 133 { 134 Value v = toValue(cast(short) 123); 135 136 assert(v.oidType == OidType.Int2); 137 assert(v.as!short == 123); 138 } 139 140 { 141 Value v = toValue(-123.456); 142 143 assert(v.oidType == OidType.Float8); 144 assert(v.as!double == -123.456); 145 } 146 147 { 148 Value v = toValue("Test string"); 149 150 assert(v.oidType == OidType.Text); 151 assert(v.as!string == "Test string"); 152 } 153 154 { 155 ubyte[] buf = [0, 1, 2, 3, 4, 5]; 156 Value v = toValue(buf.dup); 157 158 assert(v.oidType == OidType.ByteArray); 159 assert(v.as!(const ubyte[]) == buf); 160 } 161 162 { 163 Value t = toValue(true); 164 Value f = toValue(false); 165 166 assert(t.as!bool == true); 167 assert(f.as!bool == false); 168 } 169 170 { 171 Value v = toValue(Nullable!long(1)); 172 Value nv = toValue(Nullable!bool.init); 173 174 assert(!v.isNull); 175 assert(v.oidType == OidType.Int8); 176 assert(v.as!long == 1); 177 178 assert(nv.isNull); 179 assert(nv.oidType == OidType.Bool); 180 } 181 182 { 183 import std.datetime : DateTime; 184 Value v = toValue(Nullable!TimeStamp(TimeStamp(DateTime(2017, 1, 2)))); 185 186 assert(!v.isNull); 187 assert(v.oidType == OidType.TimeStamp); 188 } 189 190 { 191 // Date: '2018-1-15' 192 auto d = Date(2018, 1, 15); 193 auto v = toValue(d); 194 195 assert(v.oidType == OidType.Date); 196 assert(v.as!Date == d); 197 } 198 199 { 200 auto d = immutable Date(2018, 1, 15); 201 auto v = toValue(d); 202 203 assert(v.oidType == OidType.Date); 204 assert(v.as!Date == d); 205 } 206 207 { 208 // Date: '2000-1-1' 209 auto d = Date(2000, 1, 1); 210 auto v = toValue(d); 211 212 assert(v.oidType == OidType.Date); 213 assert(v.as!Date == d); 214 } 215 216 { 217 // Date: '0010-2-20' 218 auto d = Date(10, 2, 20); 219 auto v = toValue(d); 220 221 assert(v.oidType == OidType.Date); 222 assert(v.as!Date == d); 223 } 224 225 { 226 // Date: max (always fits into Postgres Date) 227 auto d = Date.max; 228 auto v = toValue(d); 229 230 assert(v.oidType == OidType.Date); 231 assert(v.as!Date == d); 232 } 233 234 { 235 // Date: min (overflow) 236 import std.exception: assertThrown; 237 import dpq2.value: ValueConvException; 238 239 auto d = Date.min; 240 assertThrown!ValueConvException(d.toValue); 241 } 242 243 { 244 // DateTime 245 auto d = const DateTime(2018, 2, 20, 1, 2, 3); 246 auto v = toValue(d); 247 248 assert(v.oidType == OidType.TimeStamp); 249 assert(v.as!DateTime == d); 250 } 251 252 { 253 // TimeOfDay: '14:29:17' 254 auto tod = TimeOfDay(14, 29, 17); 255 auto v = toValue(tod); 256 257 assert(v.oidType == OidType.Time); 258 assert(v.as!TimeOfDay == tod); 259 } 260 261 { 262 // SysTime: '2017-11-13T14:29:17.075678Z' 263 auto t = SysTime.fromISOExtString("2017-11-13T14:29:17.075678Z"); 264 auto v = toValue(t); 265 266 assert(v.oidType == OidType.TimeStampWithZone); 267 assert(v.as!SysTime == t); 268 } 269 270 { 271 import core.time : usecs; 272 import std.datetime.date : DateTime; 273 274 // TimeStamp: '2017-11-13 14:29:17.075678' 275 auto t = TimeStamp(DateTime(2017, 11, 13, 14, 29, 17), 75_678.usecs); 276 auto v = toValue(t); 277 278 assert(v.oidType == OidType.TimeStamp); 279 assert(v.as!TimeStamp == t); 280 } 281 }