|
1 |
| -/// Structures for MySQL types not built-in to D/Phobos. |
| 1 | +/// Structures for MySQL types not built-in to D/Phobos. |
2 | 2 | module mysql.types;
|
| 3 | +import taggedalgebraic.taggedalgebraic; |
| 4 | +import std.datetime : DateTime, TimeOfDay, Date; |
| 5 | +import std.typecons : Nullable; |
3 | 6 |
|
4 | 7 | /++
|
5 | 8 | A simple struct to represent time difference.
|
@@ -29,3 +32,303 @@ struct Timestamp
|
29 | 32 | {
|
30 | 33 | ulong rep;
|
31 | 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'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 | +MySQLVal is used in all operations interally for mysql-native, and explicitly |
| 171 | +for all safe API calls. Version 3.0.x and earlier of the mysql-native library |
| 172 | +used Variant, so this module provides multiple shims to allow code to "just |
| 173 | +work", and also provides conversion back to Variant. |
| 174 | +
|
| 175 | +$(SAFE_MIGRATION) |
| 176 | ++/ |
| 177 | +alias MySQLVal = TaggedAlgebraic!_MYTYPE; |
| 178 | + |
| 179 | +// helper to convert variants to MySQLVal. Used wherever variant is still used. |
| 180 | +import std.variant : Variant; |
| 181 | +package MySQLVal _toVal(Variant v) |
| 182 | +{ |
| 183 | + int x; |
| 184 | + // unfortunately, we need to use a giant switch. But hopefully people will stop using Variant, and this will go away. |
| 185 | + string ts = v.type.toString(); |
| 186 | + bool isRef; |
| 187 | + if (ts[$-1] == '*') |
| 188 | + { |
| 189 | + ts.length = ts.length-1; |
| 190 | + isRef= true; |
| 191 | + } |
| 192 | + |
| 193 | + import std.meta; |
| 194 | + import std.traits; |
| 195 | + import mysql.exceptions; |
| 196 | + alias BasicTypes = AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, DateTime, TimeOfDay, Date, Timestamp); |
| 197 | + alias ArrayTypes = AliasSeq!(char[], const(char)[], ubyte[], const(ubyte)[], immutable(ubyte)[]); |
| 198 | + switch (ts) |
| 199 | + { |
| 200 | + static foreach(Type; BasicTypes) |
| 201 | + { |
| 202 | + case fullyQualifiedName!Type: |
| 203 | + case "const(" ~ fullyQualifiedName!Type ~ ")": |
| 204 | + case "immutable(" ~ fullyQualifiedName!Type ~ ")": |
| 205 | + case "shared(immutable(" ~ fullyQualifiedName!Type ~ "))": |
| 206 | + if(isRef) |
| 207 | + return MySQLVal(v.get!(const(Type*))); |
| 208 | + else |
| 209 | + return MySQLVal(v.get!(const(Type))); |
| 210 | + } |
| 211 | + static foreach(Type; ArrayTypes) |
| 212 | + { |
| 213 | + case Type.stringof: |
| 214 | + { |
| 215 | + alias ET = Unqual!(typeof(Type.init[0])); |
| 216 | + if(isRef) |
| 217 | + return MySQLVal(v.get!(const(ET[]*))); |
| 218 | + else |
| 219 | + return MySQLVal(v.get!(Type)); |
| 220 | + } |
| 221 | + } |
| 222 | + case "immutable(char)[]": |
| 223 | + // have to do this separately, because everything says "string" but |
| 224 | + // Variant says "immutable(char)[]" |
| 225 | + if(isRef) |
| 226 | + return MySQLVal(v.get!(const(char[]*))); |
| 227 | + else |
| 228 | + return MySQLVal(v.get!(string)); |
| 229 | + case "typeof(null)": |
| 230 | + return MySQLVal(null); |
| 231 | + default: |
| 232 | + throw new MYX("Unsupported Database Variant Type: " ~ ts); |
| 233 | + } |
| 234 | +} |
| 235 | + |
| 236 | +/++ |
| 237 | +Convert a MySQLVal into a Variant. This provides a backwards-compatible shim to use if necessary when transitioning to the safe API. |
| 238 | +
|
| 239 | +$(SAFE_MIGRATION) |
| 240 | ++/ |
| 241 | +Variant asVariant(MySQLVal v) |
| 242 | +{ |
| 243 | + return v.apply!((a) => Variant(a)); |
| 244 | +} |
| 245 | + |
| 246 | +/// ditto |
| 247 | +Nullable!Variant asVariant(Nullable!MySQLVal v) |
| 248 | +{ |
| 249 | + if(v.isNull) |
| 250 | + return Nullable!Variant(); |
| 251 | + return Nullable!Variant(v.get.asVariant); |
| 252 | +} |
| 253 | + |
| 254 | +/++ |
| 255 | +Compatibility layer for MySQLVal. These functions provide methods that |
| 256 | +$(LINK2, http://code.dlang.org/packages/taggedalgebraic, TaggedAlgebraic) |
| 257 | +does not provide in order to keep functionality that was available with Variant. |
| 258 | +
|
| 259 | +Notes: |
| 260 | +
|
| 261 | +The `type` shim should be avoided in favor of using the `kind` property of |
| 262 | +TaggedAlgebraic. |
| 263 | +
|
| 264 | +The `get` shim works differently than the TaggedAlgebraic version, as the |
| 265 | +Variant get function would provide implicit type conversions, but the |
| 266 | +TaggedAlgebraic version did not. |
| 267 | +
|
| 268 | +All shims other than `type` will likely remain as convenience features. |
| 269 | +
|
| 270 | +Note that `peek` is inferred @system because it returns a pointer to the |
| 271 | +provided value. |
| 272 | +
|
| 273 | +$(SAFE_MIGRATION) |
| 274 | ++/ |
| 275 | +bool convertsTo(T)(ref MySQLVal val) |
| 276 | +{ |
| 277 | + return val.apply!((a) => is(typeof(a) : T)); |
| 278 | +} |
| 279 | + |
| 280 | +/// ditto |
| 281 | +T get(T)(auto ref MySQLVal val) |
| 282 | +{ |
| 283 | + static T convert(V)(ref V v) |
| 284 | + { |
| 285 | + static if(is(V : T)) |
| 286 | + return v; |
| 287 | + else |
| 288 | + { |
| 289 | + import mysql.exceptions; |
| 290 | + throw new MYX("Cannot get type " ~ T.stringof ~ " with MySQLVal storing type " ~ V.stringof); |
| 291 | + } |
| 292 | + } |
| 293 | + return val.apply!convert(); |
| 294 | +} |
| 295 | + |
| 296 | +/// ditto |
| 297 | +T coerce(T)(auto ref MySQLVal val) |
| 298 | +{ |
| 299 | + import std.conv : to; |
| 300 | + static T convert(V)(ref V v) |
| 301 | + { |
| 302 | + static if(is(V : T)) |
| 303 | + { |
| 304 | + return v; |
| 305 | + } |
| 306 | + else static if(is(typeof(v.to!T()))) |
| 307 | + { |
| 308 | + return v.to!T; |
| 309 | + } |
| 310 | + else |
| 311 | + { |
| 312 | + import mysql.exceptions; |
| 313 | + throw new MYX("Cannot coerce type " ~ V.stringof ~ " into type " ~ T.stringof); |
| 314 | + } |
| 315 | + } |
| 316 | + return val.apply!convert(); |
| 317 | +} |
| 318 | + |
| 319 | +/// ditto |
| 320 | +TypeInfo type(MySQLVal val) @safe pure nothrow |
| 321 | +{ |
| 322 | + return val.apply!((ref v) => typeid(v)); |
| 323 | +} |
| 324 | + |
| 325 | +/// ditto |
| 326 | +T *peek(T)(ref MySQLVal val) |
| 327 | +{ |
| 328 | + // use exact type. |
| 329 | + import taggedalgebraic.taggedalgebraic : get; |
| 330 | + if(val.hasType!T) |
| 331 | + return &val.get!T; |
| 332 | + return null; |
| 333 | +} |
| 334 | + |
0 commit comments