1 module mysql.row; 2 3 4 import std.algorithm; 5 import std.datetime; 6 import std.traits; 7 import std.typecons; 8 9 import mysql.exception; 10 import mysql.type; 11 12 13 template isWritableDataMember(T, string Member) { 14 static if (is(TypeTuple!(__traits(getMember, T, Member)))) { 15 enum isWritableDataMember = false; 16 } else static if (!is(typeof(__traits(getMember, T, Member)))) { 17 enum isWritableDataMember = false; 18 } else static if (is(typeof(__traits(getMember, T, Member)) == void)) { 19 enum isWritableDataMember = false; 20 } else static if (isArray!(typeof(__traits(getMember, T, Member))) && !is(typeof(typeof(__traits(getMember, T, Member)).init[0]) == ubyte) && !is(typeof(__traits(getMember, T, Member)) == string)) { 21 enum isWritableDataMember = false; 22 } else static if (isAssociativeArray!(typeof(__traits(getMember, T, Member)))) { 23 enum isWritableDataMember = false; 24 } else static if (isSomeFunction!(typeof(__traits(getMember, T, Member)))) { 25 enum isWritableDataMember = false; 26 } else static if (!is(typeof((){ T x = void; __traits(getMember, x, Member) = __traits(getMember, x, Member); }()))) { 27 enum isWritableDataMember = false; 28 } else static if ((__traits(getProtection, __traits(getMember, T, Member)) != "public") && (__traits(getProtection, __traits(getMember, T, Member)) != "export")) { 29 enum isWritableDataMember = false; 30 } else { 31 enum isWritableDataMember = true; 32 } 33 } 34 35 36 enum Strict { 37 yes = 0, 38 yesIgnoreNull, 39 no, 40 } 41 42 43 private uint hashOf(const(char)[] x) { 44 uint hash = 5381; 45 foreach(i; 0..x.length) 46 hash = (hash * 33) ^ cast(uint)(std.ascii.toLower(x.ptr[i])); 47 return cast(uint)hash; 48 } 49 50 private bool equalsCI(const(char)[]x, const(char)[] y) { 51 if (x.length != y.length) 52 return false; 53 54 foreach(i; 0..x.length) { 55 if (std.ascii.toLower(x.ptr[i]) != std.ascii.toLower(y.ptr[i])) 56 return false; 57 } 58 59 return true; 60 } 61 62 63 struct MySQLRow { 64 package void header(MySQLHeader header) { 65 auto headerLen = header.length; 66 auto idealLen = (headerLen + (headerLen >> 2)); 67 auto indexLen = index_.length; 68 69 index_[] = 0; 70 71 if (indexLen < idealLen) { 72 indexLen = max(32, indexLen); 73 74 while (indexLen < idealLen) 75 indexLen <<= 1; 76 77 index_.length = indexLen; 78 } 79 80 auto mask = (indexLen - 1); 81 assert((indexLen & mask) == 0); 82 83 names_.length = headerLen; 84 foreach (index, ref column; header) { 85 names_[index] = column.name; 86 87 auto hash = hashOf(column.name) & mask; 88 auto probe = 1; 89 90 while (true) { 91 if (index_[hash] == 0) { 92 index_[hash] = cast(uint)index + 1; 93 break; 94 } 95 96 hash = (hash + probe++) & mask; 97 } 98 } 99 } 100 101 private uint find(uint hash, const(char)[] key) const { 102 if (auto mask = index_.length - 1) { 103 assert((index_.length & mask) == 0); 104 105 hash = hash & mask; 106 auto probe = 1; 107 108 while (true) { 109 auto index = index_[hash]; 110 if (index) { 111 if (names_[index - 1].equalsCI(key)) 112 return index; 113 hash = (hash + probe++) & mask; 114 } else { 115 break; 116 } 117 } 118 } 119 return 0; 120 } 121 122 package void set(size_t index, MySQLValue x) { 123 values_[index] = x; 124 } 125 126 package void nullify(size_t index) { 127 values_[index].nullify(); 128 } 129 130 package @property length(size_t x) { 131 values_.length = x; 132 } 133 134 @property length() const { 135 return values_.length; 136 } 137 138 @property const(const(char)[])[] columns() const { 139 return names_; 140 } 141 142 @property MySQLValue opDispatch(string key)() const { 143 enum hash = hashOf(key); 144 if (auto index = find(hash, key)) 145 return opIndex(index - 1); 146 throw new MySQLErrorException("Column '" ~ key ~ "' was not found in this result set"); 147 } 148 149 MySQLValue opIndex(string key) const { 150 if (auto index = find(key.hashOf, key)) 151 return values_[index - 1]; 152 throw new MySQLErrorException("Column '" ~ key ~ "' was not found in this result set"); 153 } 154 155 MySQLValue opIndex(size_t index) const { 156 return values_[index]; 157 } 158 159 const(MySQLValue)* opBinaryRight(string op)(string key) const if (op == "in") { 160 if (auto index = find(key.hashOf, key)) 161 return &values_[index - 1]; 162 return null; 163 } 164 165 int opApply(int delegate(const ref MySQLValue value) del) const { 166 foreach (ref v; values_) 167 if (auto ret = del(v)) 168 return ret; 169 return 0; 170 } 171 172 int opApply(int delegate(ref size_t, const ref MySQLValue) del) const { 173 foreach (ref size_t i, ref v; values_) 174 if (auto ret = del(i, v)) 175 return ret; 176 return 0; 177 } 178 179 int opApply(int delegate(const ref const(char)[], const ref MySQLValue) del) const { 180 foreach (size_t i, ref v; values_) 181 if (auto ret = del(names_[i], v)) 182 return ret; 183 return 0; 184 } 185 186 void toString(Appender)(ref Appender app) const { 187 import std.format : formattedWrite; 188 formattedWrite(&app, "%s", values_); 189 } 190 191 string toString() const { 192 import std.conv : to; 193 return to!string(values_); 194 } 195 196 string[] toStringArray(size_t start = 0, size_t end = ~cast(size_t)0) const { 197 end = min(end, values_.length); 198 start = min(start, values_.length); 199 if (start > end) 200 swap(start, end); 201 202 string[] result; 203 result.reserve(end - start); 204 foreach(i; start..end) 205 result ~= values_[i].toString; 206 return result; 207 } 208 209 void toStruct(T, Strict strict = Strict.yesIgnoreNull)(ref T x) if(is(Unqual!T == struct)) { 210 static if (isTuple!(Unqual!T)) { 211 foreach(i, ref f; x.field) { 212 if (i < length) { 213 static if (strict != Strict.yes) { 214 if (this[i].isNull) 215 continue; 216 } 217 218 f = this[i].get!(Unqual!(typeof(f))); 219 continue; 220 } 221 222 static if ((strict == Strict.yes) || (strict == Strict.yesIgnoreNull)) { 223 throw new MySQLErrorException("Column " ~ i ~ " is out of range for this result set"); 224 } 225 } 226 } else { 227 structurize!(T, strict, null)(x); 228 } 229 } 230 231 T toStruct(T, Strict strict = Strict.yesIgnoreNull)() if (is(Unqual!T == struct)) { 232 T result; 233 toStruct!(T, strict)(result); 234 return result; 235 } 236 237 private: 238 void structurize(T, Strict strict = Strict.yesIgnoreNull, string path = null)(ref T result) { 239 foreach(member; __traits(allMembers, T)) { 240 static if (isWritableDataMember!(T, member)) { 241 enum pathMember = path ~ member; 242 alias MemberType = typeof(__traits(getMember, result, member)); 243 244 static if (is(Unqual!MemberType == struct) && !is(Unqual!MemberType == Date) && !is(Unqual!MemberType == DateTime) && !is(Unqual!MemberType == SysTime) && !is(Unqual!MemberType == Duration)) { 245 enum pathNew = pathMember ~ "."; 246 structurize!(MemberType, strict, pathNew)(__traits(getMember, result, member)); 247 } else { 248 enum hash = pathMember.hashOf; 249 250 if (auto index = find(hash, pathMember)) { 251 auto pvalue = values_[index - 1]; 252 253 static if ((strict == Strict.no) || (strict == Strict.yesIgnoreNull)) { 254 if (pvalue.isNull) 255 continue; 256 } 257 258 __traits(getMember, result, member) = pvalue.get!(Unqual!MemberType); 259 continue; 260 } 261 262 static if ((strict == Strict.yes) || (strict == Strict.yesIgnoreNull)) { 263 throw new MySQLErrorException("Column '" ~ pathMember ~ "' was not found in this result set"); 264 } 265 } 266 } 267 } 268 } 269 270 MySQLValue[] values_; 271 const(char)[][] names_; 272 uint[] index_; 273 }