1 /++
2 	Helper functions for generating database stuff.
3 
4 	Note: this is heavily biased toward Postgres
5 +/
6 module arsd.database_generation;
7 
8 /*
9 
10 	FIXME: support partial indexes and maybe "using"
11 	FIXME: support views
12 
13 	Let's put indexes in there too and make index functions be the preferred way of doing a query
14 	by making them convenient af.
15 */
16 
17 private enum UDA;
18 
19 @UDA struct PrimaryKey {
20 	string sql;
21 }
22 
23 @UDA struct Default {
24 	string sql;
25 }
26 
27 @UDA struct Unique { }
28 
29 @UDA struct ForeignKey(alias toWhat, string behavior) {
30 	alias ReferencedTable = __traits(parent, toWhat);
31 }
32 
33 enum CASCADE = "ON UPDATE CASCADE ON DELETE CASCADE";
34 enum NULLIFY = "ON UPDATE CASCADE ON DELETE SET NULL";
35 enum RESTRICT = "ON UPDATE CASCADE ON DELETE RESTRICT";
36 
37 @UDA struct DBName { string name; }
38 
39 struct Nullable(T) {
40 	bool isNull = true;
41 	T value;
42 
43 	void opAssign(typeof(null)) {
44 		isNull = true;
45 	}
46 
47 	void opAssign(T v) {
48 		isNull = false;
49 		value = v;
50 	}
51 
52 	T toArsdJsvar() { return value; }
53 
54 	string toString() {
55 		import std.conv;
56 
57 		if (isNull) {
58 			return "Nullable.null";
59 		}
60 		else {
61 			return to!string(this.value);
62 		}
63 	}
64 }
65 
66 struct Timestamp {
67 	string value;
68 	string toArsdJsvar() { return value; }
69 
70 	// FIXME: timezone
71 	static Timestamp fromStrings(string date, string time) {
72 	        if(time.length < 6)
73 			time ~= ":00";
74 		import std.datetime;
75 		return Timestamp(SysTime.fromISOExtString(date ~ "T" ~ time).toISOExtString());
76 	}
77 }
78 
79 SysTime parseDbTimestamp(Timestamp s) {
80 	return parseDbTimestamp(s.value);
81 }
82 
83 SysTime parseDbTimestamp(string s) {
84 	if(s.length == 0) return SysTime.init;
85 	auto date = s[0 .. 10];
86 	auto time = s[11 .. 20];
87 	auto tz = s[20 .. $];
88 	return SysTime.fromISOExtString(date ~ "T" ~ time ~ tz);
89 }
90 
91 struct Constraint(string sql) {}
92 
93 struct Index(Fields...) {}
94 struct UniqueIndex(Fields...) {}
95 
96 struct Serial {
97 	int value;
98 	int toArsdJsvar() { return value; }
99 	int getValue() { return value; }
100 	alias getValue this;
101 }
102 
103 
104 string generateCreateTableFor(alias O)() {
105 	enum tableName = tableNameFor!O();
106 	string sql = "CREATE TABLE " ~ tableName ~ " (";
107 	string postSql;
108 	bool outputtedPostSql = false;
109 
110 	string afterTableSql;
111 
112 	void addAfterTableSql(string s) {
113 		afterTableSql ~= s;
114 		afterTableSql ~= "\n";
115 	}
116 
117 	void addPostSql(string s) {
118 		if(outputtedPostSql) {
119 			postSql ~= ",";
120 		}
121 		postSql ~= "\n";
122 		postSql ~= "\t" ~ s;
123 		outputtedPostSql = true;
124 	}
125 
126 	bool outputted = false;
127 	static foreach(memberName; __traits(allMembers, O)) {{
128 		alias member = __traits(getMember, O, memberName);
129 		static if(is(typeof(member) == Constraint!constraintSql, string constraintSql)) {
130 		version(dbgenerate_sqlite) {} else { // FIXME: make it work here too, it is the specifics of the constraint strings
131 			if(outputted) {
132 				sql ~= ",";
133 			}
134 			sql ~= "\n";
135 			sql ~= "\tCONSTRAINT " ~ memberName;
136 			sql ~= " ";
137 			sql ~= constraintSql;
138 			outputted = true;
139 		}
140 		} else static if(is(typeof(member) == Index!Fields, Fields...)) {
141 			string fields = "";
142 			static foreach(field; Fields) {
143 				if(fields.length)
144 					fields ~= ", ";
145 				fields ~= __traits(identifier, field);
146 			}
147 			addAfterTableSql("CREATE INDEX " ~ tableName ~ "_" ~ memberName ~ " ON " ~ tableName ~ "("~fields~")");
148 		} else static if(is(typeof(member) == UniqueIndex!Fields, Fields...)) {
149 			string fields = "";
150 			static foreach(field; Fields) {
151 				if(fields.length)
152 					fields ~= ", ";
153 				fields ~= __traits(identifier, field);
154 			}
155 			addAfterTableSql("CREATE UNIQUE INDEX " ~ tableName ~ "_" ~ memberName ~ " ON " ~ tableName ~ "("~fields~")");
156 		} else static if(is(typeof(member) T)) {
157 			if(outputted) {
158 				sql ~= ",";
159 			}
160 			sql ~= "\n";
161 			sql ~= "\t" ~ memberName;
162 
163 			static if(is(T == Nullable!P, P)) {
164 				static if(is(P == int))
165 					sql ~= " INTEGER NULL";
166 				else static if(is(P == string))
167 					sql ~= " TEXT NULL";
168 				else static if(is(P == double))
169 					sql ~= " FLOAT NULL";
170 				else static if(is(P == Timestamp))
171 					sql ~= " TIMESTAMPTZ NULL";
172 				else static assert(0, P.stringof);
173 			} else static if(is(T == int))
174 				sql ~= " INTEGER NOT NULL";
175 			else static if(is(T == Serial)) {
176 				version(dbgenerate_sqlite)
177 					sql ~= " INTEGER PRIMARY KEY AUTOINCREMENT";
178 				else
179 					sql ~= " SERIAL"; // FIXME postgresism
180 			} else static if(is(T == string))
181 				sql ~= " TEXT NOT NULL";
182 			else static if(is(T == double))
183 				sql ~= " FLOAT NOT NULL";
184 			else static if(is(T == bool))
185 				sql ~= " BOOLEAN NOT NULL";
186 			else static if(is(T == Timestamp)) {
187 				version(dbgenerate_sqlite)
188 					sql ~= " TEXT NOT NULL";
189 				else
190 					sql ~= " TIMESTAMPTZ NOT NULL"; // FIXME: postgresism
191 			} else static if(is(T == enum))
192 				sql ~= " INTEGER NOT NULL"; // potentially crap but meh
193 
194 			static foreach(attr; __traits(getAttributes, member)) {
195 				static if(is(typeof(attr) == Default)) {
196 					// FIXME: postgresism there, try current_timestamp in sqlite
197 					version(dbgenerate_sqlite) {
198 						import std.string;
199 						sql ~= " DEFAULT " ~ std..string.replace(attr.sql, "now()", "current_timestamp");
200 					} else
201 						sql ~= " DEFAULT " ~ attr.sql;
202 				} else static if(is(attr == Unique)) {
203 					sql ~= " UNIQUE";
204 				} else static if(is(attr == PrimaryKey)) {
205 					version(dbgenerate_sqlite) {
206 						static if(is(T == Serial)) {} // skip, it is done above
207 						else
208 						addPostSql("PRIMARY KEY(" ~ memberName ~ ")");
209 					} else
210 						addPostSql("PRIMARY KEY(" ~ memberName ~ ")");
211 				} else static if(is(attr == ForeignKey!(to, sqlPolicy), alias to, string sqlPolicy)) {
212 					string refTable = tableNameFor!(__traits(parent, to))();
213 					string refField = to.stringof;
214 					addPostSql("FOREIGN KEY(" ~ memberName ~ ") REFERENCES "~refTable~"("~refField~(sqlPolicy.length ? ") " : ")") ~ sqlPolicy);
215 				}
216 			}
217 
218 			outputted = true;
219 		}
220 	}}
221 
222 	if(postSql.length && outputted)
223 		sql ~= ",\n";
224 
225 	sql ~= postSql;
226 	sql ~= "\n);\n";
227 	sql ~= afterTableSql;
228 
229 	return sql;
230 }
231 
232 string tableNameFor(T)(string def = toTableName(T.stringof)) {
233 	foreach(attr; __traits(getAttributes, T))
234 		static if(is(typeof(attr) == DBName))
235 			def = attr.name;
236 	return def;
237 }
238 
239 string toTableName(string t) {
240 	return plural(50, beautify(t, '_', true));
241 }
242 
243 // copy/pasted from english.d
244 private string plural(int count, string word, string pluralWord = null) {
245 	if(count == 1 || word.length == 0)
246 		return word; // it isn't actually plural
247 
248 	if(pluralWord !is null)
249 		return pluralWord;
250 
251 	switch(word[$ - 1]) {
252 		case 's':
253 			return word ~ "es";
254 		case 'f':
255 			return word[0 .. $-1] ~ "ves";
256 		case 'y':
257 			return word[0 .. $-1] ~ "ies";
258 		case 'a', 'e', 'i', 'o', 'u':
259 		default:
260 			return word ~ "s";
261 	}
262 }
263 
264 // copy/pasted from cgi
265 private string beautify(string name, char space = ' ', bool allLowerCase = false) {
266 	if(name == "id")
267 		return allLowerCase ? name : "ID";
268 
269 	char[160] buffer;
270 	int bufferIndex = 0;
271 	bool shouldCap = true;
272 	bool shouldSpace;
273 	bool lastWasCap;
274 	foreach(idx, char ch; name) {
275 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
276 
277 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
278 			if(lastWasCap) {
279 				// two caps in a row, don't change. Prolly acronym.
280 			} else {
281 				if(idx)
282 					shouldSpace = true; // new word, add space
283 			}
284 
285 			lastWasCap = true;
286 		} else {
287 			lastWasCap = false;
288 		}
289 
290 		if(shouldSpace) {
291 			buffer[bufferIndex++] = space;
292 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
293 			shouldSpace = false;
294 		}
295 		if(shouldCap) {
296 			if(ch >= 'a' && ch <= 'z')
297 				ch -= 32;
298 			shouldCap = false;
299 		}
300 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
301 			ch += 32;
302 		buffer[bufferIndex++] = ch;
303 	}
304 	return buffer[0 .. bufferIndex].idup;
305 }
306 
307 import arsd.database;
308 /++
309 
310 +/
311 void save(O)(ref O t, Database db) {
312 	t.insert(db);
313 }
314 
315 /++
316 
317 +/
318 void insert(O)(ref O t, Database db) {
319 	auto builder = new InsertBuilder;
320 	builder.setTable(tableNameFor!O());
321 
322 	static foreach(memberName; __traits(allMembers, O)) {{
323 		alias member = __traits(getMember, O, memberName);
324 		static if(is(typeof(member) T)) {
325 
326 			static if(is(T == Nullable!P, P)) {
327 				auto v = __traits(getMember, t, memberName);
328 				if(v.isNull)
329 					builder.addFieldWithSql(memberName, "NULL");
330 				else
331 					builder.addVariable(memberName, v.value);
332 			} else static if(is(T == int))
333 				builder.addVariable(memberName, __traits(getMember, t, memberName));
334 			else static if(is(T == Serial)) {
335 				auto v = __traits(getMember, t, memberName).value;
336 				if(v) {
337 					builder.addVariable(memberName, v);
338 				} else {
339 					// skip and let it auto-fill
340 				}
341 			} else static if(is(T == string)) {
342 				builder.addVariable(memberName, __traits(getMember, t, memberName));
343 			} else static if(is(T == double))
344 				builder.addVariable(memberName, __traits(getMember, t, memberName));
345 			else static if(is(T == bool))
346 				builder.addVariable(memberName, __traits(getMember, t, memberName));
347 			else static if(is(T == Timestamp)) {
348 				auto v = __traits(getMember, t, memberName).value;
349 				if(v.length)
350 					builder.addVariable(memberName, v);
351 			} else static if(is(T == enum))
352 				builder.addVariable(memberName, cast(int) __traits(getMember, t, memberName));
353 		}
354 	}}
355 
356 	import std.conv;
357 	version(dbgenerate_sqlite) {
358 		builder.execute(db);
359 		foreach(row; db.query("SELECT max(id) FROM " ~ tableNameFor!O()))
360 			t.id.value = to!int(row[0]);
361 	} else {
362 		static if (__traits(hasMember, O, "id"))
363 		{
364 			foreach(row; builder.execute(db, "RETURNING id")) // FIXME: postgres-ism
365 				t.id.value = to!int(row[0]);
366 		}
367 		else
368 		{
369 			builder.execute(db);
370 		}
371 	}
372 }
373 
374 // Check that insert doesn't require an `id`
375 unittest
376 {
377 	static struct NoPK
378 	{
379 		int a;
380 	}
381 
382 	alias test = insert!NoPK;
383 }
384 ///
385 class RecordNotFoundException : Exception {
386 	this() { super("RecordNotFoundException"); }
387 }
388 
389 /++
390 	Returns a given struct populated from the database. Assumes types known to this module.
391 
392 	MyItem item = db.find!(MyItem.id)(3);
393 
394 	If you just give a type, it assumes the relevant index is "id".
395 
396 +/
397 auto find(alias T)(Database db, int id) {
398 
399 	// FIXME: if T is an index, search by it.
400 	// if it is unique, return an individual item.
401 	// if not, return the array
402 
403 	foreach(record; db.query("SELECT * FROM " ~ tableNameFor!T() ~ " WHERE id = ?", id)) {
404 		T t;
405 		populateFromDbRow(t, record);
406 
407 		return t;
408 		// if there is ever a second record, that's a wtf, but meh.
409 	}
410 	throw new RecordNotFoundException();
411 }
412 
413 private void populateFromDbRow(T)(ref T t, Row record) {
414 	foreach(field, value; record) {
415 		sw: switch(field) {
416 			static foreach(const idx, alias mem; T.tupleof) {
417 				case __traits(identifier, mem):
418 					populateFromDbVal(t.tupleof[idx], value);
419 				break sw;
420 			}
421 			default:
422 				// intentionally blank
423 		}
424 	}
425 }
426 
427 private void populateFromDbVal(V)(ref V val, string value) {
428 	import std.conv;
429 	static if(is(V == Constraint!constraintSql, string constraintSql)) {
430 
431 	} else static if(is(V == Nullable!P, P)) {
432 		// FIXME
433 		if(value.length && value != "null") {
434 			val.isNull = false;
435 			val.value = to!P(value);
436 		}
437 	} else static if(is(V == bool)) {
438 		val = value == "t" || value == "1" || value == "true";
439 	} else static if(is(V == int) || is(V == string) || is(V == double)) {
440 		val = to!V(value);
441 	} else static if(is(V == enum)) {
442 		val = cast(V) to!int(value);
443 	} else static if(is(V == Timestamp)) {
444 		val.value = value;
445 	} else static if(is(V == Serial)) {
446 		val.value = to!int(value);
447 	}
448 }
449 
450 unittest
451 {
452 	static struct SomeStruct
453 	{
454 		int a;
455 		void foo() {}
456 		int b;
457 	}
458 
459 	auto rs = new PredefinedResultSet(
460 		[ "a", "b" ],
461 		[ Row([ "1", "2" ]) ]
462 	);
463 
464 	SomeStruct s;
465 	populateFromDbRow(s, rs.front);
466 
467 	assert(s.a == 1);
468 	assert(s.b == 2);
469 }
470 /++
471 	Gets all the children of that type. Specifically, it looks in T for a ForeignKey referencing B and queries on that.
472 
473 	To do a join through a many-to-many relationship, you could get the children of the join table, then get the children of that...
474 	Or better yet, use real sql. This is more intended to get info where there is one parent row and then many child
475 	rows, not for a combined thing.
476 +/
477 QueryBuilderHelper!(T[]) children(T, B)(B base) {
478 	int countOfAssociations() {
479 		int count = 0;
480 		static foreach(memberName; __traits(allMembers, T))
481 		static foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {{
482 			static if(is(attr == ForeignKey!(K, policy), alias K, string policy)) {
483 				static if(is(attr.ReferencedTable == B))
484 					count++;
485 			}
486 		}}
487 		return count;
488 	}
489 	static assert(countOfAssociations() == 1, T.stringof ~ " does not have exactly one foreign key of type " ~ B.stringof);
490 	string keyName() {
491 		static foreach(memberName; __traits(allMembers, T))
492 		static foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {{
493 			static if(is(attr == ForeignKey!(K, policy), alias K, string policy)) {
494 				static if(is(attr.ReferencedTable == B))
495 					return memberName;
496 			}
497 		}}
498 	}
499 
500 	// return QueryBuilderHelper!(T[])(toTableName(T.stringof)).where!(mixin(keyName ~ " => base.id"));
501 
502 	// changing mixin cuz of regression in dmd 2.088
503 	mixin("return QueryBuilderHelper!(T[])(tableNameFor!T()).where!("~keyName ~ " => base.id);");
504 }
505 
506 /++
507 	Finds the single row associated with a foreign key in `base`.
508 
509 	`T` is used to find the key, unless ambiguous, in which case you must pass `key`.
510 
511 	To do a join through a many-to-many relationship, go to [children] or use real sql.
512 +/
513 T associated(B, T, string key = null)(B base, Database db) {
514 	int countOfAssociations() {
515 		int count = 0;
516 		static foreach(memberName; __traits(allMembers, B))
517 		static foreach(attr; __traits(getAttributes, __traits(getMember, B, memberName))) {
518 			static if(is(attr == ForeignKey!(K, policy), alias K, string policy)) {
519 				static if(is(attr.ReferencedTable == T))
520 					static if(key is null || key == memberName)
521 						count++;
522 			}
523 		}
524 		return count;
525 	}
526 
527 	static if(key is null) {
528 		enum coa = countOfAssociations();
529 		static assert(coa != 0, B.stringof ~ " has no association of type " ~ T);
530 		static assert(coa == 1, B.stringof ~ " has multiple associations of type " ~ T ~ "; please specify the key you want");
531 		static foreach(memberName; __traits(allMembers, B))
532 		static foreach(attr; __traits(getAttributes, __traits(getMember, B, memberName))) {
533 			static if(is(attr == ForeignKey!(K, policy), alias K, string policy)) {
534 				static if(is(attr.ReferencedTable == T))
535 					return db.find!T(__traits(getMember, base, memberName));
536 			}
537 		}
538 	} else {
539 		static assert(countOfAssociations() == 1, B.stringof ~ " does not have a key named " ~ key ~ " of type " ~ T);
540 		static foreach(attr; __traits(getAttributes, __traits(getMember, B, memberName))) {
541 			static if(is(attr == ForeignKey!(K, policy), alias K, string policy)) {
542 				static if(is(attr.ReferencedTable == T)) {
543 					return db.find!T(__traits(getMember, base, key));
544 				}
545 			}
546 		}
547 		assert(0);
548 	}
549 }
550 
551 
552 /++
553 	It will return an aggregate row with a member of type of each table in the join.
554 
555 	Could do an anonymous object for other things in the sql...
556 +/
557 auto join(TableA, TableB, ThroughTable = void)() {}
558 
559 /++
560 
561 +/
562 struct QueryBuilderHelper(T) {
563 	static if(is(T == R[], R))
564 		alias TType = R;
565 	else
566 		alias TType = T;
567 
568 	SelectBuilder selectBuilder;
569 
570 	this(string tableName) {
571 		selectBuilder = new SelectBuilder();
572 		selectBuilder.table = tableName;
573 		selectBuilder.fields = ["*"];
574 	}
575 
576 	T execute(Database db) {
577 		selectBuilder.db = db;
578 		static if(is(T == R[], R)) {
579 
580 		} else {
581 			selectBuilder.limit = 1;
582 		}
583 
584 		T ret;
585 		bool first = true;
586 		foreach(row; db.query(selectBuilder.toString())) {
587 			TType t;
588 			populateFromDbRow(t, row);
589 
590 			static if(is(T == R[], R))
591 				ret ~= t;
592 			else {
593 				if(first) {
594 					ret = t;
595 					first = false;
596 				} else {
597 					assert(0);
598 				}
599 			}
600 		}
601 		return ret;
602 	}
603 
604 	///
605 	typeof(this) orderBy(string criterion)() {
606 		string name() {
607 			int idx = 0;
608 			while(idx < criterion.length && criterion[idx] != ' ')
609 				idx++;
610 			return criterion[0 .. idx];
611 		}
612 
613 		string direction() {
614 			int idx = 0;
615 			while(idx < criterion.length && criterion[idx] != ' ')
616 				idx++;
617 			import std.string;
618 			return criterion[idx .. $].strip;
619 		}
620 
621 		static assert(is(typeof(__traits(getMember, TType, name()))), TType.stringof ~ " has no field " ~ name());
622 		static assert(direction().length == 0 || direction() == "ASC" || direction() == "DESC", "sort direction must be empty, ASC, or DESC");
623 
624 		selectBuilder.orderBys ~= criterion;
625 		return this;
626 	}
627 }
628 
629 QueryBuilderHelper!(T[]) from(T)() {
630 	return QueryBuilderHelper!(T[])(tableNameFor!T());
631 }
632 
633 /// ditto
634 template where(conditions...) {
635 	Qbh where(Qbh)(Qbh this_, string[] sqlCondition...) {
636 		assert(this_.selectBuilder !is null);
637 
638 		static string extractName(string s) {
639 			if(s.length == 0) assert(0);
640 			auto i = s.length - 1;
641 			while(i) {
642 				if(s[i] == ')') {
643 					// got to close paren, now backward to non-identifier char to get name
644 					auto end = i;
645 					while(i) {
646 						if(s[i] == ' ')
647 							return s[i + 1 .. end];
648 						i--;
649 					}
650 					assert(0);
651 				}
652 				i--;
653 			}
654 			assert(0);
655 		}
656 
657 		static foreach(idx, cond; conditions) {{
658 			// I hate this but __parameters doesn't work here for some reason
659 			// see my old thread: https://forum.dlang.org/post/awjuoemsnmxbfgzhgkgx@forum.dlang.org
660 			enum name = extractName(typeof(cond!int).stringof);
661 			auto value = cond(null);
662 
663 			// FIXME: convert the value as necessary
664 			static if(is(typeof(value) == Serial))
665 				auto dbvalue = value.value;
666 			else static if(is(typeof(value) == enum))
667 				auto dbvalue = cast(int) value;
668 			else
669 				auto dbvalue = value;
670 
671 			import std.conv;
672 
673 			static assert(is(typeof(__traits(getMember, Qbh.TType, name))), Qbh.TType.stringof ~ " has no member " ~ name);
674 			static if(is(typeof(__traits(getMember, Qbh.TType, name)) == int)) {
675 				static if(is(typeof(value) : const(int)[])) {
676 					string s;
677 					foreach(v; value) {
678 						if(s.length) s ~= ", ";
679 						s ~= to!string(v);
680 					}
681 					this_.selectBuilder.wheres ~= name ~ " IN (" ~ s ~ ")";
682 				} else {
683 					static assert(is(typeof(value) : const(int)) || is(typeof(value) == Serial), Qbh.TType.stringof ~ " is a integer key, but you passed an incompatible " ~ typeof(value).stringof);
684 
685 					auto placeholder = "?_internal" ~ to!string(idx);
686 					this_.selectBuilder.wheres ~= name ~ " = " ~ placeholder;
687 					this_.selectBuilder.setVariable(placeholder, dbvalue);
688 				}
689 			} else static if(is(typeof(__traits(getMember, Qbh.TType, name)) == Nullable!int)) {
690 				static if(is(typeof(value) : const(int)[])) {
691 					string s;
692 					foreach(v; value) {
693 						if(s.length) s ~= ", ";
694 						s ~= to!string(v);
695 					}
696 					this_.selectBuilder.wheres ~= name ~ " IN (" ~ s ~ ")";
697 				} else {
698 					static assert(is(typeof(value) : const(int)) || is(typeof(value) == Serial), Qbh.TType.stringof ~ " is a integer key, but you passed an incompatible " ~ typeof(value).stringof);
699 
700 					auto placeholder = "?_internal" ~ to!string(idx);
701 					this_.selectBuilder.wheres ~= name ~ " = " ~ placeholder;
702 					this_.selectBuilder.setVariable(placeholder, dbvalue);
703 				}
704 			} else static if(is(typeof(__traits(getMember, Qbh.TType, name)) == Serial)) {
705 				static if(is(typeof(value) : const(int)[])) {
706 					string s;
707 					foreach(v; value) {
708 						if(s.length) s ~= ", ";
709 						s ~= to!string(v);
710 					}
711 					this_.selectBuilder.wheres ~= name ~ " IN (" ~ s ~ ")";
712 				} else {
713 					static assert(is(typeof(value) : const(int)) || is(typeof(value) == Serial), Qbh.TType.stringof ~ " is a integer key, but you passed an incompatible " ~ typeof(value).stringof);
714 
715 					auto placeholder = "?_internal" ~ to!string(idx);
716 					this_.selectBuilder.wheres ~= name ~ " = " ~ placeholder;
717 					this_.selectBuilder.setVariable(placeholder, dbvalue);
718 				}
719 
720 
721 			} else {
722 				static assert(is(typeof(__traits(getMember, Qbh.TType, name)) == typeof(value)), Qbh.TType.stringof ~ "." ~ name ~ " is not of type " ~ typeof(value).stringof);
723 
724 				auto placeholder = "?_internal" ~ to!string(idx);
725 				this_.selectBuilder.wheres ~= name ~ " = " ~ placeholder;
726 				this_.selectBuilder.setVariable(placeholder, dbvalue);
727 			}
728 		}}
729 
730 		this_.selectBuilder.wheres ~= sqlCondition;
731 		return this_;
732 	}
733 }
734 
735 // Basically a wrapper for a ResultSet
736 struct TabResultSet(T)
737 {
738 	this(ResultSet result)
739 	{
740 		this.result = result;
741 	}
742 
743 	bool empty() @property
744 	{
745 		return this.result.empty;
746 	}
747 
748 	T front() @property
749 	{
750 		T row;
751 		row.populateFromDbRow(this.result.front);
752 		return row;
753 	}
754 
755 	void popFront()
756 	{
757 		this.result.popFront();
758 	}
759 
760 	size_t length() @property
761 	{
762 		return this.result.length;
763 	}
764 
765 	private ResultSet result;
766 }
767 
768 // ditto
769 TabResultSet!T to_table_rows(T)(ResultSet res)
770 {
771 	return TabResultSet!T(res);
772 }
773 
774 private template FieldReference(alias field_)
775 {
776 	alias Table = __traits(parent, field_);
777 	alias field = field_;
778 }
779 
780 private template isFieldRefInAttributes(Attributes...)
781 {
782 	static if (Attributes.length == 0) {
783 		static immutable bool isFieldRefInAttributes = false;
784 	}
785 	else {
786 		alias attr = Attributes[0];
787 		static if (is(attr == ForeignKey!(field, s), alias field, string s)) {
788 			static immutable bool isFieldRefInAttributes = true;
789 		}
790 		else {
791 			static immutable bool fieldRefInAttributes =
792 				isFieldRefInAttributes!(Attributes[1..$]);
793 		}
794 	}
795 }
796 
797 private template getFieldRefInAttributes(Attributes...)
798 {
799 	alias attr = Attributes[0];
800 	static if (is(attr == ForeignKey!(field, s), alias field, string s)) {
801 		alias getFieldRefInAttributes = FieldReference!(field);
802 	}
803 	else {
804 		alias fieldRefInAttributes =
805 			getFieldRefInAttributes!(RT, Attributes[1..$]);
806 	}
807 }
808 
809 private alias getRefToField(alias fk_field) =
810 	getFieldRefInAttributes!(__traits(getAttributes, fk_field));
811 
812 unittest
813 {
814 	struct Role { int id; }
815 
816 	struct User
817 	{
818 		int id;
819 		@ForeignKey!(Role.id, "") int role_id;
820 	}
821 
822 	alias FieldRef = getRefToField!(User.role_id);
823 	assert(is(FieldRef.Table == Role));
824 	assert(__traits(isSame, FieldRef.field, Role.id));
825 }
826 
827 string toFieldName(T)(string s, bool isPlural = false)
828 {
829 	int cnt = isPlural ? 2 : 1;
830 	if (s is null)
831 		return plural(cnt, beautify(tableNameFor!T(), '_', true));
832 	return s;
833 }
834 
835 /++
836 	generates get functions for a one-to-many relationship with the form
837 	`T2 get_<t2>(T1 row, Database db)` and
838 	`TabResultSet!T1 get_<t1>(T2 row, Database db)`
839 
840 
841 	[children] also works with a one-to-many relationship, but they are different in that [children] only gives you the many in the one-to-many relationship and only works with a single foreign key at a time.
842 
843 	Say you have a User and Role tables where each User has a role and a Role can be used by multiple users, with:
844 
845 	---
846 	/*
847 	This would give you all of the users with the Role `role`.
848 	*/
849 	auto res = role.children!(User, Role).execute(db);
850 	---
851 
852 	However if you wanted to get the Role of a user there would be no way of doing so with children. It doesn't work the other way around.
853 
854 	Also the big thing that one_to_many can do and children can not do is handle multiple relationships(Multiple foreign keys pointing to the same Table for example:
855 
856 	---
857 	import std.stdio;
858 	import arsd.sqlite;
859 	import arsd.database_generation;
860 
861 	alias FK(alias toWhat) = ForeignKey!(toWhat, null);
862 
863 	@DBName("Professor") struct Professor
864 	{
865 	    int id;
866 	    string name;
867 	}
868 
869 	@DBName("Course") struct Course
870 	{
871 	    int id;
872 	    @FK!(Professor.id) int professor_id;
873 	    @FK!(Professor.id) int assistant_id;
874 	}
875 
876 	mixin(one_to_many!(Course.professor_id, "prof", "courses_taught"));
877 	mixin(one_to_many!(Course.assistant_id, "assistant", "courses_assisted"));
878 
879 	void main()
880 	{
881 	    Database db = new Sqlite("test2.db");
882 
883 	    Course course = db.find!Course(1);
884 	    Professor prof = course.get_prof(db);
885 
886 	    writeln(prof.get_courses_taught(db));
887 	    writeln(prof.get_courses_assisted(db));
888 	}
889 	---
890 
891 	Here there are 2 relationships from Course to Professor here. One of them you can get from get_courses_taught and the other one with get_courses_assisted.
892 	If you attempt to use children like so
893 
894 	---
895 	writeln(prof.children!(Course, Professor).execute(db));
896 	---
897 
898 	You would get:
899 	$(CONSOLE
900 		source/arsd/database_generation.d(489,2): Error: static assert: "Course does not have exactly one foreign key of type Professor"
901 	)
902 
903 	In conclusion, children is nice in that its simple, doesn't require mixins to create extra symbols(functions). However it doesn't handle the one in one-to-many relationships at all, and it also doesn't work in tables with more than one relationship to a table. And finally, you might prefer the syntax of `prof.get_courses(db)` over `prof.children!(Course, Professor).execute(db)`.
904 
905 	Examples: 
906 
907 	---
908 	Struct Role { int id; }
909 	struct User {
910 		@ForeignKey!(Role.id, "") int role_id;
911 	}
912 
913 	mixin(one_to_many!(User.role_id, "role", "users"));
914 	void main()
915 	{
916 		Database db = ...
917 		User user = db.find!User(1);
918 		Role role = user.get_role(db);
919 		auto users = role.get_users(db);
920 	}
921 	---
922 
923 	if t2 or t1 are set as null they will be inferred from either
924 	the `DBName` attribute or from the name of the Table
925 
926 	History:
927 		Added November 5, 2022 (dub v10.10)
928 +/
929 template one_to_many(alias fk_field, string t2 = null, string t1 = null)
930 {
931 	private {
932 		alias T1 = __traits(parent, fk_field);
933 
934 		static assert(
935 			isFieldRefInAttributes!(__traits(getAttributes, fk_field)),
936 			T1.stringof ~ "." ~ fk_field.stringof ~ " does't have a ForeignKey");
937 
938 		alias FieldRef = getRefToField!(fk_field);
939 		alias T2 = FieldRef.Table;
940 		alias ref_field = FieldRef.field;
941 
942 		immutable string t2_name = toFieldName!T2(t2);
943 		immutable string t1_name = toFieldName!T1(t1, true);
944 	}
945 
946 	static immutable string one_to_many =
947 		T2.stringof~` get_`~t2_name~`(`~T1.stringof~` row, Database db)
948 		{
949 			import std.exception;
950 
951 			enforce(db !is null, "Database must not be null");
952 			auto fk_id = row.`~fk_field.stringof~`;
953 
954 			auto res = db.query(
955 				"select * from `~tableNameFor!T2()~`" ~
956 				" where `~ref_field.stringof~` = ?", fk_id
957 			).to_table_rows!`~T2.stringof~`;
958 
959 			return res.front();
960 		}
961 		TabResultSet!`~T1.stringof~` get_`~t1_name~`(`~T2.stringof~` row, Database db)
962 		{
963 			import std.exception;
964 
965 			enforce(db !is null, "Database must not be null");
966 			auto id = row.`~ref_field.stringof~`;
967 
968 			auto res = db.query(
969 				"select * from `~tableNameFor!T1()~`"~
970 				" where `~fk_field.stringof~` = ?", id
971 			).to_table_rows!`~T1.stringof~`;
972 
973 			return res;
974 		}`;
975 }