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 }