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 }