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