1 /// Structures for MySQL types not built-in to D/Phobos. 2 module mysql.types; 3 import taggedalgebraic.taggedalgebraic; 4 import std.datetime : DateTime, TimeOfDay, Date; 5 import std.typecons : Nullable; 6 7 /++ 8 A simple struct to represent time difference. 9 10 D's std.datetime does not have a type that is closely compatible with the MySQL 11 interpretation of a time difference, so we define a struct here to hold such 12 values. 13 +/ 14 struct TimeDiff 15 { 16 bool negative; 17 int days; 18 ubyte hours, minutes, seconds; 19 } 20 21 /++ 22 A D struct to stand for a TIMESTAMP 23 24 It is assumed that insertion of TIMESTAMP values will not be common, since in general, 25 such columns are used for recording the time of a row insertion, and are filled in 26 automatically by the server. If you want to force a timestamp value in a prepared insert, 27 set it into a timestamp struct as an unsigned long in the format YYYYMMDDHHMMSS 28 and use that for the appropriate parameter. When TIMESTAMPs are retrieved as part of 29 a result set it will be as DateTime structs. 30 +/ 31 struct Timestamp 32 { 33 ulong rep; 34 } 35 36 private union _MYTYPE 37 { 38 @safeOnly: 39 // blobs are const because of the indirection. In this case, it's not 40 // important because nobody is going to use MySQLVal to maintain their 41 // ubyte array. 42 ubyte[] Blob; 43 const(ubyte)[] CBlob; 44 45 typeof(null) Null; 46 bool Bit; 47 ubyte UByte; 48 byte Byte; 49 ushort UShort; 50 short Short; 51 uint UInt; 52 int Int; 53 ulong ULong; 54 long Long; 55 float Float; 56 double Double; 57 .DateTime DateTime; 58 TimeOfDay Time; 59 .Timestamp Timestamp; 60 .Date Date; 61 62 @disableIndex string Text; 63 @disableIndex const(char)[] CText; 64 65 // pointers 66 const(bool)* BitRef; 67 const(ubyte)* UByteRef; 68 const(byte)* ByteRef; 69 const(ushort)* UShortRef; 70 const(short)* ShortRef; 71 const(uint)* UIntRef; 72 const(int)* IntRef; 73 const(ulong)* ULongRef; 74 const(long)* LongRef; 75 const(float)* FloatRef; 76 const(double)* DoubleRef; 77 const(.DateTime)* DateTimeRef; 78 const(TimeOfDay)* TimeRef; 79 const(.Date)* DateRef; 80 const(string)* TextRef; 81 const(char[])* CTextRef; 82 const(ubyte[])* BlobRef; 83 const(.Timestamp)* TimestampRef; 84 } 85 86 /++ 87 MySQLVal is mysql-native's tagged algebraic type that supports only @safe usage 88 (see $(LINK2 http://code.dlang.org/packages/taggedalgebraic, TaggedAlgebraic) 89 for more information on the features of this type). Note that TaggedAlgebraic 90 has UFCS methods that are not available without importing that module in your 91 code. 92 93 The type can hold any possible type that MySQL can use or return. The _MYTYPE 94 union, which is a private union for the project, defines the names of the types 95 that can be stored. These names double as the names for the MySQLVal.Kind 96 enumeration. To that end, this is the entire union definition: 97 98 ------ 99 private union _MYTYPE 100 { 101 ubyte[] Blob; 102 const(ubyte)[] CBlob; 103 104 typeof(null) Null; 105 bool Bit; 106 ubyte UByte; 107 byte Byte; 108 ushort UShort; 109 short Short; 110 uint UInt; 111 int Int; 112 ulong ULong; 113 long Long; 114 float Float; 115 double Double; 116 std.datetime.DateTime DateTime; 117 std.datetime.TimeOfDay Time; 118 mysql.types.Timestamp Timestamp; 119 std.datetime.Date Date; 120 121 string Text; 122 const(char)[] CText; 123 124 // pointers 125 const(bool)* BitRef; 126 const(ubyte)* UByteRef; 127 const(byte)* ByteRef; 128 const(ushort)* UShortRef; 129 const(short)* ShortRef; 130 const(uint)* UIntRef; 131 const(int)* IntRef; 132 const(ulong)* ULongRef; 133 const(long)* LongRef; 134 const(float)* FloatRef; 135 const(double)* DoubleRef; 136 const(DateTime)* DateTimeRef; 137 const(TimeOfDay)* TimeRef; 138 const(Date)* DateRef; 139 const(string)* TextRef; 140 const(char[])* CTextRef; 141 const(ubyte[])* BlobRef; 142 const(Timestamp)* TimestampRef; 143 } 144 ------ 145 146 Note that the pointers are all const, as the only use case in mysql-native for them is as rebindable parameters to a Prepared struct. 147 148 MySQLVal allows operations, field, and member function access for each of the supported types without unwrapping the MySQLVal value. For example: 149 150 ------ 151 import mysql.safe; 152 153 // support for comparison is valid for any type that supports it 154 assert(conn.queryValue("SELECT COUNT(*) FROM sometable") > 20); 155 156 // access members of supporting types without unwrapping or verifying type first 157 assert(conn.queryValue("SELECT updated_date FROM someTable WHERE id=5").year == 2020); 158 159 // arithmetic is supported, return type may vary 160 auto val = conn.queryValue("SELECT some_integer FROM sometable WHERE id=5") + 100; 161 static assert(is(typeof(val) == MySQLVal)); 162 assert(val.kind == MySQLVal.Kind.Int); 163 164 // this will be a double and not a MySQLVal, because all types that support 165 // addition with a double result in a double. 166 auto val2 = conn.queryValue("SELECT some_float FROM sometable WHERE id=5") + 100.0; 167 static assert(is(typeof(val2) == double)); 168 ------ 169 170 Note that per [TaggedAlgebraic's API](https://vibed.org/api/taggedalgebraic.taggedalgebraic/TaggedAlgebraic), 171 using operators or members of a MySQLVal that aren't valid for the currently 172 held type will throw an assertion error. If you wish to avoid this, and are not 173 sure of the actual type, first validate the type is as you expect using the 174 `kind` member. 175 176 MySQLVal is used in all operations interally for mysql-native, and explicitly 177 for all safe API calls. Version 3.0.x and earlier of the mysql-native library 178 used Variant, so this module provides multiple shims to allow code to "just 179 work", and also provides conversion back to Variant. 180 181 $(SAFE_MIGRATION) 182 +/ 183 alias MySQLVal = TaggedAlgebraic!_MYTYPE; 184 185 // helper to convert variants to MySQLVal. Used wherever variant is still used. 186 import std.variant : Variant; 187 package MySQLVal _toVal(Variant v) 188 { 189 int x; 190 // unfortunately, we need to use a giant switch. But hopefully people will stop using Variant, and this will go away. 191 string ts = v.type.toString(); 192 bool isRef; 193 if (ts[$-1] == '*') 194 { 195 ts.length = ts.length-1; 196 isRef= true; 197 } 198 199 import std.meta; 200 import std.traits; 201 import mysql.exceptions; 202 alias BasicTypes = AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, DateTime, TimeOfDay, Date, Timestamp); 203 alias ArrayTypes = AliasSeq!(char[], const(char)[], ubyte[], const(ubyte)[], immutable(ubyte)[]); 204 switch (ts) 205 { 206 static foreach(Type; BasicTypes) 207 { 208 case fullyQualifiedName!Type: 209 case "const(" ~ fullyQualifiedName!Type ~ ")": 210 case "immutable(" ~ fullyQualifiedName!Type ~ ")": 211 case "shared(immutable(" ~ fullyQualifiedName!Type ~ "))": 212 if(isRef) 213 return MySQLVal(v.get!(const(Type*))); 214 else 215 return MySQLVal(v.get!(const(Type))); 216 } 217 static foreach(Type; ArrayTypes) 218 { 219 case Type.stringof: 220 { 221 alias ET = Unqual!(typeof(Type.init[0])); 222 if(isRef) 223 return MySQLVal(v.get!(const(ET[]*))); 224 else 225 return MySQLVal(v.get!(Type)); 226 } 227 } 228 case "immutable(char)[]": 229 // have to do this separately, because everything says "string" but 230 // Variant says "immutable(char)[]" 231 if(isRef) 232 return MySQLVal(v.get!(const(char[]*))); 233 else 234 return MySQLVal(v.get!(string)); 235 case "typeof(null)": 236 return MySQLVal(null); 237 default: 238 throw new MYX("Unsupported Database Variant Type: " ~ ts); 239 } 240 } 241 242 /++ 243 Convert a MySQLVal into a Variant. This provides a backwards-compatible shim to use if necessary when transitioning to the safe API. 244 245 $(SAFE_MIGRATION) 246 +/ 247 Variant asVariant(MySQLVal v) 248 { 249 return v.apply!((a) => Variant(a)); 250 } 251 252 /// ditto 253 Nullable!Variant asVariant(Nullable!MySQLVal v) 254 { 255 if(v.isNull) 256 return Nullable!Variant(); 257 return Nullable!Variant(v.get.asVariant); 258 } 259 260 /++ 261 Compatibility layer for MySQLVal. These functions provide methods that 262 $(LINK2 http://code.dlang.org/packages/taggedalgebraic, TaggedAlgebraic) 263 does not provide in order to keep functionality that was available with Variant. 264 265 Notes: 266 267 The `type` shim should be avoided in favor of using the `kind` property of 268 TaggedAlgebraic. 269 270 The `get` shim works differently than the TaggedAlgebraic version, as the 271 Variant get function would provide implicit type conversions, but the 272 TaggedAlgebraic version does not. 273 274 All shims other than `type` will likely remain as convenience features. 275 276 Note that `peek` is inferred @system because it returns a pointer to the 277 provided value. 278 279 $(SAFE_MIGRATION) 280 +/ 281 bool convertsTo(T)(ref MySQLVal val) 282 { 283 return val.apply!((a) => is(typeof(a) : T)); 284 } 285 286 /// ditto 287 T get(T)(auto ref MySQLVal val) 288 { 289 static T convert(V)(ref V v) 290 { 291 static if(is(V : T)) 292 return v; 293 else 294 { 295 import mysql.exceptions; 296 throw new MYX("Cannot get type " ~ T.stringof ~ " from MySQLVal storing type " ~ V.stringof); 297 } 298 } 299 return val.apply!convert(); 300 } 301 302 /// ditto 303 T coerce(T)(auto ref MySQLVal val) 304 { 305 import std.conv : to; 306 static T convert(V)(ref V v) 307 { 308 static if(is(V : T)) 309 { 310 return v; 311 } 312 else static if(is(typeof(v.to!T()))) 313 { 314 return v.to!T; 315 } 316 else 317 { 318 import mysql.exceptions; 319 throw new MYX("Cannot coerce type " ~ V.stringof ~ " into type " ~ T.stringof); 320 } 321 } 322 return val.apply!convert(); 323 } 324 325 /// ditto 326 TypeInfo type(MySQLVal val) @safe pure nothrow 327 { 328 return val.apply!((ref v) => typeid(v)); 329 } 330 331 /// ditto 332 T *peek(T)(ref MySQLVal val) 333 { 334 // use exact type. 335 import taggedalgebraic.taggedalgebraic : get; 336 if(val.hasType!T) 337 return &val.get!T; 338 return null; 339 }