From a6bed51ff612cf522a7fa1c1613622429b7d7f75 Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Sun, 5 Feb 2023 14:12:14 -0500 Subject: [PATCH] Fix #272. Blob types should be accessible via byte[] for unsafe interface. --- integration-tests/source/mysql/test/common.d | 8 ++++ .../source/mysql/test/integration.d | 19 ++++++--- source/mysql/impl/prepared.d | 16 +++++-- source/mysql/types.d | 42 +++++++++++++++---- 4 files changed, 69 insertions(+), 16 deletions(-) diff --git a/integration-tests/source/mysql/test/common.d b/integration-tests/source/mysql/test/common.d index 88a84385..d5d02290 100644 --- a/integration-tests/source/mysql/test/common.d +++ b/integration-tests/source/mysql/test/common.d @@ -122,8 +122,16 @@ version(DoCoreTests) // Timestamp is a bit special as it's converted to a DateTime when // returning from MySQL to avoid having to use a mysql specific type. + // + // byte[] is also special (for now) because it's supported with the + // unsafe portion of prepared statements. However, it's always ubyte[] + // underneath. + // + // TODO: remove this hack for byte[] when unsafe mysql-native is removed. static if(is(T == DateTime) && is(U == Timestamp)) assert(result.get.get!DateTime == expected.toDateTime()); + else static if(is(T == byte[])) + assert(cast(byte[])result.get.get!(ubyte[]) == expected); else assert(result.get.get!T == expected); } diff --git a/integration-tests/source/mysql/test/integration.d b/integration-tests/source/mysql/test/integration.d index f0889135..031e97d0 100644 --- a/integration-tests/source/mysql/test/integration.d +++ b/integration-tests/source/mysql/test/integration.d @@ -982,13 +982,20 @@ debug(MYSQLN_TESTS) assertBasicTests!string("TEXT", "", "aoeu"); assertBasicTests!string("LONGTEXT", "", "aoeu"); - assertBasicTests!(ubyte[])("TINYBLOB", "", "aoeu"); - assertBasicTests!(ubyte[])("MEDIUMBLOB", "", "aoeu"); - assertBasicTests!(ubyte[])("BLOB", "", "aoeu"); - assertBasicTests!(ubyte[])("LONGBLOB", "", "aoeu"); + import std.meta : AliasSeq; + static if(doSafe) alias blobTypes = AliasSeq!(ubyte[]); + else alias blobTypes = AliasSeq!(ubyte[], byte[]); - assertBasicTests!(ubyte[])("TINYBLOB", cast(ubyte[])"".dup, cast(ubyte[])"aoeu".dup); - assertBasicTests!(ubyte[])("TINYBLOB", "".dup, "aoeu".dup); + static foreach(BT; blobTypes) + { + assertBasicTests!(BT)("TINYBLOB", "", "aoeu"); + assertBasicTests!(BT)("MEDIUMBLOB", "", "aoeu"); + assertBasicTests!(BT)("BLOB", "", "aoeu"); + assertBasicTests!(BT)("LONGBLOB", "", "aoeu"); + + assertBasicTests!(BT)("TINYBLOB", cast(BT)"".dup, cast(BT)"aoeu".dup); + assertBasicTests!(BT)("TINYBLOB", "".dup, "aoeu".dup); + } assertBasicTests!Date("DATE", Date(2013, 10, 03)); assertBasicTests!DateTime("DATETIME", DateTime(2013, 10, 03, 12, 55, 35)); diff --git a/source/mysql/impl/prepared.d b/source/mysql/impl/prepared.d index b1ee2dfa..fe099231 100644 --- a/source/mysql/impl/prepared.d +++ b/source/mysql/impl/prepared.d @@ -324,7 +324,13 @@ struct UnsafePrepared void setArg(T)(size_t index, T val, UnsafeParameterSpecialization psn = UPSN.init) @system if(!is(T == Variant)) { - _safe.setArg(index, val, cast(SPSN)psn); + // forward to the safe API, but if not, fall back on what the unsafe + // version did. + static if(__traits(compiles, _safe.setArg(index, val, cast(SPSN)psn))) + _safe.setArg(index, val, cast(SPSN)psn); + else + // convert to variant first, then rely on the runtime to catch it. + setArg(index, Variant(val), psn); } /// ditto @@ -342,8 +348,13 @@ struct UnsafePrepared auto translateArg(alias arg)() { static if(is(typeof(arg) == Variant)) return _toVal(arg); - else + else static if(__traits(compiles, setArg(0, arg))) return arg; + else + // not settable using the safe API, convert to variant first, + // and then use the variant conversion routine to flesh out any + // cases. + return _toVal(Variant(arg)); } _safe.setArgs(staticMap!(translateArg, args)); } @@ -581,4 +592,3 @@ package(mysql) struct PreparedRegistrations(Payload) return info; } } - diff --git a/source/mysql/types.d b/source/mysql/types.d index 9508765e..53932e86 100644 --- a/source/mysql/types.d +++ b/source/mysql/types.d @@ -183,7 +183,7 @@ $(SAFE_MIGRATION) alias MySQLVal = TaggedAlgebraic!_MYTYPE; // helper to convert variants to MySQLVal. Used wherever variant is still used. -import std.variant : Variant; +private import std.variant : Variant; package MySQLVal _toVal(Variant v) { int x; @@ -197,18 +197,36 @@ package MySQLVal _toVal(Variant v) } import std.meta; - import std.traits; import mysql.exceptions; + import std.traits : Unqual; + // much simpler/focused fullyqualifiedname template + template FQN(T) { + static if(is(T == DateTime) || is(T == Date) || is(T == TimeOfDay)) + enum FQN = "std.datetime.date." ~ T.stringof; + else static if(is(T == Timestamp)) + enum FQN = "mysql.types.Timestamp"; + else + enum FQN = T.stringof; + } + alias BasicTypes = AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, DateTime, TimeOfDay, Date, Timestamp); - alias ArrayTypes = AliasSeq!(char[], const(char)[], ubyte[], const(ubyte)[], immutable(ubyte)[]); + alias ArrayTypes = AliasSeq!(char[], const(char)[], + ubyte[], const(ubyte)[], immutable(ubyte)[]); + + // types that worked with the old system via Variant, but have to be + // converted to work with MySQLVal + alias ConvertibleTypes = AliasSeq!(byte[], const(byte)[], immutable(byte)[]); + alias ConvertedTypes = AliasSeq!(const(ubyte[]), const(ubyte[]), const(ubyte[]) ); + static assert(ConvertibleTypes.length == ConvertedTypes.length); + switch (ts) { static foreach(Type; BasicTypes) { - case fullyQualifiedName!Type: - case "const(" ~ fullyQualifiedName!Type ~ ")": - case "immutable(" ~ fullyQualifiedName!Type ~ ")": - case "shared(immutable(" ~ fullyQualifiedName!Type ~ "))": + case FQN!Type: + case "const(" ~ FQN!Type ~ ")": + case "immutable(" ~ FQN!Type ~ ")": + case "shared(immutable(" ~ FQN!Type ~ "))": if(isRef) return MySQLVal(v.get!(const(Type*))); else @@ -225,6 +243,16 @@ package MySQLVal _toVal(Variant v) return MySQLVal(v.get!(Type)); } } + static foreach(i; 0 .. ConvertibleTypes.length) + { + case ConvertibleTypes[i].stringof: + { + if(isRef) + return MySQLVal(cast(ConvertedTypes[i]*)v.get!(ConvertibleTypes[i]*)); + else + return MySQLVal(cast(ConvertedTypes[i])v.get!(ConvertibleTypes[i])); + } + } case "immutable(char)[]": // have to do this separately, because everything says "string" but // Variant says "immutable(char)[]"