1 module database.mysql.row; 2 3 import std.algorithm; 4 import std.datetime; 5 import std.traits; 6 import std.typecons; 7 import std.format : format; 8 import std.ascii; 9 10 import database.mysql.exception; 11 import database.mysql.type; 12 13 enum Strict 14 { 15 yes = 0, 16 yesIgnoreNull, 17 no, 18 } 19 20 private uint hashOf(const(char)[] x) 21 { 22 uint hash = 2166136261u; 23 foreach(i; 0..x.length) 24 hash = (hash ^ cast(uint)(std.ascii.toLower(x.ptr[i]))) * 16777619u; 25 26 return hash; 27 } 28 29 private bool equalsCI(const(char)[]x, const(char)[] y) 30 { 31 if (x.length != y.length) 32 return false; 33 34 foreach(i; 0..x.length) 35 { 36 if (std.ascii.toLower(x.ptr[i]) != std.ascii.toLower(y.ptr[i])) 37 return false; 38 } 39 40 return true; 41 } 42 43 struct MySQLRow 44 { 45 @property size_t opDollar() const 46 { 47 return values_.length; 48 } 49 50 @property const(const(char)[])[] columns() const 51 { 52 return names_; 53 } 54 55 @property ref auto opDispatch(string key)() const 56 { 57 enum hash = hashOf(key); 58 return dispatchFast_(hash, key); 59 } 60 61 auto opSlice() const 62 { 63 return values_; 64 } 65 66 auto opSlice(size_t i, size_t j) const 67 { 68 return values_[i..j]; 69 } 70 71 ref auto opIndex(string key) const 72 { 73 if (auto index = find_(key.hashOf, key)) 74 return values_[index - 1]; 75 throw new MySQLErrorException("Column '" ~ key ~ "' was not found in this result set"); 76 } 77 78 ref auto opIndex(size_t index) const 79 { 80 return values_[index]; 81 } 82 83 const(MySQLValue)* opBinaryRight(string op)(string key) const if (op == "in") 84 { 85 if (auto index = find(key.hashOf, key)) 86 return &values_[index - 1]; 87 return null; 88 } 89 90 int opApply(int delegate(const ref MySQLValue value) del) const 91 { 92 foreach (ref v; values_) 93 if (auto ret = del(v)) 94 return ret; 95 return 0; 96 } 97 98 int opApply(int delegate(ref size_t, const ref MySQLValue) del) const 99 { 100 foreach (ref size_t i, ref v; values_) 101 if (auto ret = del(i, v)) 102 return ret; 103 return 0; 104 } 105 106 int opApply(int delegate(const ref const(char)[], const ref MySQLValue) del) const 107 { 108 foreach (size_t i, ref v; values_) 109 if (auto ret = del(names_[i], v)) 110 return ret; 111 return 0; 112 } 113 114 void toString(Appender)(ref Appender app) const 115 { 116 import std.format : formattedWrite; 117 formattedWrite(&app, "%s", values_); 118 } 119 120 string toString() const 121 { 122 import std.conv : to; 123 return to!string(values_); 124 } 125 126 string[] toStringArray(size_t start = 0, size_t end = ~cast(size_t)0) const 127 { 128 end = min(end, values_.length); 129 start = min(start, values_.length); 130 if (start > end) 131 swap(start, end); 132 133 string[] result; 134 result.reserve(end - start); 135 foreach(i; start..end) 136 result ~= values_[i].toString; 137 return result; 138 } 139 140 string[string] toAA() 141 { 142 string[string] result; 143 foreach(i, name; names_) 144 { 145 result[name] = values_[i].toString(); 146 } 147 148 return result; 149 } 150 151 void toStruct(T, Strict strict = Strict.yesIgnoreNull)(ref T x) if(is(Unqual!T == struct) && !is(T == Strict)) 152 { 153 static if (isTuple!(Unqual!T)) 154 { 155 foreach(i, ref f; x.field) 156 { 157 if (i < values_.length) 158 { 159 static if (strict != Strict.yes) 160 { 161 if (!this[i].isNull) 162 f = this[i].get!(Unqual!(typeof(f))); 163 } 164 else 165 { 166 f = this[i].get!(Unqual!(typeof(f))); 167 } 168 } 169 else static if ((strict == Strict.yes) || (strict == Strict.yesIgnoreNull)) 170 { 171 throw new MySQLErrorException("Column " ~ i ~ " is out of range for this result set"); 172 } 173 } 174 } 175 else 176 { 177 structurize!(strict, null)(x); 178 } 179 } 180 181 void toStruct(Strict strict, T)(ref T x) if (is(Unqual!T == struct)) { 182 toStruct!(T, strict)(x); 183 } 184 185 T toStruct(T, Strict strict = Strict.yesIgnoreNull)() if (is(Unqual!T == struct)) 186 { 187 T result; 188 toStruct!(T, strict)(result); 189 190 return result; 191 } 192 193 package: 194 195 ref auto dispatchFast_(uint hash, string key) const 196 { 197 if (auto index = find_(hash, key)) 198 return opIndex(index - 1); 199 throw new MySQLErrorException("Column '" ~ key ~ "' was not found in this result set"); 200 } 201 202 void header_(MySQLHeader header) 203 { 204 auto headerLen = header.length; 205 auto idealLen = (headerLen + (headerLen >> 2)); 206 auto indexLen = index_.length; 207 208 index_[] = 0; 209 210 if (indexLen < idealLen) 211 { 212 indexLen = max(32, indexLen); 213 214 while (indexLen < idealLen) 215 indexLen <<= 1; 216 217 index_.length = indexLen; 218 } 219 220 auto mask = (indexLen - 1); 221 assert((indexLen & mask) == 0); 222 223 names_.length = headerLen; 224 values_.length = headerLen; 225 foreach (index, ref column; header) 226 { 227 names_[index] = column.name; 228 229 auto hash = hashOf(column.name) & mask; 230 auto probe = 1; 231 232 while (true) 233 { 234 if (index_[hash] == 0) 235 { 236 index_[hash] = cast(uint)index + 1; 237 break; 238 } 239 240 hash = (hash + probe++) & mask; 241 } 242 } 243 } 244 245 uint find_(uint hash, const(char)[] key) const 246 { 247 if (auto mask = index_.length - 1) { 248 assert((index_.length & mask) == 0); 249 250 hash = hash & mask; 251 auto probe = 1; 252 253 while (true) 254 { 255 auto index = index_[hash]; 256 if (index) 257 { 258 if (names_[index - 1].equalsCI(key)) 259 return index; 260 hash = (hash + probe++) & mask; 261 } 262 else 263 { 264 break; 265 } 266 } 267 } 268 269 return 0; 270 } 271 272 ref auto get_(size_t index) 273 { 274 return values_[index]; 275 } 276 277 private: 278 279 void structurize(Strict strict = Strict.yesIgnoreNull, string path = null, T)(ref T result) 280 { 281 enum unCamel = hasUDA!(T, UnCamelCaseAttribute); 282 283 foreach(member; __traits(allMembers, T)) 284 { 285 static if (isWritableDataMember!(T, member)) 286 { 287 static if (!hasUDA!(__traits(getMember, result, member), NameAttribute)) 288 { 289 enum pathMember = path ~ member; 290 static if (unCamel) 291 { 292 enum pathMemberAlt = path ~ member.unCamelCase; 293 } 294 } 295 else 296 { 297 enum pathMember = path ~ getUDAs!(__traits(getMember, result, member), NameAttribute)[0].name; 298 static if (unCamel) 299 { 300 enum pathMemberAlt = pathMember; 301 } 302 } 303 304 alias MemberType = typeof(__traits(getMember, result, member)); 305 306 static if (isPointer!MemberType && !isValueType!(PointerTarget!MemberType) || !isValueType!MemberType) 307 { 308 enum pathNew = pathMember ~ "."; 309 enum st = Select!(hasUDA!(__traits(getMember, result, member), OptionalAttribute), Strict.no, strict); 310 static if (isPointer!MemberType) 311 { 312 if (__traits(getMember, result, member)) 313 structurize!(st, pathNew)(*__traits(getMember, result, member)); 314 } 315 else 316 { 317 structurize!(st, pathNew)(__traits(getMember, result, member)); 318 } 319 } 320 else 321 { 322 enum hash = pathMember.hashOf; 323 static if (unCamel) 324 { 325 enum hashAlt = pathMemberAlt.hashOf; 326 } 327 328 auto index = find_(hash, pathMember); 329 static if (unCamel && (pathMember != pathMemberAlt)) 330 { 331 if (!index) 332 index = find_(hashAlt, pathMemberAlt); 333 } 334 335 if (index) 336 { 337 auto pvalue = values_[index - 1]; 338 339 static if ((strict == Strict.no) || (strict == Strict.yesIgnoreNull) || hasUDA!(__traits(getMember, result, member), OptionalAttribute)) 340 { 341 if (pvalue.isNull) 342 continue; 343 } 344 345 __traits(getMember, result, member) = pvalue.get!(Unqual!MemberType); 346 continue; 347 } 348 349 static if (((strict == Strict.yes) || (strict == Strict.yesIgnoreNull)) && !hasUDA!(__traits(getMember, result, member), OptionalAttribute)) 350 { 351 static if (!unCamel || (pathMember == pathMemberAlt)) 352 { 353 enum ColumnError = format("Column '%s' was not found in this result set", pathMember); 354 } 355 else 356 { 357 enum ColumnError = format("Column '%s' or '%s' was not found in this result set", pathMember, pathMemberAlt); 358 } 359 throw new MySQLErrorException(ColumnError); 360 } 361 } 362 } 363 } 364 } 365 366 MySQLValue[] values_; 367 const(char)[][] names_; 368 uint[] index_; 369 } 370 371 string unCamelCase(string x) 372 { 373 assert(x.length <= 64); 374 375 enum CharClass 376 { 377 LowerCase, 378 UpperCase, 379 Underscore, 380 Digit, 381 } 382 383 CharClass classify(char ch) @nogc @safe pure nothrow 384 { 385 switch (ch) with (CharClass) 386 { 387 case 'A':..case 'Z': 388 return UpperCase; 389 case 'a':..case 'z': 390 return LowerCase; 391 case '0':..case '9': 392 return Digit; 393 case '_': 394 return Underscore; 395 default: 396 assert(false, "only supports identifier-type strings"); 397 } 398 } 399 400 if (x.length > 0) 401 { 402 char[128] buffer; 403 size_t length; 404 405 auto pcls = classify(x.ptr[0]); 406 foreach (i; 0..x.length) with (CharClass) 407 { 408 auto ch = x.ptr[i]; 409 auto cls = classify(ch); 410 411 final switch (cls) 412 { 413 case Underscore: 414 buffer[length++] = '_'; 415 break; 416 case LowerCase: 417 buffer[length++] = ch; 418 break; 419 case UpperCase: 420 if ((pcls != UpperCase) && (pcls != Underscore)) 421 buffer[length++] = '_'; 422 buffer[length++] = std.ascii.toLower(ch); 423 break; 424 case Digit: 425 if (pcls != Digit) 426 buffer[length++] = '_'; 427 buffer[length++] = ch; 428 break; 429 } 430 pcls = cls; 431 432 if (length == buffer.length) 433 break; 434 } 435 return buffer[0..length].idup; 436 } 437 return x; 438 }