1 module dorm.api.db;
2 
3 import dorm.declarative;
4 import dorm.declarative.conversion;
5 import dorm.lib.util;
6 import dorm.types;
7 import dorm.model : Model;
8 import ffi = dorm.lib.ffi;
9 
10 import std.algorithm : any, move;
11 import std.range : chain;
12 import std.conv : text, to;
13 import std.datetime : Clock, Date, DateTime, DateTimeException, SysTime, TimeOfDay, UTC;
14 import std.meta;
15 import std.range.primitives;
16 import std.traits;
17 import std.typecons : Nullable;
18 
19 import core.attribute;
20 import core.time;
21 
22 public import dorm.types : DormPatch;
23 public import dorm.lib.ffi : DBBackend;
24 
25 public import dorm.api.condition;
26 
27 static if (!is(typeof(mustuse)))
28 	private enum mustuse; // @suppress(dscanner.style.phobos_naming_convention)
29 
30 /// Currently only a limited number of joins is supported per query, this could
31 /// configure it when it becomes a problem. This is due to a maximum number of
32 /// join aliases being available right now.
33 private enum maxJoins = 256;
34 
35 /**
36  * Configuration operation to connect to a database.
37  */
38 struct DBConnectOptions
39 {
40 @safe:
41 	/// Specifies the driver that will be used.
42 	DBBackend backend;
43 	/// Name of the database, in case of `DatabaseBackend.SQLite` name of the file.
44 	string name;
45 	/// Host to connect to. Not used in case of `DatabaseBackend.SQLite`.
46 	string host;
47 	/// Port to connect to. Not used in case of `DatabaseBackend.SQLite`.
48 	ushort port;
49 	/// Username to authenticate with. Not used in case of `DatabaseBackend.SQLite`.
50 	string user;
51 	/// Password to authenticate with. Not used in case of `DatabaseBackend.SQLite`.
52 	string password;
53 	/// Minimal connections to initialize upfront. Must not be 0.
54 	uint minConnections = ffi.DBConnectOptions.init.minConnections;
55 	/// Maximum connections that allowed to be created. Must not be 0.
56 	uint maxConnections = ffi.DBConnectOptions.init.maxConnections;
57 }
58 
59 /**
60  * High-level wrapper around a database. Through the driver implementation layer
61  * this handles connection pooling and distributes work across a thread pool
62  * automatically.
63  *
64  * Use the (UFCS) methods
65  *
66  * - $(LREF select)
67  * - $(LREF update)
68  * - $(LREF insert)
69  *
70  * to access the database.
71  *
72  * This struct cannot be copied, to pass it around, use `ref` or `move`. Once
73  * the struct goes out of scope or gets unset, the connection to the database
74  * will be freed.
75  */
76 struct DormDB
77 {
78 @safe:
79 	private ffi.DBHandle handle;
80 
81 	@disable this();
82 
83 	/**
84 	 * Performs a Database connection (possibly in another thread) and returns
85 	 * the constructed DormDB handle once connected.
86 	 */
87 	this(DBConnectOptions options) @trusted
88 	{
89 		auto ffiOptions = options.ffiInto!(ffi.DBConnectOptions);
90 
91 		scope dbHandleAsync = FreeableAsyncResult!(ffi.DBHandle).make;
92 		ffi.rorm_db_connect(ffiOptions, dbHandleAsync.callback.expand);
93 		handle = dbHandleAsync.result;
94 	}
95 
96 	~this() @trusted
97 	{
98 		if (handle)
99 		{
100 			ffi.rorm_db_free(handle);
101 			handle = null;
102 		}
103 	}
104 
105 	@disable this(this);
106 
107 	/// Starts a database transaction, on which most operations can be called.
108 	///
109 	/// Gets automatically rolled back if commit isn't called and the
110 	/// transaction goes out of scope, but it's recommended to explicitly
111 	/// call `rollback` to clarify the intent.
112 	DormTransaction startTransaction() return
113 	{
114 		ffi.DBTransactionHandle txHandle;
115 		(() @trusted {
116 			auto ctx = FreeableAsyncResult!(ffi.DBTransactionHandle).make;
117 			ffi.rorm_db_start_transaction(this.handle, ctx.callback.expand);
118 			txHandle = ctx.result();
119 		})();
120 		return DormTransaction(&this, txHandle);
121 	}
122 
123 	/// Database operation to INSERT a single value or multiple values when a
124 	/// slice is passed into `insert`.
125 	///
126 	/// It's possible to insert full Model instances, in which case every field
127 	/// of the model is used for the insertion. (also the primary key)
128 	///
129 	/// It's also possible to insert DormPatch instances to only pass the
130 	/// available fields into the SQL insert statement. This means default
131 	/// values will be auto-generated if possible.
132 	/// (see $(REF hasGeneratedDefaultValue, dorm,declarative,ModelFormat,Field))
133 	///
134 	/// This is the place where `@constructValue` constructors are called.
135 	///
136 	/// This method can also be used on transactions.
137 	void insert(T)(T value)
138 	if (!is(T == U[], U))
139 	{
140 		return (() @trusted => insertImpl!true(handle, (&value)[0 .. 1], null))();
141 	}
142 
143 	/// ditto
144 	void insert(T)(scope T[] value)
145 	{
146 		return insertImpl!false(handle, value, null);
147 	}
148 
149 	/**
150 	 * Returns a builder struct that can be used to perform an UPDATE statement
151 	 * in the SQL database on the provided Model table.
152 	 *
153 	 * See_Also: `DormTransaction.update`
154 	 */
155 	UpdateOperation!T update(T : Model)() return pure
156 	{
157 		return UpdateOperation!T(&this, null);
158 	}
159 
160 	/**
161 	 * Returns a builder struct that can be used to perform a DELETE statement
162 	 * in the SQL database on the provided Model table.
163 	 *
164 	 * See_Also: `DormTransaction.remove`
165 	 */
166 	RemoveOperation!T remove(T : Model)() return pure
167 	{
168 		return RemoveOperation!T(&this, null);
169 	}
170 
171 	/**
172 	 * Deletes the given model instance from the database.
173 	 *
174 	 * Equivalent to calling `db.remove!T.single(instance)`.
175 	 *
176 	 * See_Also: `RemoveOperation.single`
177 	 *
178 	 * Returns: true if anything was deleted, false otherwise.
179 	 */
180 	bool remove(T : Model)(T instance) return
181 	{
182 		return remove!T.single(instance);
183 	}
184 
185 	/// ditto
186 	bool remove(TPatch)(TPatch instance) return
187 	if (!is(TPatch : Model) && isSomePatch!TPatch)
188 	{
189 		alias T = DBType!TPatch;
190 		return remove!T.single(instance);
191 	}
192 
193 	/**
194 	 * This function executes a raw SQL statement.
195 	 *
196 	 * Iterate over the result using `foreach`.
197 	 *
198 	 * Statements are executed as prepared statements, if possible.
199 	 *
200 	 * To define placeholders, use `?` in SQLite and MySQL and $1, $n in Postgres.
201 	 * The corresponding parameters are bound in order to the query.
202 	 *
203 	 * The number of placeholder must match with the number of provided bind
204 	 * parameters.
205 	 *
206 	 * Params:
207 	 *     queryString = SQL statement to execute.
208 	 *     bindParams = Parameters to fill into placeholders of `queryString`.
209 	 *
210 	 * See_Also: `DormTransaction.rawSQL`
211 	 */
212 	RawSQLIterator rawSQL(
213 		scope return const(char)[] queryString,
214 		scope return ffi.FFIValue[] bindParams = null
215 	) return pure
216 	{
217 		return RawSQLIterator(&this, null, queryString, bindParams);
218 	}
219 }
220 
221 // defined this as global so that we can pass `Foo.fieldName` as alias argument,
222 // to have it be selected.
223 /**
224  * Starts a builder struct that can be used to SELECT (query) data from the
225  * database.
226  *
227  * It's possible to query full Model instances (get all fields), which are
228  * allocated by the GC. It's also possible to only query parts of a Model, for
229  * which DormPatch types are used, which is useful for improved query
230  * performance when only using parts of a Model as well as reusing the data in
231  * later update calls. (if the primary key is included in the patch)
232  *
233  * See `SelectOperation` for possible conditions and how to extract data.
234  *
235  * This method can also be used on transactions.
236  */
237 static SelectOperation!(DBType!(Selection), SelectType!(Selection)) select(
238 	Selection...
239 )(
240 	return ref const DormDB db
241 ) @trusted
242 {
243 	return typeof(return)(&db, null);
244 }
245 
246 /// ditto
247 static SelectOperation!(DBType!(Selection), SelectType!(Selection)) select(
248 	Selection...
249 )(
250 	return ref const DormTransaction tx
251 ) @trusted
252 {
253 	return typeof(return)(tx.db, tx.txHandle);
254 }
255 
256 /// Helper struct that makes it possible to `foreach` over the `rawSQL` result.
257 @mustuse struct RawSQLIterator
258 {
259 	private DormDB* db;
260 	private ffi.DBTransactionHandle tx;
261 	private const(char)[] queryString;
262 	private ffi.FFIValue[] bindParams;
263 	private size_t rowCountImpl = -1;
264 
265 	/// Returns the number of rows, only valid inside the foreach.
266 	size_t rowCount()
267 	{
268 		assert(rowCountImpl != -1, "Calling rowCount is only valid inside the foreach / opApply");
269 		return rowCountImpl;
270 	}
271 
272 	// TODO: delegate with @safe / @system differences + index overloads + don't mark whole thing as @trusted
273 	/// Starts a new query and iterates all the results on each foreach call.
274 	int opApply(scope int delegate(scope RawRow row) dg) @trusted
275 	{
276 		scope (exit)
277 			rowCountImpl = -1;
278 		assert(rowCountImpl == -1, "Don't iterate over the same RawSQLIterator on multiple threads!");
279 
280 		int result = 0;
281 		auto ctx = FreeableAsyncResult!(void delegate(scope ffi.FFIArray!(ffi.DBRowHandle))).make;
282 		ctx.forward_callback = (scope rows) {
283 			rowCountImpl = rows.size;
284 			foreach (row; rows[])
285 			{
286 				result = dg(RawRow(row));
287 				if (result)
288 					break;
289 			}
290 		};
291 		ffi.rorm_db_raw_sql(db.handle,
292 			tx,
293 			ffi.ffi(queryString),
294 			ffi.ffi(bindParams),
295 			ctx.callback.expand);
296 		ctx.result();
297 		return result;
298 	}
299 
300 	/// Runs the raw SQL query, discarding results (throwing on error)
301 	void exec()
302 	{
303 		assert(rowCountImpl == -1, "Don't iterate over the same RawSQLIterator on multiple threads!");
304 
305 		auto ctx = FreeableAsyncResult!(void delegate(scope ffi.FFIArray!(ffi.DBRowHandle))).make;
306 		ctx.forward_callback = (scope rows) {};
307 		ffi.rorm_db_raw_sql(db.handle,
308 			tx,
309 			ffi.ffi(queryString),
310 			ffi.ffi(bindParams),
311 			ctx.callback.expand);
312 		ctx.result();
313 	}
314 }
315 
316 /// Allows column access on a raw DB row as returned by `db.rawSQL`.
317 struct RawRow
318 {
319 	private ffi.DBRowHandle row;
320 
321 	@disable this(this);
322 
323 	private static template ffiConvPrimitive(T)
324 	{
325 		static if (is(T == short))
326 			alias ffiConvPrimitive = ffi.rorm_row_get_i16;
327 		else static if (is(T == int))
328 			alias ffiConvPrimitive = ffi.rorm_row_get_i32;
329 		else static if (is(T == long))
330 			alias ffiConvPrimitive = ffi.rorm_row_get_i64;
331 		else static if (is(T == float))
332 			alias ffiConvPrimitive = ffi.rorm_row_get_f32;
333 		else static if (is(T == double))
334 			alias ffiConvPrimitive = ffi.rorm_row_get_f64;
335 		else static if (is(T == bool))
336 			alias ffiConvPrimitive = ffi.rorm_row_get_bool;
337 		else
338 			static assert(false, "Unsupported column type: " ~ T.stringof);
339 	}
340 
341 	/// Gets the value of the column at the given column name assuming it is of
342 	/// the given type. If the value is not of the given type, an exception will
343 	/// be thrown.
344 	///
345 	/// Supported types:
346 	/// - any string type (auto-converted from strings / varchar)
347 	/// - `ubyte[]` for binary data
348 	/// - `short`, `int`, `long`, `float`, `double`, `bool`
349 	///
350 	/// For nullable values, use $(LREF opt) instead.
351 	T get(T)(scope const(char)[] column)
352 	{
353 		auto ffiColumn = ffi.ffi(column);
354 		ffi.RormError error;
355 		T result;
356 
357 		static if (isSomeString!T)
358 		{
359 			auto slice = ffi.rorm_row_get_str(row, ffiColumn, error);
360 			if (!error)
361 			{
362 				static if (is(T : char[]))
363 					result = cast(T)slice[].dup;
364 				else
365 					result = slice[].to!T;
366 			}
367 		}
368 		else static if (is(T : ubyte[]))
369 		{
370 			auto slice = ffi.rorm_row_get_binary(row, ffiColumn, error);
371 			if (!error)
372 				result = cast(T)slice[].dup;
373 		}
374 		else
375 		{
376 			alias fn = ffiConvPrimitive!T;
377 			result = fn(row, ffiColumn, error);
378 		}
379 
380 		if (error)
381 			throw error.makeException(" (in column '" ~ column.idup ~ "')");
382 		return result;
383 	}
384 
385 	private static template ffiConvOptionalPrimitive(T)
386 	{
387 		static if (is(T == short))
388 			alias ffiConvOptionalPrimitive = ffi.rorm_row_get_null_i16;
389 		else static if (is(T == int))
390 			alias ffiConvOptionalPrimitive = ffi.rorm_row_get_null_i32;
391 		else static if (is(T == long))
392 			alias ffiConvOptionalPrimitive = ffi.rorm_row_get_null_i64;
393 		else static if (is(T == float))
394 			alias ffiConvOptionalPrimitive = ffi.rorm_row_get_null_f32;
395 		else static if (is(T == double))
396 			alias ffiConvOptionalPrimitive = ffi.rorm_row_get_null_f64;
397 		else static if (is(T == bool))
398 			alias ffiConvOptionalPrimitive = ffi.rorm_row_get_null_bool;
399 		else
400 			static assert(false, "Unsupported column type: " ~ T.stringof);
401 	}
402 
403 	/// Same as get, wraps primitives inside Nullable!T. Strings and ubyte[]
404 	/// binary arrays will return `null` (checkable with `is null`), but
405 	/// otherwise simply be embedded.
406 	auto opt(T)(scope const(char)[] column)
407 	{
408 		auto ffiColumn = ffi.ffi(column);
409 		ffi.RormError error;
410 
411 		static if (isSomeString!T)
412 		{
413 			auto slice = ffi.rorm_row_get_null_str(row, ffiColumn, error);
414 			if (!error)
415 			{
416 				if (slice.isNull)
417 					return null;
418 				static if (is(T : char[]))
419 					return cast(T)slice.raw_value[].dup;
420 				else
421 					return slice.raw_value[].to!T;
422 			}
423 			else
424 				throw error.makeException(" (in column '" ~ column.idup ~ "')");
425 		}
426 		else static if (is(T : ubyte[]))
427 		{
428 			auto slice = ffi.rorm_row_get_null_binary(row, ffiColumn, error);
429 			if (slice.isNull)
430 				return null;
431 			if (!error)
432 				return cast(T)slice.raw_value[].dup;
433 			else
434 				throw error.makeException(" (in column '" ~ column.idup ~ "')");
435 		}
436 		else
437 		{
438 			Nullable!T result;
439 			alias fn = ffiConvOptionalPrimitive!T;
440 			auto opt = fn(row, ffiColumn, error);
441 			if (error)
442 				throw error.makeException(" (in column '" ~ column.idup ~ "')");
443 			if (!opt.isNull)
444 				result = opt.raw_value;
445 			return result;
446 		}
447 	}
448 }
449 
450 /**
451  * Wrapper around a Database transaction. Most methods that can be used on a
452  * DormDB can also be used on a transaction.
453  *
454  * Performs a rollback when going out of scope and wasn't committed or rolled
455  * back explicitly.
456  */
457 struct DormTransaction
458 {
459 @safe:
460 	private DormDB* db;
461 	private ffi.DBTransactionHandle txHandle;
462 
463 	@disable this(this);
464 
465 	~this()
466 	{
467 		if (txHandle)
468 		{
469 			rollback();
470 		}
471 	}
472 
473 	/// Commits this transaction, so the changes are recorded to the current
474 	/// database state.
475 	void commit()
476 	{
477 		scope (exit) txHandle = null;
478 		(() @trusted {
479 			auto ctx = FreeableAsyncResult!void.make;
480 			ffi.rorm_transaction_commit(txHandle, ctx.callback.expand);
481 			ctx.result();
482 		})();
483 	}
484 
485 	/// Rolls back this transaction, so the DB changes are reverted to before
486 	/// the transaction was started.
487 	void rollback()
488 	{
489 		scope (exit) txHandle = null;
490 		(() @trusted {
491 			auto ctx = FreeableAsyncResult!void.make;
492 			ffi.rorm_transaction_rollback(txHandle, ctx.callback.expand);
493 			ctx.result();
494 		})();
495 	}
496 
497 	/// Transacted variant of $(LREF DormDB.insert). Can insert a single value
498 	/// or multiple values at once.
499 	void insert(T)(T value)
500 	{
501 		return (() @trusted => insertImpl!true(db.handle, (&value)[0 .. 1], txHandle))();
502 	}
503 
504 	/// ditto
505 	void insert(T)(scope T[] value)
506 	{
507 		return insertImpl!false(db.handle, value, txHandle);
508 	}
509 
510 	/**
511 	 * This function executes a raw SQL statement.
512 	 *
513 	 * Iterate over the result using `foreach`.
514 	 *
515 	 * Statements are executed as prepared statements, if possible.
516 	 *
517 	 * To define placeholders, use `?` in SQLite and MySQL and $1, $n in Postgres.
518 	 * The corresponding parameters are bound in order to the query.
519 	 *
520 	 * The number of placeholder must match with the number of provided bind
521 	 * parameters.
522 	 *
523 	 * Params:
524 	 *     queryString = SQL statement to execute.
525 	 *     bindParams = Parameters to fill into placeholders of `queryString`.
526 	 *
527 	 * See_Also: `DormDB.rawSQL`
528 	 */
529 	RawSQLIterator rawSQL(
530 		scope return const(char)[] queryString,
531 		scope return ffi.FFIValue[] bindParams = null
532 	) return pure
533 	{
534 		return RawSQLIterator(db, txHandle, queryString, bindParams);
535 	}
536 
537 	/**
538 	 * Returns a builder struct that can be used to perform an update statement
539 	 * in the SQL database on the provided Model table.
540 	 *
541 	 * See_Also: `DormDB.update`
542 	 */
543 	UpdateOperation!T update(T : Model)() return pure
544 	{
545 		return UpdateOperation!T(db, txHandle);
546 	}
547 
548 	/**
549 	 * Returns a builder struct that can be used to perform a DELETE statement
550 	 * in the SQL database on the provided Model table.
551 	 *
552 	 * See_Also: `DormDB.remove`
553 	 */
554 	RemoveOperation!T remove(T : Model)() return pure
555 	{
556 		return RemoveOperation!T(db, txHandle);
557 	}
558 
559 	/**
560 	 * Deletes the given model instance from the database inside the transaction.
561 	 *
562 	 * Equivalent to calling `tx.remove!T.single(instance)`.
563 	 *
564 	 * See_Also: `RemoveOperation.single`
565 	 *
566 	 * Returns: true if anything was deleted, false otherwise.
567 	 */
568 	bool remove(T : Model)(T instance) return
569 	{
570 		return remove!T.single(instance);
571 	}
572 
573 	/// ditto
574 	bool remove(TPatch)(TPatch instance) return
575 	if (!is(TPatch : Model) && isSomePatch!TPatch)
576 	{
577 		alias T = DBType!TPatch;
578 		return remove!T.single(instance);
579 	}
580 }
581 
582 private string makePatchAccessPrefix(Patch, DB)()
583 {
584 	string ret;
585 	static if (!is(Patch == DB)
586 		&& is(__traits(parent, Patch) == DB))
587 	{
588 		static foreach (i, field; DB.tupleof)
589 		{
590 			static if (is(typeof(field) == Patch))
591 			{
592 				static foreach_reverse (j, field; DB.tupleof)
593 					static if (is(typeof(field) == Patch))
594 						static assert(i == j, "Multiple implicit "
595 							~ Patch.stringof ~ " patches on the same "
596 							~ DB.stringof ~ " Model class!");
597 
598 				ret = DB.tupleof[i].stringof ~ ".";
599 			}
600 		}
601 	}
602 	return ret;
603 }
604 
605 private void insertImpl(bool single, T)(
606 	scope ffi.DBHandle handle,
607 	scope T[] value,
608 	ffi.DBTransactionHandle transaction)
609 @safe
610 {
611 	import core.lifetime;
612 	alias DB = DBType!T;
613 
614 	enum patchAccessPrefix = makePatchAccessPrefix!(T, DB);
615 
616 	static stripPrefix(string s)
617 	{
618 		return patchAccessPrefix.length && s.length > patchAccessPrefix.length
619 			&& s[0 .. patchAccessPrefix.length] == patchAccessPrefix
620 			? s[patchAccessPrefix.length .. $] : s;
621 	}
622 
623 	enum NumColumns = {
624 		int used;
625 		static foreach (field; DormFields!DB)
626 			static if (is(typeof(mixin("value[0]." ~ stripPrefix(field.sourceColumn)))) || field.hasConstructValue)
627 				used++;
628 		return used;
629 	}();
630 
631 	ffi.FFIString[NumColumns] columns;
632 	static if (single)
633 	{
634 		ffi.FFIValue[NumColumns][1] values;
635 	}
636 	else
637 	{
638 		ffi.FFIValue[NumColumns][] values;
639 		values.length = value.length;
640 
641 		if (!values.length)
642 			return;
643 	}
644 
645 	int used;
646 
647 	static if (!is(T == DB))
648 	{
649 		auto validatorObject = new DB();
650 		static if (!single)
651 		{
652 			DB validatorCopy;
653 			if (values.length > 1)
654 				(() @trusted => copyEmplace(validatorObject, validatorCopy))();
655 		}
656 	}
657 
658 	static foreach (field; DormFields!DB)
659 	{{
660 		static if (is(typeof(mixin("value[0]." ~ stripPrefix(field.sourceColumn)))))
661 		{
662 			columns[used] = ffi.ffi(field.columnName);
663 			foreach (i; 0 .. values.length)
664 				values[i][used] = conditionValue!field(mixin("value[i]." ~ stripPrefix(field.sourceColumn)));
665 			used++;
666 		}
667 		else static if (field.hasConstructValue)
668 		{
669 			// filled in by constructor
670 			columns[used] = ffi.ffi(field.columnName);
671 			foreach (i; 0 .. values.length)
672 			{
673 				static if (is(T == DB))
674 					values[i][used] = conditionValue!field(mixin("value[i]." ~ field.sourceColumn));
675 				else
676 					values[i][used] = conditionValue!field(mixin("validatorObject." ~ stripPrefix(field.sourceColumn)));
677 			}
678 			used++;
679 		}
680 		else static if (field.hasGeneratedDefaultValue)
681 		{
682 			// OK
683 		}
684 		else static if (!is(T == DB))
685 			static assert(false, "Trying to insert a patch " ~ T.stringof
686 				~ " into " ~ DB.stringof ~ ", but it is missing the required field "
687 				~ stripPrefix(field.sourceReferenceName) ~ "! "
688 				~ "Fields with auto-generated values may be omitted in patch types. "
689 				~ ModelFormat.Field.humanReadableGeneratedDefaultValueTypes);
690 		else
691 			static assert(false, "wat? (defined DormField not found inside the Model class that defined it)");
692 	}}
693 
694 	assert(used == NumColumns);
695 
696 	static if (is(T == DB))
697 	{
698 		foreach (i; 0 .. values.length)
699 		{
700 			auto brokenFields = value[i].runValidators();
701 
702 			string error;
703 			foreach (field; brokenFields)
704 			{
705 				static if (single)
706 					error ~= "Field `" ~ field.sourceColumn ~ "` defined in "
707 						~ field.definedAt.toString ~ " failed user validation.";
708 				else
709 					error ~= "row[" ~ i.to!string
710 						~ "] field `" ~ field.sourceColumn ~ "` defined in "
711 						~ field.definedAt.toString ~ " failed user validation.";
712 			}
713 			if (error.length)
714 				throw new Exception(error);
715 		}
716 	}
717 	else
718 	{
719 		foreach (i; 0 .. values.length)
720 		{
721 			static if (!single)
722 				if (i != 0)
723 					(() @trusted => copyEmplace(validatorCopy, validatorObject))();
724 
725 			validatorObject.applyPatch(value[i]);
726 			auto brokenFields = validatorObject.runValidators();
727 
728 			string error;
729 			foreach (field; brokenFields)
730 			{
731 				switch (field.columnName)
732 				{
733 					static foreach (sourceField; DormFields!DB)
734 					{
735 						static if (is(typeof(mixin("value[i]." ~ stripPrefix(sourceField.sourceColumn)))))
736 						{
737 							case sourceField.columnName:
738 						}
739 					}
740 					static if (single)
741 						error ~= "Field `" ~ field.sourceColumn ~ "` defined in "
742 							~ field.definedAt.toString ~ " failed user validation.";
743 					else
744 						error ~= "row[" ~ i.to!string
745 							~ "] field `" ~ field.sourceColumn ~ "` defined in "
746 							~ field.definedAt.toString ~ " failed user validation.";
747 					break;
748 				default:
749 					break;
750 				}
751 			}
752 
753 			if (error.length)
754 				throw new Exception(error);
755 		}
756 	}
757 
758 
759 	(() @trusted {
760 		auto ctx = FreeableAsyncResult!void.make;
761 		static if (single)
762 		{
763 			ffi.rorm_db_insert(handle,
764 				transaction,
765 				ffi.ffi(DormLayout!DB.tableName),
766 				ffi.ffi(columns),
767 				ffi.ffi(values[0]), ctx.callback.expand);
768 		}
769 		else
770 		{
771 			auto rows = new ffi.FFIArray!(ffi.FFIValue)[values.length];
772 			foreach (i; 0 .. values.length)
773 				rows[i] = ffi.ffi(values[i]);
774 
775 			ffi.rorm_db_insert_bulk(handle,
776 				transaction,
777 				ffi.ffi(DormLayout!DB.tableName),
778 				ffi.ffi(columns),
779 				ffi.ffi(rows), ctx.callback.expand);
780 		}
781 		ctx.result();
782 	})();
783 }
784 
785 private struct ConditionBuilderData
786 {
787 	@disable this(this);
788 
789 	JoinInformation joinInformation;
790 }
791 
792 /// This is the type of the variable that is passed into the condition callback
793 /// at runtime on the `SelectOperation` struct. It automatically mirrors all
794 /// DORM fields that are defined on the passed-in `T` Model class.
795 ///
796 /// Fields can be accessed with the same name they were defined in the Model
797 /// class. Embedded structs will only use the deepest variable name, e.g. a
798 /// nested field of name `userCommon.username` will only need to be accessed
799 /// using `username`. Duplicate / shadowing members is not implemented and will
800 /// be unable to use the condition builder on them.
801 ///
802 /// If any boolean types are defined in the model, they can be quickly checked
803 /// to be false using the `not.booleanFieldName` helper.
804 /// See `NotConditionBuilder` for this.
805 ///
806 /// When mistyping names, an expressive error message is printed as compile
807 /// time output, showing all possible members for convenience.
808 struct ConditionBuilder(T)
809 {
810 	private ConditionBuilderData* builderData;
811 
812 	static foreach (field; DormFields!T)
813 	{
814 		static if (field.isForeignKey)
815 		{
816 			mixin("ForeignModelConditionBuilderField!(typeof(T.", field.sourceColumn, "), field) ",
817 				field.sourceColumn.lastIdentifier,
818 				"() @property return { return typeof(return)(DormLayout!T.tableName, builderData); }");
819 		}
820 		else
821 		{
822 			mixin("ConditionBuilderField!(typeof(T.", field.sourceColumn, "), field) ",
823 				field.sourceColumn.lastIdentifier,
824 				" = ConditionBuilderField!(typeof(T.", field.sourceColumn, "), field)(`",
825 				DormLayout!T.tableName, "`, `", field.columnName,
826 				"`);");
827 		}
828 	}
829 
830 	static if (__traits(allMembers, NotConditionBuilder!T).length > 1)
831 	{
832 		/// Helper to quickly create `field == false` conditions for boolean fields.
833 		NotConditionBuilder!T not;
834 	}
835 	else
836 	{
837 		/// Helper to quickly create `field == false` conditions for boolean fields.
838 		void not()() { static assert(false, "Model " ~ T.stringof
839 			~ " has no fields that can be used with .not"); }
840 	}
841 
842 	mixin DynamicMissingMemberErrorHelper!"condition field";
843 }
844 
845 /// This is the type of the variable that is passed into the `orderBy` callback
846 /// at runtime on the `SelectOperation` struct. It automatically mirrors all
847 /// DORM fields that are defined on the passed-in `T` Model class.
848 ///
849 /// Fields can be accessed with the same name they were defined in the Model
850 /// class. Embedded structs will only use the deepest variable name, e.g. a
851 /// nested field of name `userCommon.username` will only need to be accessed
852 /// using `username`. Duplicate / shadowing members is not implemented and will
853 /// be unable to use the builder on them.
854 ///
855 /// On the columns you can either use `.asc` to sort ascending or `.desc` to
856 /// sort descending by the column.
857 ///
858 /// When mistyping names, an expressive error message is printed as compile
859 /// time output, showing all possible members for convenience.
860 struct OrderBuilder(T)
861 {
862 	private ConditionBuilderData* builderData;
863 
864 	static foreach (field; DormFields!T)
865 	{
866 		static if (field.isForeignKey)
867 		{
868 			mixin("ForeignModelOrderBuilderField!(typeof(T.", field.sourceColumn, "), field) ",
869 				field.sourceColumn.lastIdentifier,
870 				"() @property return { return typeof(return)(DormLayout!T.tableName, builderData); }");
871 		}
872 		else
873 		{
874 			mixin("OrderBuilderField!(typeof(T.", field.sourceColumn, "), field) ",
875 				field.sourceColumn.lastIdentifier,
876 				" = OrderBuilderField!(typeof(T.", field.sourceColumn, "), field)(`",
877 				DormLayout!T.tableName, "`, `", field.columnName,
878 				"`);");
879 		}
880 	}
881 
882 	/// Only useful at runtime: when it's decided that no ordering needs to be
883 	/// done after all, simply return this method to do nothing.
884 	ffi.FFIOrderByEntry none() const @safe @property
885 	{
886 		return ffi.FFIOrderByEntry.init;
887 	}
888 
889 	mixin DynamicMissingMemberErrorHelper!"order field";
890 }
891 
892 /// This is the type of the variable that is passed into the `populate` callback
893 /// at runtime on the `SelectOperation` struct. It automatically mirrors all
894 /// DORM fields that are defined on the passed-in `T` Model class.
895 ///
896 /// Fields can be accessed with the same name they were defined in the Model
897 /// class. Embedded structs will only use the deepest variable name, e.g. a
898 /// nested field of name `userCommon.username` will only need to be accessed
899 /// using `username`. Duplicate / shadowing members is not implemented and will
900 /// be unable to use the builder on them.
901 ///
902 /// On the column you currently just need to write `.yes` after the column to
903 /// actually include it. This is a limitation because otherwise it wouldn't be
904 /// possible to populate both reference columns directly or references of
905 /// references. e.g. populating both `model.author` and `model.author.friends`
906 /// can be done by doing `model.author.yes` and `model.author.friends.yes`
907 ///
908 /// When mistyping names, an expressive error message is printed as compile
909 /// time output, showing all possible members for convenience.
910 struct PopulateBuilder(T)
911 {
912 	private ConditionBuilderData* builderData;
913 
914 	static foreach (field; DormFields!T)
915 	{
916 		static if (field.isForeignKey)
917 		{
918 			mixin("PopulateBuilderField!(typeof(T.", field.sourceColumn, "), field) ",
919 				field.sourceColumn.lastIdentifier,
920 				"() @property return { return typeof(return)(DormLayout!T.tableName, builderData); }");
921 		}
922 	}
923 
924 	mixin DynamicMissingMemberErrorHelper!"populate builder";
925 }
926 
927 /// This MUST be mixed in at the end to show proper members
928 private mixin template DynamicMissingMemberErrorHelper(string fieldName, string simplifyName = "")
929 {
930 	auto opDispatch(string member, string file = __FILE__, size_t line = __LINE__)()
931 	{
932 		import std.string : join;
933 
934 		enum available = [__traits(allMembers, typeof(this))][0 .. $ - 1].filterBuiltins;
935 
936 		enum suggestion = findSuggestion(available, member);
937 		enum suggestionMsg = suggestion.length ? "\n\n\t\tDid you mean " ~ suggestion ~ "?" : "";
938 
939 		pragma(msg, errorBoldPrefix ~ file ~ "(" ~ line.to!string ~ "): " ~ supplErrorWithFilePrefix
940 			~ fieldName ~ " `" ~ member ~ "` does not exist on "
941 			~ (simplifyName.length ? simplifyName : typeof(this).stringof) ~ ". Available members are: "
942 			~ available.join(", ") ~ suggestionMsg);
943 		static assert(false, "See DORM error above.");
944 	}
945 }
946 
947 private mixin template DisallowOperators(string typeName)
948 {
949 	auto opBinary(string op, R, string file = __FILE__, size_t line = __LINE__)(const R rhs)
950 	const @safe pure nothrow @nogc
951 	{
952 		pragma(msg, errorBoldPrefix ~ file ~ "(" ~ line.to!string ~ "): " ~ supplErrorWithFilePrefix
953 			~ "You are not supposed to use operators like '" ~ .op ~ "' on "
954 			~ typeName ~ "! Use the operation fields on this instead.");
955 		static assert(false, "See DORM error above.");
956 	}
957 
958 	auto opBinaryRight(string op, L, string file = __FILE__, size_t line = __LINE__)(const L lhs)
959 	const @safe pure nothrow @nogc
960 	{
961 		pragma(msg, errorBoldPrefix ~ file ~ "(" ~ line.to!string ~ "): " ~ supplErrorWithFilePrefix
962 			~ "You are not supposed to use operators like '" ~ .op ~ "' on "
963 			~ typeName ~ "! Use the operation fields on this instead.");
964 		static assert(false, "See DORM error above.");
965 	}
966 
967 	bool opEquals(R, string file = __FILE__, size_t line = __LINE__)(const R other)
968 	const @safe pure nothrow @nogc
969 	if (!is(immutable R == immutable typeof(this)))
970 	{
971 		pragma(msg, errorBoldPrefix ~ file ~ "(" ~ line.to!string ~ "): " ~ supplErrorWithFilePrefix
972 			~ "You are not supposed to use operators like '==' on "
973 			~ typeName ~ "! Use the operation fields on this instead.");
974 		static assert(false, "See DORM error above.");
975 	}
976 }
977 
978 private string[] filterBuiltins(string[] members)
979 {
980 	import std.algorithm : among, remove;
981 
982 	foreach_reverse (i, member; members)
983 		if (member.among("__ctor", "__dtor"))
984 			members = members.remove(i);
985 	return members;
986 }
987 
988 private string findSuggestion(string[] available, string member)
989 {
990 	// TODO: levenshteinDistance doesn't work at CTFE
991 	// import std.algorithm : levenshteinDistance;
992 
993 	// size_t minDistance = size_t.max;
994 	// string suggestion;
995 
996 	// foreach (a; available)
997 	// {
998 	// 	auto dist = levenshteinDistance(a, member);
999 	// 	if (dist < minDistance)
1000 	// 	{
1001 	// 		suggestion = a;
1002 	// 		minDistance = dist;
1003 	// 	}
1004 	// }
1005 	// return minDistance < 3 ? suggestion : null;
1006 
1007 	import std.string : soundex;
1008 
1009 	char[4] q, test;
1010 	if (!soundex(member, q[]))
1011 		return null;
1012 	foreach (a; available)
1013 	{
1014 		auto t = soundex(a, test[]);
1015 		if (t == q)
1016 			return a;
1017 	}
1018 	return null;
1019 }
1020 
1021 private enum errorBoldPrefix = "\x1B[1m";
1022 private enum supplErrorWithFilePrefix = "\x1B[1;31mDORM Error:\x1B[m ";
1023 private enum supplErrorPrefix = "           " ~ supplErrorWithFilePrefix;
1024 
1025 /// Helper type to quickly create `field == false` conditions for boolean fields.
1026 ///
1027 /// See `ConditionBuilder`
1028 struct NotConditionBuilder(T)
1029 {
1030 	static foreach (field; DormFields!T)
1031 	{
1032 		static if (is(typeof(mixin("T.", field.sourceColumn)) : bool))
1033 		{
1034 			mixin("Condition ",
1035 				field.sourceColumn.lastIdentifier,
1036 				"() @property { return Condition(UnaryCondition(UnaryConditionType.Not,
1037 					makeColumnReference(`",
1038 				DormLayout!T.tableName, "`, `", field.columnName,
1039 				"`))); }");
1040 		}
1041 	}
1042 
1043 	mixin DynamicMissingMemberErrorHelper!"negated condition field";
1044 }
1045 
1046 private Condition* makeColumnReference(string tableName, string columnName) @safe
1047 {
1048 	// TODO: think of how we can abstract memory allocation here
1049 	return new Condition(columnValue(tableName, columnName));
1050 }
1051 
1052 private Condition* makeConditionConstant(ModelFormat.Field fieldInfo, T)(T value) @safe
1053 {
1054 	// TODO: think of how we can abstract memory allocation here
1055 	return new Condition(conditionValue!fieldInfo(value));
1056 }
1057 
1058 private mixin template ForeignJoinHelper()
1059 {
1060 	private string srcTableName;
1061 	private ConditionBuilderData* builderData;
1062 
1063 	/// Constructs this ForeignModelConditionBuilderField, operating on the given data pointer during its lifetime
1064 	this(string srcTableName, ConditionBuilderData* builderData) @safe
1065 	{
1066 		this.srcTableName = srcTableName;
1067 		this.builderData = builderData;
1068 	}
1069 
1070 	private string ensureJoined() @safe
1071 	{
1072 		return builderData.joinInformation.joinSuppl[ensureJoinedIdx].placeholder;
1073 	}
1074 
1075 	private size_t ensureJoinedIdx() @trusted
1076 	{
1077 		auto ji = &builderData.joinInformation;
1078 		string fkName = field.columnName;
1079 		auto exist = fkName in ji.joinedTables;
1080 		if (exist)
1081 		{
1082 			return *exist;
1083 		}
1084 		else
1085 		{
1086 			size_t index = ji.joins.length;
1087 			assert(ji.joinSuppl.length == index);
1088 			string placeholder = JoinInformation.joinAliasList[ji.joinedTables.length];
1089 			ffi.FFICondition* condition = new ffi.FFICondition();
1090 			condition.type = ffi.FFICondition.Type.BinaryCondition;
1091 			condition.binaryCondition.type = ffi.FFIBinaryCondition.Type.Equals;
1092 			auto lhs = new ffi.FFICondition();
1093 			auto rhs = new ffi.FFICondition();
1094 			lhs.type = ffi.FFICondition.Type.Value;
1095 			lhs.value = columnValue(placeholder, ModelRef.primaryKeyField.columnName);
1096 			rhs.type = ffi.FFICondition.Type.Value;
1097 			rhs.value = columnValue(srcTableName, field.columnName);
1098 			condition.binaryCondition.lhs = lhs;
1099 			condition.binaryCondition.rhs = rhs;
1100 
1101 			assert(ji.joins.length == index,
1102 				"this method must absolutely never be called in parallel on the same object");
1103 			ji.joinedTables[fkName] = index;
1104 			ji.joins ~= ffi.FFIJoin(
1105 				ffi.FFIJoinType.join,
1106 				ffi.ffi(DormLayout!RefDB.tableName),
1107 				ffi.ffi(placeholder),
1108 				condition
1109 			);
1110 			ji.joinSuppl ~= JoinInformation.JoinSuppl(
1111 				placeholder,
1112 				false
1113 			);
1114 			return index;
1115 		}
1116 	}
1117 }
1118 
1119 /// Helper type to access sub-fields through `ModelRef` foreign key fields. Will
1120 /// join the foreign model table automatically if using any fields on there,
1121 /// other than the primary key, which can be read directly from the source.
1122 ///
1123 /// Just like `ConditionBuilder` this automatically mirrors all DORM fields of
1124 /// the _foreign_ table, i.e. the referenced model type.
1125 ///
1126 /// This type is returned by the `ConditionBuilder`. It does not define any
1127 /// members itself, it only defines all members of the referenced Model to be
1128 /// accessible. When operating on the primary key that is referenced to from the
1129 /// ModelRef foreign key, no join operation will be enforced, as the data is
1130 /// stored entirely in the table with the foreign key.
1131 struct ForeignModelConditionBuilderField(ModelRef, ModelFormat.Field field)
1132 {
1133 	alias RefDB = ModelRef.TModel;
1134 
1135 	mixin ForeignJoinHelper;
1136 
1137 	static foreach (field; DormFields!RefDB)
1138 	{
1139 		static if (__traits(isSame, ModelRef.primaryKeyAlias, mixin("RefDB.", field.sourceColumn)))
1140 		{
1141 			mixin("ConditionBuilderField!(ModelRef.PrimaryKeyType, field) ",
1142 				field.sourceColumn.lastIdentifier,
1143 				"() @property @safe return { return ConditionBuilderField!(ModelRef.PrimaryKeyType, field)(srcTableName, `",
1144 				field.columnName, "`); }");
1145 		}
1146 		else static if (field.isForeignKey)
1147 		{
1148 			mixin("ForeignModelConditionBuilderField!(typeof(RefDB.", field.sourceColumn, "), field) ",
1149 				field.sourceColumn.lastIdentifier,
1150 				"() @property return { string placeholder = ensureJoined(); return typeof(return)(placeholder, builderData); }");
1151 		}
1152 		else
1153 		{
1154 			mixin("ConditionBuilderField!(typeof(RefDB.", field.sourceColumn, "), field) ",
1155 				field.sourceColumn.lastIdentifier,
1156 				"() @property @safe return { string placeholder = ensureJoined(); return typeof(return)(placeholder, `",
1157 				field.columnName,
1158 				"`); }");
1159 		}
1160 	}
1161 
1162 	mixin DynamicMissingMemberErrorHelper!(
1163 		"foreign condition field",
1164 		"`ForeignModelConditionBuilderField` on " ~ RefDB.stringof ~ "." ~ field.sourceColumn
1165 	);
1166 }
1167 
1168 /// Helper type to access sub-fields through `ModelRef` foreign key fields. Will
1169 /// join the foreign model table automatically if using any fields on there,
1170 /// other than the primary key, which can be read directly from the source.
1171 ///
1172 /// Just like `OrderBuilder` this automatically mirrors all DORM fields of
1173 /// the _foreign_ table, i.e. the referenced model type.
1174 ///
1175 /// This type is returned by the `OrderBuilder`. It does not define any members
1176 /// itself, it only defines all members of the referenced Model to be
1177 /// accessible. When operating on the primary key that is referenced to from the
1178 /// ModelRef foreign key, no join operation will be enforced, as the data is
1179 /// stored entirely in the table with the foreign key.
1180 struct ForeignModelOrderBuilderField(ModelRef, ModelFormat.Field field)
1181 {
1182 	alias RefDB = ModelRef.TModel;
1183 
1184 	mixin ForeignJoinHelper;
1185 
1186 	static foreach (field; DormFields!RefDB)
1187 	{
1188 		static if (__traits(isSame, ModelRef.primaryKeyAlias, mixin("RefDB.", field.sourceColumn)))
1189 		{
1190 			mixin("OrderBuilderField!(ModelRef.PrimaryKeyType, field) ",
1191 				field.sourceColumn.lastIdentifier,
1192 				"() @property @safe return { return OrderBuilderField!(ModelRef.PrimaryKeyType, field)(srcTableName, `",
1193 				field.columnName, "`); }");
1194 		}
1195 		else static if (field.isForeignKey)
1196 		{
1197 			mixin("ForeignModelOrderBuilderField!(typeof(RefDB.", field.sourceColumn, "), field) ",
1198 				field.sourceColumn.lastIdentifier,
1199 				"() @property return { string placeholder = ensureJoined(); return typeof(return)(placeholder, builderData); }");
1200 		}
1201 		else
1202 		{
1203 			mixin("OrderBuilderField!(typeof(RefDB.", field.sourceColumn, "), field) ",
1204 				field.sourceColumn.lastIdentifier,
1205 				"() @property @safe return { string placeholder = ensureJoined(); return typeof(return)(placeholder, `",
1206 				field.columnName,
1207 				"`); }");
1208 		}
1209 	}
1210 
1211 	mixin DynamicMissingMemberErrorHelper!(
1212 		"foreign condition field",
1213 		"`ForeignModelOrderBuilderField` on " ~ RefDB.stringof ~ "." ~ field.sourceColumn
1214 	);
1215 }
1216 
1217 /// Internal structure returned by the `PopulateBuilder`, which is passed to
1218 /// user code from the `populate` method on a `SelectOperation`. Internally this
1219 /// works by setting the `include` flag on the internal join info structure that
1220 /// either already exists because of previous condition or ordering operations
1221 /// or generates the join info structure on-demand.
1222 ///
1223 /// Do not create this struct manually, only use the `PopulateBuilderField` that
1224 /// is passed to you as parameter through the `populate` function on the
1225 /// `SelectOperation` struct, which is returned by `db.select` or `tx.select`.
1226 struct PopulateRef
1227 {
1228 	/// Internal index inside the JoinInfo array that is stored on the
1229 	/// `SelectBuilder`. Do not modify manually, you should only use the
1230 	/// `populate` function on `SelectOperation` to generate this.
1231 	size_t idx;
1232 }
1233 
1234 /// Helper struct
1235 struct PopulateBuilderField(ModelRef, ModelFormat.Field field)
1236 {
1237 	alias RefDB = ModelRef.TModel;
1238 
1239 	mixin ForeignJoinHelper;
1240 
1241 	/// Explicitly say this field is used
1242 	PopulateRef[] yes()
1243 	{
1244 		return [PopulateRef(ensureJoinedIdx)];
1245 	}
1246 
1247 	static foreach (field; DormFields!RefDB)
1248 	{
1249 		static if (field.isForeignKey)
1250 		{
1251 			mixin("PopulateBuilderField!(typeof(RefDB.", field.sourceColumn, "), field) ",
1252 				field.sourceColumn.lastIdentifier,
1253 				"() @property return { string placeholder = ensureJoined(); return typeof(return)(placeholder, builderData); }");
1254 		}
1255 	}
1256 
1257 	mixin DisallowOperators!(
1258 		"`PopulateBuilderField` on " ~ RefDB.stringof ~ "." ~ field.sourceColumn
1259 	);
1260 
1261 	mixin DynamicMissingMemberErrorHelper!(
1262 		"populate field",
1263 		"`PopulateBuilderField` on " ~ RefDB.stringof ~ "." ~ field.sourceColumn
1264 	);
1265 }
1266 
1267 /// Returns `"baz"` from `"foo.bar.baz"` (identifier after last .)
1268 /// Returns `s` as-is if it doesn't contain any dots.
1269 private string lastIdentifier(string s)
1270 {
1271 	foreach_reverse (i, c; s)
1272 		if (c == '.')
1273 			return s[i + 1 .. $];
1274 	return s;
1275 }
1276 
1277 /// Type that actually implements the condition building on a
1278 /// `ConditionBuilder`.
1279 ///
1280 /// Implements building simple unary, binary and ternary operators:
1281 /// - `equals`
1282 /// - `notEquals`
1283 /// - `isTrue` (only defined on boolean types)
1284 /// - `lessThan`
1285 /// - `lessThanOrEqual`
1286 /// - `greaterThan`
1287 /// - `greaterThanOrEqual`
1288 /// - `like`
1289 /// - `notLike`
1290 /// - `regexp`
1291 /// - `notRegexp`
1292 /// - `in_`
1293 /// - `notIn`
1294 /// - `isNull`
1295 /// - `isNotNull`
1296 /// - `exists`
1297 /// - `notExists`
1298 /// - `between`
1299 /// - `notBetween`
1300 struct ConditionBuilderField(T, ModelFormat.Field field)
1301 {
1302 	// TODO: all the type specific field to Condition thingies
1303 
1304 	private string tableName;
1305 	private string columnName;
1306 
1307 	/// Constructs this ConditionBuilderField with the given columnName for generated conditions.
1308 	this(string tableName, string columnName) @safe
1309 	{
1310 		this.tableName = tableName;
1311 		this.columnName = columnName;
1312 	}
1313 
1314 	private Condition* lhs() @safe
1315 	{
1316 		return makeColumnReference(tableName, columnName);
1317 	}
1318 
1319 	/// Returns: SQL condition `field == value`
1320 	Condition equals(V)(V value) @safe
1321 	{
1322 		return Condition(BinaryCondition(BinaryConditionType.Equals, lhs, makeConditionConstant!field(value)));
1323 	}
1324 
1325 	/// Returns: SQL condition `field != value`
1326 	Condition notEquals(V)(V value) @safe
1327 	{
1328 		return Condition(BinaryCondition(BinaryConditionType.NotEquals, lhs, makeConditionConstant!field(value)));
1329 	}
1330 
1331 	static if (field.type == ModelFormat.Field.DBType.boolean)
1332 	{
1333 		/// Returns: SQL condition `field == true`
1334 		Condition isTrue() @safe
1335 		{
1336 			return equals(true);
1337 		}
1338 	}
1339 
1340 	/// Returns: SQL condition `field < value`
1341 	Condition lessThan(V)(V value) @safe
1342 	{
1343 		return Condition(BinaryCondition(BinaryConditionType.Less, lhs, makeConditionConstant!field(value)));
1344 	}
1345 
1346 	/// Returns: SQL condition `field <= value`
1347 	Condition lessThanOrEqual(V)(V value) @safe
1348 	{
1349 		return Condition(BinaryCondition(BinaryConditionType.LessOrEquals, lhs, makeConditionConstant!field(value)));
1350 	}
1351 
1352 	/// Returns: SQL condition `field > value`
1353 	Condition greaterThan(V)(V value) @safe
1354 	{
1355 		return Condition(BinaryCondition(BinaryConditionType.Greater, lhs, makeConditionConstant!field(value)));
1356 	}
1357 
1358 	/// Returns: SQL condition `field >= value`
1359 	Condition greaterThanOrEqual(V)(V value) @safe
1360 	{
1361 		return Condition(BinaryCondition(BinaryConditionType.GreaterOrEquals, lhs, makeConditionConstant!field(value)));
1362 	}
1363 
1364 	/// Returns: SQL condition `field LIKE value`
1365 	Condition like(V)(V value) @safe
1366 	{
1367 		return Condition(BinaryCondition(BinaryConditionType.Like, lhs, makeConditionConstant!field(value)));
1368 	}
1369 
1370 	/// Returns: SQL condition `field NOT LIKE value`
1371 	Condition notLike(V)(V value) @safe
1372 	{
1373 		return Condition(BinaryCondition(BinaryConditionType.NotLike, lhs, makeConditionConstant!field(value)));
1374 	}
1375 
1376 	/// Returns: SQL condition `field REGEXP value`
1377 	Condition regexp(V)(V value) @safe
1378 	{
1379 		return Condition(BinaryCondition(BinaryConditionType.Regexp, lhs, makeConditionConstant!field(value)));
1380 	}
1381 
1382 	/// Returns: SQL condition `field NOT REGEXP value`
1383 	Condition notRegexp(V)(V value) @safe
1384 	{
1385 		return Condition(BinaryCondition(BinaryConditionType.NotRegexp, lhs, makeConditionConstant!field(value)));
1386 	}
1387 
1388 	/// Returns: SQL condition `field IN value`
1389 	Condition in_(V)(V value) @safe
1390 	{
1391 		return Condition(BinaryCondition(BinaryConditionType.In, lhs, makeConditionConstant!field(value)));
1392 	}
1393 
1394 	/// Returns: SQL condition `field NOT IN value`
1395 	Condition notIn(V)(V value) @safe
1396 	{
1397 		return Condition(BinaryCondition(BinaryConditionType.NotIn, lhs, makeConditionConstant!field(value)));
1398 	}
1399 
1400 	/// Returns: SQL condition `field IS NULL`
1401 	Condition isNull() @safe
1402 	{
1403 		return Condition(UnaryCondition(UnaryConditionType.IsNull, lhs));
1404 	}
1405 
1406 	alias equalsNull = isNull;
1407 
1408 	/// Returns: SQL condition `field IS NOT NULL`
1409 	Condition isNotNull() @safe
1410 	{
1411 		return Condition(UnaryCondition(UnaryConditionType.IsNotNull, lhs));
1412 	}
1413 
1414 	alias notEqualsNull = isNotNull;
1415 
1416 	/// Returns: SQL condition `field EXISTS`
1417 	Condition exists() @safe
1418 	{
1419 		return Condition(UnaryCondition(UnaryConditionType.Exists, lhs));
1420 	}
1421 
1422 	/// Returns: SQL condition `field NOT EXISTS`
1423 	Condition notExists() @safe
1424 	{
1425 		return Condition(UnaryCondition(UnaryConditionType.NotExists, lhs));
1426 	}
1427 
1428 	/// Returns: SQL condition `field BETWEEN min AND max`
1429 	Condition between(L, R)(L min, R max) @safe
1430 	{
1431 		return Condition(TernaryCondition(
1432 			TernaryConditionType.Between,
1433 			lhs,
1434 			makeConditionConstant!field(min),
1435 			makeConditionConstant!field(max)
1436 		));
1437 	}
1438 
1439 	/// Returns: SQL condition `field NOT BETWEEN min AND max`
1440 	Condition notBetween(L, R)(L min, R max) @safe
1441 	{
1442 		return Condition(TernaryCondition(
1443 			TernaryConditionType.NotBetween,
1444 			lhs,
1445 			makeConditionConstant!field(min),
1446 			makeConditionConstant!field(max)
1447 		));
1448 	}
1449 
1450 	mixin DisallowOperators!(
1451 		"`ConditionBuilderField!(" ~ T.stringof ~ ")` on " ~ field.sourceColumn
1452 	);
1453 
1454 	mixin DynamicMissingMemberErrorHelper!(
1455 		"field comparision operator",
1456 		"`ConditionBuilderField!(" ~ T.stringof ~ ")` on " ~ field.sourceColumn
1457 	);
1458 }
1459 
1460 /// Type that actually implements the asc/desc methods inside the orderBy
1461 /// callback. (`OrderBuilder`) Defaults to ascending.
1462 struct OrderBuilderField(T, ModelFormat.Field field)
1463 {
1464 	private string tableName;
1465 	private string columnName;
1466 
1467 	/// Constructs this OrderBuilderField with the given columnName for generated orders.
1468 	this(string tableName, string columnName) @safe
1469 	{
1470 		this.tableName = tableName;
1471 		this.columnName = columnName;
1472 	}
1473 
1474 	/// Ascending ordering.
1475 	ffi.FFIOrderByEntry asc() @safe
1476 	{
1477 		return ffi.FFIOrderByEntry(ffi.FFIOrdering.asc, ffi.ffi(tableName), ffi.ffi(columnName));
1478 	}
1479 
1480 	/// Descending ordering.
1481 	ffi.FFIOrderByEntry desc() @safe
1482 	{
1483 		return ffi.FFIOrderByEntry(ffi.FFIOrdering.desc, ffi.ffi(tableName), ffi.ffi(columnName));
1484 	}
1485 
1486 	mixin DisallowOperators!(
1487 		"`OrderBuilderField!(" ~ T.stringof ~ ")` on " ~ field.sourceColumn
1488 	);
1489 
1490 	mixin DynamicMissingMemberErrorHelper!(
1491 		"field ordering",
1492 		"`OrderBuilderField!(" ~ T.stringof ~ ")` on " ~ field.sourceColumn
1493 	);
1494 }
1495 
1496 private struct JoinInformation
1497 {
1498 	private static immutable joinAliasList = {
1499 		// list of _0, _1, _2, _3, ... embedded into the executable
1500 		string[] aliasList;
1501 		foreach (i; 0 .. maxJoins)
1502 			aliasList ~= ("_" ~ i.to!string);
1503 		return aliasList;
1504 	}();
1505 
1506 	static struct JoinSuppl
1507 	{
1508 		string placeholder;
1509 		bool include;
1510 	}
1511 
1512 	private ffi.FFIJoin[] joins;
1513 	/// Supplemental information for joins, same length and order as in joins.
1514 	private JoinSuppl[] joinSuppl;
1515 	/// Lookup foreign key name -> array index
1516 	private size_t[string] joinedTables;
1517 }
1518 
1519 /**
1520  * This is the builder struct that's used for update operations.
1521  *
1522  * Don't construct this struct manually, use the db.update or tx.update method
1523  * to create this struct.
1524  *
1525  * Methods you can call on this builder to manipulate the result:
1526  * - `condition` to limit which rows to update. (can only be called once)
1527  * - `set!("sourceColumnName")(value)` to update a single column to the given
1528  *   value
1529  * - `set(patchValue)`, where patchValue is a patch for this UpdateOperation, to
1530  *   set multiple fields at once.
1531  *
1532  * Finishing methods you can call on this builder:
1533  * - `await` to send the prepared update operation
1534  */
1535 struct UpdateOperation(
1536 	T : Model,
1537 	bool hasWhere = false,
1538 )
1539 {
1540 @safe:
1541 	private const(DormDB)* db;
1542 	private ffi.DBTransactionHandle tx;
1543 	private ffi.FFICondition[] conditionTree;
1544 	private JoinInformation joinInformation;
1545 	private ffi.FFIUpdate[] updates;
1546 
1547 	// TODO: might be copyable
1548 	@disable this(this);
1549 
1550 	static if (!hasWhere)
1551 	{
1552 		/// Argument to `condition`. Callback that takes in a
1553 		/// `ConditionBuilder!T` and returns a `Condition` that can easily be
1554 		/// created using that builder.
1555 		alias ConditionBuilderCallback = Condition delegate(ConditionBuilder!T);
1556 
1557 		/// Limits the update to only rows matching this condition. Maps to the
1558 		/// `WHERE` clause in an SQL statement.
1559 		///
1560 		/// This method may only be called once on each query.
1561 		///
1562 		/// See `ConditionBuilder` to see how the callback-based overload is
1563 		/// implemented. Basically the argument that is passed to the callback
1564 		/// is a virtual type that mirrors all the DB-related types from the
1565 		/// Model class, on which operations such as `.equals` or `.like` can
1566 		/// be called to generate conditions.
1567 		///
1568 		/// Use the `Condition.and(...)`, `Condition.or(...)` or `Condition.not(...)`
1569 		/// methods to combine conditions into more complex ones. You can also
1570 		/// choose to not use the builder object at all and integrate manually
1571 		/// constructed
1572 		UpdateOperation!(T, true) condition(
1573 			ConditionBuilderCallback callback
1574 		) return @trusted
1575 		{
1576 			scope ConditionBuilderData data;
1577 			scope ConditionBuilder!T builder;
1578 			builder.builderData = &data;
1579 			data.joinInformation = move(joinInformation);
1580 			conditionTree = callback(builder).makeTree;
1581 			joinInformation = move(data.joinInformation);
1582 			return cast(typeof(return))move(this);
1583 		}
1584 	}
1585 
1586 	/// Method to set one field or multiple via a patch. Update will be
1587 	/// performed when `await` is called.
1588 	template set(FieldOrPatch...)
1589 	{
1590 		static if (FieldOrPatch.length == 0)
1591 		{
1592 			typeof(this) set(P)(P patch) return
1593 			{
1594 				setPatch(patch);
1595 				return move(this);
1596 			}
1597 		}
1598 		else
1599 		{
1600 			static assert(FieldOrPatch.length == 1,
1601 				"Allowed template types on `update.set!(...)` are:\n"
1602 				~ "\t- `set!(\"fieldName\")(value)`\n"
1603 				~ "\t- `set(SomePatch(...))`");
1604 
1605 			static if (is(FieldOrPatch[0] == struct))
1606 			{
1607 				typeof(this) set(FieldOrPatch[0] patch) return
1608 				{
1609 					setPatch(patch);
1610 					return move(this);
1611 				}
1612 			}
1613 			else
1614 			{
1615 				static assert(hasDormField!(T, FieldOrPatch[0]),
1616 					"Called update.set with field `" ~ FieldOrPatch[0]
1617 					~ "`, but it doesn't exist on Model `"
1618 					~ T.stringof ~ "`\n\tAvailable fields:" ~ DormListFieldsForError!T);
1619 
1620 				typeof(this) set(typeof(mixin("T.", FieldOrPatch[0])) value) return
1621 				{
1622 					enum field = DormField!(T, FieldOrPatch[0]);
1623 					static immutable columnName = field.columnName;
1624 					updates ~= ffi.FFIUpdate(
1625 						ffi.ffi(columnName),
1626 						conditionValue!field(value)
1627 					);
1628 					return move(this);
1629 				}
1630 			}
1631 		}
1632 	}
1633 
1634 	private void setPatch(TPatch)(TPatch patch)
1635 	if (isSomePatch!TPatch)
1636 	{
1637 		mixin ValidatePatch!(TPatch, T);
1638 
1639 		import std.array;
1640 
1641 		enum fields = FilterLayoutFields!(T, TPatch);
1642 
1643 		static assert(fields.length > 0, "Could not find any fields to set in patch! "
1644 			~ "Model: " ~ T.stringof ~ ", Patch: " ~ TPatch.stringof);
1645 
1646 		static foreach (i, field; fields)
1647 		{{
1648 			updates ~= ffi.FFIUpdate(
1649 				ffi.ffi(field.columnName),
1650 				conditionValue!field(
1651 					mixin("patch.", field.sourceColumn)
1652 				)
1653 			);
1654 		}}
1655 	}
1656 
1657 	/**
1658 	 * Starts the update procedure and waits for the result. Throws in case of
1659 	 * an error. Returns the number of rows affected.
1660 	 *
1661 	 * Uses the state modified by previous calls to the builder methods like
1662 	 * `set` and `condition` on this builder object.
1663 	 *
1664 	 * Bugs: currently does not support joins because the underlying library
1665 	 * doesn't expose them yet.
1666 	 */
1667 	ulong await()
1668 	{
1669 		// TODO: use join information
1670 
1671 		return (() @trusted {
1672 			auto ctx = FreeableAsyncResult!ulong.make;
1673 			ffi.rorm_db_update(
1674 				db.handle,
1675 				tx,
1676 				ffi.ffi(DormLayout!T.tableName),
1677 				ffi.ffi(updates),
1678 				conditionTree.length ? &conditionTree[0] : null,
1679 				ctx.callback.expand
1680 			);
1681 			return ctx.result;
1682 		})();
1683 	}
1684 }
1685 
1686 /**
1687  * This is the builder struct that's used for delete operations.
1688  *
1689  * Don't construct this struct manually, use the db.remove or tx.remove method
1690  * to create this struct.
1691  *
1692  * Finishing methods you can call on this builder:
1693  * - `byCondition` to delete all rows matching the condition.
1694  * - `single` to delete a single instance, matched by primary key.
1695  * - `all` to delete all rows in the table.
1696  */
1697 struct RemoveOperation(T : Model)
1698 {
1699 @safe:
1700 	private const(DormDB)* db;
1701 	private ffi.DBTransactionHandle tx;
1702 
1703 	// TODO: might be copyable
1704 	@disable this(this);
1705 
1706 	/// Argument to `condition`. Callback that takes in a
1707 	/// `ConditionBuilder!T` and returns a `Condition` that can easily be
1708 	/// created using that builder.
1709 	alias ConditionBuilderCallback = Condition delegate(ConditionBuilder!T);
1710 
1711 	/**
1712 	 * Deletes the rows matching this condition. Maps to the `WHERE` clause in
1713 	 * an SQL statement.
1714 	 *
1715 	 * See `ConditionBuilder` to see how the callback-based overload is
1716 	 * implemented. Basically the argument that is passed to the callback
1717 	 * is a virtual type that mirrors all the DB-related types from the
1718 	 * Model class, on which operations such as `.equals` or `.like` can
1719 	 * be called to generate conditions.
1720 	 *
1721 	 * Use the `Condition.and(...)`, `Condition.or(...)` or `Condition.not(...)`
1722 	 * methods to combine conditions into more complex ones. You can also
1723 	 * choose to not use the builder object at all and integrate manually
1724 	 * constructed.
1725 	 *
1726 	 * Returns: DB-returned number of how many rows have been touched. May also
1727 	 * include foreign rows deleted by referential actions and other things.
1728 	 *
1729 	 * Bugs: currently does not support joins because the underlying library
1730 	 * doesn't expose them yet.
1731 	 */
1732 	ulong byCondition(
1733 		ConditionBuilderCallback callback
1734 	) return @trusted
1735 	{
1736 		scope ConditionBuilderData data;
1737 		scope ConditionBuilder!T builder;
1738 		builder.builderData = &data;
1739 		auto conditionTree = callback(builder).makeTree;
1740 		auto joinInformation = move(data.joinInformation);
1741 
1742 		// TODO: use join information
1743 
1744 		return (() @trusted {
1745 			auto ctx = FreeableAsyncResult!ulong.make;
1746 			ffi.rorm_db_delete(
1747 				db.handle,
1748 				tx,
1749 				ffi.ffi(DormLayout!T.tableName),
1750 				&conditionTree[0],
1751 				ctx.callback.expand
1752 			);
1753 			return ctx.result;
1754 		})();
1755 	}
1756 
1757 	/**
1758 	 * Deletes the passed-in value by limiting the delete operation to the
1759 	 * primary key of this instance.
1760 	 *
1761 	 * Returns: true if anything was deleted, false otherwise.
1762 	 */
1763 	bool single(T value) @safe
1764 	{
1765 		return singleImpl(conditionValue!(DormPrimaryKey!T)(
1766 			mixin("value.", DormPrimaryKey!T.sourceColumn)));
1767 	}
1768 
1769 	/// ditto
1770 	bool single(P)(P patch) @safe
1771 	if (!is(P == T) && isSomePatch!P)
1772 	{
1773 		mixin ValidatePatch!(P, T);
1774 
1775 		static assert(is(typeof(mixin("patch.", DormPrimaryKey!T.sourceColumn))),
1776 			"Primary key '" ~ DormPrimaryKey!T.sourceColumn
1777 			~ "' must be included in patch type "
1778 			~ P.stringof ~ " in order to be a valid argument to remove!");
1779 
1780 		return singleImpl(conditionValue!(DormPrimaryKey!T)(
1781 			mixin("patch.", DormPrimaryKey!T.sourceColumn)));
1782 	}
1783 
1784 	private bool singleImpl(ffi.FFIValue primaryKey) @trusted
1785 	{
1786 		ffi.FFICondition condition, lhs, rhs;
1787 		condition.type = ffi.FFICondition.Type.BinaryCondition;
1788 		condition.binaryCondition.type = ffi.FFIBinaryCondition.Type.Equals;
1789 		condition.binaryCondition.lhs = &lhs;
1790 		condition.binaryCondition.rhs = &rhs;
1791 
1792 		lhs.type = ffi.FFICondition.Type.Value;
1793 		rhs.type = ffi.FFICondition.Type.Value;
1794 		lhs.value = columnValue(DormLayout!T.tableName, DormPrimaryKey!T.columnName);
1795 		rhs.value = primaryKey;
1796 
1797 		auto ctx = FreeableAsyncResult!ulong.make;
1798 		ffi.rorm_db_delete(
1799 			db.handle,
1800 			tx,
1801 			ffi.ffi(DormLayout!T.tableName),
1802 			&condition,
1803 			ctx.callback.expand
1804 		);
1805 		return ctx.result != 0;
1806 	}
1807 
1808 	/**
1809 	 * Deletes the passed-in values by limiting the delete operation to the
1810 	 * primary key of this instance.
1811 	 *
1812 	 * Returns: DB-returned number of how many rows have been touched. May also
1813 	 * include foreign rows deleted by referential actions and other things.
1814 	 */
1815 	ulong bulk(T[] values...) @trusted
1816 	{
1817 		ffi.FFICondition[] condition, rhs;
1818 		condition.length = values.length;
1819 		rhs.length = values.length;
1820 		ffi.FFICondition lhs;
1821 		lhs.type = ffi.FFICondition.Type.Value;
1822 		lhs.value = columnValue(DormLayout!T.tableName, DormPrimaryKey!T.columnName);
1823 
1824 		foreach (i, value; values)
1825 		{
1826 			condition[i].type = ffi.FFICondition.Type.BinaryCondition;
1827 			condition[i].binaryCondition.type = ffi.FFIBinaryCondition.Type.Equals;
1828 			condition[i].binaryCondition.lhs = &lhs;
1829 			condition[i].binaryCondition.rhs = &rhs[i];
1830 
1831 			rhs[i].type = ffi.FFICondition.Type.Value;
1832 			rhs[i].value = conditionValue!(DormPrimaryKey!T)(
1833 				mixin("value.", DormPrimaryKey!T.sourceColumn));
1834 		}
1835 
1836 		ffi.FFICondition finalCondition;
1837 		finalCondition.type = ffi.FFICondition.Type.Disjunction;
1838 		finalCondition.disjunction = ffi.ffi(condition);
1839 
1840 		auto ctx = FreeableAsyncResult!ulong.make;
1841 		ffi.rorm_db_delete(
1842 			db.handle,
1843 			tx,
1844 			ffi.ffi(DormLayout!T.tableName),
1845 			&finalCondition,
1846 			ctx.callback.expand
1847 		);
1848 		return ctx.result;
1849 	}
1850 
1851 	/** 
1852 	 * Deletes all entries in this model.
1853 	 *
1854 	 * Returns: DB-returned number of how many rows have been touched. May also
1855 	 * include foreign rows deleted by referential actions and other things.
1856 	 */
1857 	ulong all() @trusted
1858 	{
1859 		auto ctx = FreeableAsyncResult!ulong.make;
1860 		ffi.rorm_db_delete(
1861 			db.handle,
1862 			tx,
1863 			ffi.ffi(DormLayout!T.tableName),
1864 			null,
1865 			ctx.callback.expand
1866 		);
1867 		return ctx.result;
1868 	}
1869 }
1870 
1871 /**
1872  * This is the builder struct that's used for select operations (queries)
1873  *
1874  * Don't construct this struct manually, use the db.select or tx.select method
1875  * (UFCS method defined globally) to create this struct.
1876  *
1877  * Methods you can call on this builder to manipulate the result:
1878  *
1879  * The following methods are implemented for restricting queries: (most can
1880  * only be called once, which is enforced through the template parameters)
1881  * - `condition` is used to set the "WHERE" clause in SQL. It can only be
1882  *   called once on any query operation.
1883  * - `limit` can be used to set a maximum number of rows to return. When this
1884  *   restriction is called, `findOne` and `findOptional` can no longer be used.
1885  * - `offset` can be used to offset after how many rows to start returning.
1886  * - `orderBy` can be used to order how the results are to be returned by the
1887  *   database.
1888  *
1889  * The following methods are important when working with `ModelRef` / foreign
1890  * keys:
1891  * - `populate` eagerly loads data from a foreign model, (re)using a join
1892  *
1893  * Finishing methods you can call on this builder:
1894  *
1895  * The following methods can be used to extract the data:
1896  * - `stream` to asynchronously stream data. (can be used as iterator / range)
1897  * - `array` to eagerly fetch all data and do a big memory allocation to store
1898  *   all the values into.
1899  * - `findOne` to find the first matching item or throw for no data.
1900  * - `findOptional` to find the first matching item or return Nullable!T.init
1901  *   for no data.
1902  *
1903  * There are restrictions when `stream`/`array` as well as when
1904  * `findOne`/`findOptional` can be used:
1905  *
1906  * `stream`/`array` are usable when:
1907  * - neither `limit` and `offset` are set
1908  * - both `limit` and `offset` are set
1909  * - only `limit` is set and `offset` is not set
1910  *
1911  * `findOne`/`findOptional` are only usable when no `limit` is set.
1912  */
1913 struct SelectOperation(
1914 	T,
1915 	TSelect,
1916 	bool hasWhere = false,
1917 	bool hasOffset = false,
1918 	bool hasLimit = false,
1919 )
1920 {
1921 @safe:
1922 	private const(DormDB)* db;
1923 	private ffi.DBTransactionHandle tx;
1924 	private ffi.FFICondition[] conditionTree;
1925 	private ffi.FFIOrderByEntry[] ordering;
1926 	private JoinInformation joinInformation;
1927 	private ulong _offset, _limit;
1928 
1929 	// TODO: might be copyable
1930 	@disable this(this);
1931 
1932 	static if (!hasWhere)
1933 	{
1934 		/// Argument to `condition`. Callback that takes in a
1935 		/// `ConditionBuilder!T` and returns a `Condition` that can easily be
1936 		/// created using that builder.
1937 		alias ConditionBuilderCallback = Condition delegate(ConditionBuilder!T);
1938 
1939 		/// Limits the query to only rows matching this condition. Maps to the
1940 		/// `WHERE` clause in an SQL statement.
1941 		///
1942 		/// This method may only be called once on each query.
1943 		///
1944 		/// See `ConditionBuilder` to see how the callback-based overload is
1945 		/// implemented. Basically the argument that is passed to the callback
1946 		/// is a virtual type that mirrors all the DB-related types from the
1947 		/// Model class, on which operations such as `.equals` or `.like` can
1948 		/// be called to generate conditions.
1949 		///
1950 		/// Use the `Condition.and(...)`, `Condition.or(...)` or `Condition.not(...)`
1951 		/// methods to combine conditions into more complex ones. You can also
1952 		/// choose to not use the builder object at all and integrate manually
1953 		/// constructed
1954 		SelectOperation!(T, TSelect, true, hasOffset, hasLimit) condition(
1955 			ConditionBuilderCallback callback
1956 		) return @trusted
1957 		{
1958 			scope ConditionBuilderData data;
1959 			scope ConditionBuilder!T builder;
1960 			builder.builderData = &data;
1961 			data.joinInformation = move(joinInformation);
1962 			conditionTree = callback(builder).makeTree;
1963 			joinInformation = move(data.joinInformation);
1964 			return cast(typeof(return))move(this);
1965 		}
1966 	}
1967 
1968 	/// Argument to `orderBy`. Callback that takes in an `OrderBuilder!T` and
1969 	/// returns the ffi ordering value that can be easily created using the
1970 	/// builder.
1971 	alias OrderBuilderCallback = ffi.FFIOrderByEntry delegate(OrderBuilder!T);
1972 
1973 	/// Allows ordering by the specified field with the specified direction.
1974 	/// (defaults to ascending)
1975 	///
1976 	/// Returning `u => u.none` means no ordering will be added. (Useful only
1977 	/// at runtime)
1978 	///
1979 	/// Multiple `orderBy` can be added to the same query object. Ordering is
1980 	/// important - the first order orders all the rows, the second order only
1981 	/// orders each group of rows where the previous order had the same values,
1982 	/// etc.
1983 	typeof(this) orderBy(OrderBuilderCallback callback) return @trusted
1984 	{
1985 		scope ConditionBuilderData data;
1986 		scope OrderBuilder!T builder;
1987 		builder.builderData = &data;
1988 		data.joinInformation = move(joinInformation);
1989 		auto order = callback(builder);
1990 		if (order !is typeof(order).init)
1991 			ordering ~= order;
1992 		joinInformation = move(data.joinInformation);
1993 		return move(this);
1994 	}
1995 
1996 	/// Argument to `populate`. Callback that takes in an `OrderBuilder!T` and
1997 	/// returns the ffi ordering value that can be easily created using the
1998 	/// builder.
1999 	alias PopulateBuilderCallback = PopulateRef[] delegate(PopulateBuilder!T);
2000 
2001 	/// Eagerly loads the data for the specified foreign key ModelRef fields
2002 	/// when executing the query.
2003 	///
2004 	/// Returning `u => null` means no further populate will be added. (Useful
2005 	/// only at runtime)
2006 	typeof(this) populate(PopulateBuilderCallback callback) return @trusted
2007 	{
2008 		scope ConditionBuilderData data;
2009 		scope PopulateBuilder!T builder;
2010 		builder.builderData = &data;
2011 		data.joinInformation = move(joinInformation);
2012 		foreach (populates; callback(builder))
2013 			joinInformation.joinSuppl[populates.idx].include = true;
2014 		joinInformation = move(data.joinInformation);
2015 		return move(this);
2016 	}
2017 
2018 	static if (!hasOffset)
2019 	{
2020 		/// Sets the offset. (number of rows after which to return from the database)
2021 		SelectOperation!(T, TSelect, hasWhere, true, hasLimit) offset(ulong offset) return @trusted
2022 		{
2023 			_offset = offset;
2024 			return cast(typeof(return))move(this);
2025 		}
2026 	}
2027 
2028 	static if (!hasLimit)
2029 	{
2030 		/// Sets the maximum number of rows to return. Using this method
2031 		/// disables the `findOne` and `findOptional` methods.
2032 		SelectOperation!(T, TSelect, hasWhere, hasOffset, true) limit(ulong limit) return @trusted
2033 		{
2034 			_limit = limit;
2035 			return cast(typeof(return))move(this);
2036 		}
2037 	}
2038 
2039 	static if (!hasOffset && !hasLimit)
2040 	{
2041 		/// Implementation detail, makes it possible to use `[start .. end]` on
2042 		/// the select struct to set both offset and limit at the same time.
2043 		///
2044 		/// Start is inclusive, end is exclusive - mimicking how array slicing
2045 		/// works.
2046 		ulong[2] opSlice(size_t dim)(ulong start, ulong end)
2047 		{
2048 			return [start, end];
2049 		}
2050 
2051 		/// ditto
2052 		SelectOperation!(T, TSelect, hasWhere, true, true) opIndex(ulong[2] slice) return @trusted
2053 		{
2054 			this._offset = slice[0];
2055 			this._limit = cast(long)slice[1] - cast(long)slice[0];
2056 			return cast(typeof(return))move(this);
2057 		}
2058 
2059 		/// ditto
2060 		SelectOperation!(T, TSelect, hasWhere, true, true) range(ulong start, ulong endExclusive) return @safe
2061 		{
2062 			return this[start .. endExclusive];
2063 		}
2064 	}
2065 
2066 	private ffi.FFIOption!(ffi.FFILimitClause) ffiLimit() const @property @safe
2067 	{
2068 		ffi.FFIOption!(ffi.FFILimitClause) ret;
2069 		static if (hasLimit)
2070 		{
2071 			ret.state = ret.State.some;
2072 			ret.raw_value.limit = _limit;
2073 			static if (hasOffset)
2074 				ret.raw_value.offset = ffi.FFIOption!ulong(_offset);
2075 		}
2076 		return ret;
2077 	}
2078 
2079 	static if (hasLimit || !hasOffset)
2080 	{
2081 		/// Fetches all result data into one array. Uses the GC to allocate the
2082 		/// data, so it's not needed to keep track of how long objects live by the
2083 		/// user.
2084 		TSelect[] array() @trusted
2085 		{
2086 			enum fields = FilterLayoutFields!(T, TSelect);
2087 
2088 			ffi.FFIColumnSelector[fields.length] columns;
2089 			static foreach (i, field; fields)
2090 			{{
2091 				enum aliasedName = "__" ~ field.columnName;
2092 
2093 				columns[i] = ffi.FFIColumnSelector(
2094 					ffi.ffi(DormLayout!T.tableName),
2095 					ffi.ffi(field.columnName),
2096 					ffi.ffi(aliasedName)
2097 				);
2098 			}}
2099 
2100 			mixin(makeRtColumns);
2101 
2102 			TSelect[] ret;
2103 			auto ctx = FreeableAsyncResult!(void delegate(scope ffi.FFIArray!(ffi.DBRowHandle))).make;
2104 			ctx.forward_callback = (scope rows) {
2105 				ret.length = rows.size;
2106 				foreach (i; 0 .. rows.size)
2107 					ret[i] = unwrapRowResult!(T, TSelect)(rows.data[i], joinInformation);
2108 			};
2109 			ffi.rorm_db_query_all(db.handle,
2110 				tx,
2111 				ffi.ffi(DormLayout!T.tableName),
2112 				ffi.ffi(rtColumns),
2113 				ffi.ffi(joinInformation.joins),
2114 				conditionTree.length ? &conditionTree[0] : null,
2115 				ffi.ffi(ordering),
2116 				ffiLimit,
2117 				ctx.callback.expand);
2118 			ctx.result();
2119 			return ret;
2120 		}
2121 
2122 		/// Fetches all data into a range that can be iterated over or processed
2123 		/// with regular range functions. Does not allocate an array to store the
2124 		/// fetched data in, but may still use sparingly the GC in implementation.
2125 		auto stream() @trusted
2126 		{
2127 			enum fields = FilterLayoutFields!(T, TSelect);
2128 
2129 			ffi.FFIColumnSelector[fields.length] columns;
2130 			static foreach (i, field; fields)
2131 			{{
2132 				enum aliasedName = "__" ~ field.columnName;
2133 
2134 				columns[i] = ffi.FFIColumnSelector(
2135 					ffi.ffi(DormLayout!T.tableName),
2136 					ffi.ffi(field.columnName),
2137 					ffi.ffi(aliasedName)
2138 				);
2139 			}}
2140 
2141 			mixin(makeRtColumns);
2142 
2143 			auto stream = sync_call!(ffi.rorm_db_query_stream)(db.handle,
2144 				tx,
2145 				ffi.ffi(DormLayout!T.tableName),
2146 				ffi.ffi(rtColumns),
2147 				ffi.ffi(joinInformation.joins),
2148 				conditionTree.length ? &conditionTree[0] : null,
2149 				ffi.ffi(ordering),
2150 				ffiLimit);
2151 
2152 			return RormStream!(T, TSelect)(stream, joinInformation);
2153 		}
2154 	}
2155 
2156 	static if (!hasLimit)
2157 	{
2158 		/// Returns the first row of the result data or throws if no data exists.
2159 		TSelect findOne() @trusted
2160 		{
2161 			enum fields = FilterLayoutFields!(T, TSelect);
2162 
2163 			ffi.FFIColumnSelector[fields.length] columns;
2164 			static foreach (i, field; fields)
2165 			{{
2166 				enum aliasedName = "__" ~ field.columnName;
2167 
2168 				columns[i] = ffi.FFIColumnSelector(
2169 					ffi.ffi(DormLayout!T.tableName),
2170 					ffi.ffi(field.columnName),
2171 					ffi.ffi(aliasedName)
2172 				);
2173 			}}
2174 
2175 			mixin(makeRtColumns);
2176 
2177 			TSelect ret;
2178 			auto ctx = FreeableAsyncResult!(void delegate(scope ffi.DBRowHandle)).make;
2179 			ctx.forward_callback = (scope row) {
2180 				ret = unwrapRowResult!(T, TSelect)(row, joinInformation);
2181 			};
2182 			ffi.rorm_db_query_one(db.handle,
2183 				tx,
2184 				ffi.ffi(DormLayout!T.tableName),
2185 				ffi.ffi(rtColumns),
2186 				ffi.ffi(joinInformation.joins),
2187 				conditionTree.length ? &conditionTree[0] : null,
2188 				ffi.ffi(ordering),
2189 				ffi.FFIOption!ulong(_offset),
2190 				ctx.callback.expand);
2191 			ctx.result();
2192 			return ret;
2193 		}
2194 
2195 		/// Returns the first row of the result data or throws if no data exists.
2196 		Nullable!TSelect findOptional() @trusted
2197 		{
2198 			enum fields = FilterLayoutFields!(T, TSelect);
2199 
2200 			ffi.FFIColumnSelector[fields.length] columns;
2201 			static foreach (i, field; fields)
2202 			{{
2203 				enum aliasedName = "__" ~ field.columnName;
2204 
2205 				columns[i] = ffi.FFIColumnSelector(
2206 					ffi.ffi(DormLayout!T.tableName),
2207 					ffi.ffi(field.columnName),
2208 					ffi.ffi(aliasedName)
2209 				);
2210 			}}
2211 
2212 			mixin(makeRtColumns);
2213 
2214 			Nullable!TSelect ret;
2215 			auto ctx = FreeableAsyncResult!(void delegate(scope ffi.DBRowHandle)).make;
2216 			ctx.forward_callback = (scope row) {
2217 				if (row)
2218 					ret = unwrapRowResult!(T, TSelect)(row, joinInformation);
2219 			};
2220 			ffi.rorm_db_query_optional(db.handle,
2221 				tx,
2222 				ffi.ffi(DormLayout!T.tableName),
2223 				ffi.ffi(rtColumns),
2224 				ffi.ffi(joinInformation.joins),
2225 				conditionTree.length ? &conditionTree[0] : null,
2226 				ffi.ffi(ordering),
2227 				ffi.FFIOption!ulong(_offset),
2228 				ctx.callback.expand);
2229 			ctx.result();
2230 			return ret;
2231 		}
2232 	}
2233 }
2234 
2235 private enum makeRtColumns = q{
2236 	// inputs: ffi.FFIColumnSelector[n] columns;
2237 	//         JoinInformation joinInformation;
2238 	//         T (template type)
2239 	// output: ffi.FFIColumnSelector[] rtColumns;
2240 
2241 	ffi.FFIColumnSelector[] rtColumns = columns[];
2242 	if (joinInformation.joinSuppl.any!"a.include")
2243 	{
2244 		static foreach (fk; DormForeignKeys!T)
2245 		{
2246 			if (auto joinId = fk.columnName in joinInformation.joinedTables)
2247 			{
2248 				auto suppl = joinInformation.joinSuppl[*joinId];
2249 				if (suppl.include)
2250 				{
2251 					auto ffiPlaceholder = ffi.ffi(suppl.placeholder);
2252 					alias RefField = typeof(mixin("T.", fk.sourceColumn));
2253 					enum filteredFields = FilterLayoutFields!(RefField.TModel, RefField.TSelect);
2254 					size_t start = rtColumns.length;
2255 					size_t i = 0;
2256 					rtColumns.length += filteredFields.length;
2257 					static foreach (field; filteredFields)
2258 					{{
2259 						auto ffiColumnName = ffi.ffi(field.columnName);
2260 						auto aliasCol = text(suppl.placeholder, ("_" ~ field.columnName));
2261 						rtColumns[start + i].tableName = ffiPlaceholder;
2262 						rtColumns[start + i].columnName = ffiColumnName;
2263 						rtColumns[start + i].selectAlias = ffi.ffi(aliasCol);
2264 						i++;
2265 					}}
2266 				}
2267 			}
2268 		}
2269 	}
2270 };
2271 
2272 /// Row streaming range implementation. (query_stream)
2273 private struct RormStream(T, TSelect)
2274 {
2275 	import dorm.lib.util;
2276 
2277 	private static struct RowHandleState
2278 	{
2279 		FreeableAsyncResult!(ffi.DBRowHandle) impl;
2280 		alias impl this;
2281 		bool done;
2282 
2283 		void reset() @safe
2284 		{
2285 			impl.reset();
2286 			done = false;
2287 		}
2288 	}
2289 
2290 	extern(C) private static void rowCallback(
2291 		void* data,
2292 		ffi.DBRowHandle result,
2293 		scope ffi.RormError error
2294 	) nothrow @trusted
2295 	{
2296 		auto res = cast(RowHandleState*)data;
2297 		if (error.tag == ffi.RormError.Tag.NoRowsLeftInStream)
2298 			res.done = true;
2299 		else if (error)
2300 			res.error = error.makeException;
2301 		else
2302 			res.raw_result = result;
2303 		res.awaiter.set();
2304 	}
2305 
2306 	private ffi.DBStreamHandle handle;
2307 	private RowHandleState currentHandle;
2308 	private JoinInformation joinInformation;
2309 	private bool started;
2310 
2311 	this(ffi.DBStreamHandle handle, JoinInformation joinInformation = JoinInformation.init) @trusted
2312 	{
2313 		this.handle = handle;
2314 		this.joinInformation = joinInformation;
2315 		currentHandle = RowHandleState(FreeableAsyncResult!(ffi.DBRowHandle).make);
2316 	}
2317 
2318 	~this() @trusted
2319 	{
2320 		if (started)
2321 		{
2322 			currentHandle.impl.waitAndThrow();
2323 			if (currentHandle.impl.raw_result !is null)
2324 				ffi.rorm_row_free(currentHandle.impl.raw_result);
2325 			ffi.rorm_stream_free(handle);
2326 		}
2327 	}
2328 
2329 	@disable this(this);
2330 
2331 	/// Helper to `foreach` over this entire stream using the row mapped to
2332 	/// `TSelect`.
2333 	int opApply(scope int delegate(TSelect) @system dg) @system
2334 	{
2335 		return opApplyImpl(cast(int delegate(TSelect) @safe) dg);
2336 	}
2337 	/// ditto
2338 	int opApply(scope int delegate(TSelect) @safe dg) @safe
2339 	{
2340 		return opApplyImpl(dg);
2341 	}
2342 	/// ditto
2343 	int opApplyImpl(scope int delegate(TSelect) @safe dg) @safe
2344 	{
2345 		int result = 0;
2346 		for (; !this.empty; this.popFront())
2347 		{
2348 			result = dg(this.front);
2349 			if (result)
2350 				break;
2351 		}
2352 		return result;
2353 	}
2354 
2355 	/// Helper to `foreach` over this entire stream using an index (simply
2356 	/// counting up from 0 in D code) and the row mapped to `TSelect`.
2357 	int opApply(scope int delegate(size_t i, TSelect) @system dg) @system
2358 	{
2359 		return opApplyImpl(cast(int delegate(size_t i, TSelect) @safe) dg);
2360 	}
2361 	/// ditto
2362 	int opApply(scope int delegate(size_t i, TSelect) @safe dg) @safe
2363 	{
2364 		return opApplyImpl(dg);
2365 	}
2366 	/// ditto
2367 	int opApplyImpl(scope int delegate(size_t i, TSelect) @safe dg) @safe
2368 	{
2369 		int result = 0;
2370 		size_t i;
2371 		for (; !this.empty; this.popFront())
2372 		{
2373 			result = dg(i++, this.front);
2374 			if (result)
2375 				break;
2376 		}
2377 		return result;
2378 	}
2379 
2380 	/// Starts the iteration if it hasn't already, waits until data is there
2381 	/// and returns the current row.
2382 	///
2383 	/// Implements the standard D range interface.
2384 	auto front() @trusted
2385 	{
2386 		if (!started) nextIteration();
2387 		return unwrapRowResult!(T, TSelect)(currentHandle.result(), joinInformation);
2388 	}
2389 
2390 	/// Starts the iteration if it hasn't already, waits until data is there
2391 	/// and returns if there is any data left to be read using `front`.
2392 	bool empty() @trusted
2393 	{
2394 		if (!started) nextIteration();
2395 		currentHandle.impl.waitAndThrow();
2396 		return currentHandle.done;
2397 	}
2398 
2399 	/// Starts the iteration if it hasn't already, waits until the current
2400 	/// request is finished and skips the current row, so empty and front can
2401 	/// be called next.
2402 	void popFront() @trusted
2403 	{
2404 		if (!started) nextIteration();
2405 		currentHandle.impl.waitAndThrow();
2406 		if (currentHandle.done)
2407 			throw new Exception("attempted to run popFront on ended stream");
2408 		else if (currentHandle.impl.error)
2409 			throw currentHandle.impl.error;
2410 		else
2411 		{
2412 			ffi.rorm_row_free(currentHandle.impl.raw_result);
2413 			currentHandle.reset();
2414 			nextIteration();
2415 		}
2416 	}
2417 
2418 	private void nextIteration() @trusted
2419 	{
2420 		started = true;
2421 		ffi.rorm_stream_get_row(handle, &rowCallback, cast(void*)&currentHandle);
2422 	}
2423 
2424 	static assert(isInputRange!RormStream, "implementation error: did not become an input range");
2425 }
2426 
2427 /// Extracts the DBRowHandle, optionally using JoinInformation when joins were
2428 /// used, into the TSelect datatype. TSelect may be a DormPatch or the model T
2429 /// directly. This is mostly used internally. Expect changes to this API until
2430 /// there is a stable API.
2431 TSelect unwrapRowResult(T, TSelect)(ffi.DBRowHandle row, JoinInformation ji) @safe
2432 {
2433 	auto base = unwrapRowResultImpl!(T, TSelect)(row, "__");
2434 	if (ji.joins.length)
2435 	{
2436 		static foreach (fk; DormForeignKeys!T)
2437 		{{
2438 			if (auto idx = fk.columnName in ji.joinedTables)
2439 			{
2440 				auto suppl = ji.joinSuppl[*idx];
2441 				if (suppl.include)
2442 				{
2443 					auto prefix = suppl.placeholder;
2444 					alias ModelRef = typeof(mixin("T.", fk.sourceColumn));
2445 					mixin("base.", fk.sourceColumn) =
2446 						unwrapRowResult!(ModelRef.TModel, ModelRef.TSelect)(row, prefix);
2447 				}
2448 			}
2449 		}}
2450 	}
2451 	return base;
2452 }
2453 
2454 /// ditto
2455 TSelect unwrapRowResult(T, TSelect)(ffi.DBRowHandle row) @safe
2456 {
2457 	return unwrapRowResultImpl!(T, TSelect, false)(row, null);
2458 }
2459 
2460 /// Unwraps the row like the other unwrap methods, but prefixes all fields with
2461 /// `<placeholder>_`, so for example placeholder `foo` and field `user` would
2462 /// result in `foo_user`.
2463 TSelect unwrapRowResult(T, TSelect)(ffi.DBRowHandle row, string placeholder) @safe
2464 {
2465 	scope placeholderDot = new char[placeholder.length + 1];
2466 	placeholderDot[0 .. placeholder.length] = placeholder;
2467 	placeholderDot[$ - 1] = '_'; // was dot before, but that's not valid SQL - we use _ to separate names in aliases!
2468 	return unwrapRowResultImpl!(T, TSelect)(row, (() @trusted => cast(string)placeholderDot)());
2469 }
2470 
2471 private TSelect unwrapRowResultImpl(T, TSelect)(ffi.DBRowHandle row, string columnPrefix) @safe
2472 {
2473 	TSelect res;
2474 	static if (is(TSelect == class))
2475 		res = new TSelect();
2476 	ffi.RormError rowError;
2477 	enum fields = FilterLayoutFields!(T, TSelect);
2478 	static foreach (field; fields)
2479 	{
2480 		mixin("res." ~ field.sourceColumn) = extractField!(field, typeof(mixin("res." ~ field.sourceColumn)),
2481 			text(" from model ", T.stringof,
2482 				" in column ", field.sourceColumn,
2483 				" in file ", field.definedAt).idup
2484 			)(row, rowError, columnPrefix);
2485 		if (rowError)
2486 			throw rowError.makeException(" (in column '" ~ columnPrefix ~ field.columnName ~ "')");
2487 	}
2488 	return res;
2489 }
2490 
2491 private T extractField(alias field, T, string errInfo)(
2492 	ffi.DBRowHandle row,
2493 	ref ffi.RormError error,
2494 	string columnPrefix
2495 ) @trusted
2496 {
2497 	import std.conv;
2498 	import dorm.declarative;
2499 
2500 	auto columnName = ffi.ffi(columnPrefix.length
2501 		? columnPrefix ~ field.columnName
2502 		: field.columnName);
2503 
2504 	enum pre = field.isNullable() ? "ffi.rorm_row_get_null_" : "ffi.rorm_row_get_";
2505 	enum suf = "(row, columnName, error)";
2506 
2507 	final switch (field.type) with (ModelFormat.Field.DBType)
2508 	{
2509 		case varchar:
2510 			static if (field.type == varchar) return fieldInto!(T, errInfo)(mixin(pre, "str", suf), error);
2511 			else assert(false);
2512 		case varbinary:
2513 			static if (field.type == varbinary) return fieldInto!(T, errInfo)(mixin(pre, "binary", suf), error);
2514 			else assert(false);
2515 		case int8:
2516 			static if (field.type == int8) return fieldInto!(T, errInfo)(mixin(pre, "i16", suf), error);
2517 			else assert(false);
2518 		case int16:
2519 			static if (field.type == int16) return fieldInto!(T, errInfo)(mixin(pre, "i16", suf), error);
2520 			else assert(false);
2521 		case int32:
2522 			static if (field.type == int32) return fieldInto!(T, errInfo)(mixin(pre, "i32", suf), error);
2523 			else assert(false);
2524 		case int64:
2525 			static if (field.type == int64) return fieldInto!(T, errInfo)(mixin(pre, "i64", suf), error);
2526 			else assert(false);
2527 		case floatNumber:
2528 			static if (field.type == floatNumber) return fieldInto!(T, errInfo)(mixin(pre, "f32", suf), error);
2529 			else assert(false);
2530 		case doubleNumber:
2531 			static if (field.type == doubleNumber) return fieldInto!(T, errInfo)(mixin(pre, "f64", suf), error);
2532 			else assert(false);
2533 		case boolean:
2534 			static if (field.type == boolean) return fieldInto!(T, errInfo)(mixin(pre, "bool", suf), error);
2535 			else assert(false);
2536 		case date:
2537 			static if (field.type == date) return fieldInto!(T, errInfo)(mixin(pre, "date", suf), error);
2538 			else assert(false);
2539 		case time:
2540 			static if (field.type == time) return fieldInto!(T, errInfo)(mixin(pre, "time", suf), error);
2541 			else assert(false);
2542 		case datetime:
2543 			static if (field.type == datetime) return fieldInto!(T, errInfo)(mixin(pre, "datetime", suf), error);
2544 			else assert(false);
2545 
2546 		static assert(
2547 			field.type != set,
2548 			"field type " ~ field.type.to!string ~ " not yet implemented for reading");
2549 
2550 		case choices:
2551 			static if (field.type == choices) return fieldInto!(T, errInfo)(mixin(pre, "str", suf), error);
2552 			else assert(false);
2553 		case set: assert(false);
2554 	}
2555 }
2556 
2557 private T fieldInto(T, string errInfo, From)(scope From v, ref ffi.RormError error) @safe
2558 {
2559 	import dorm.lib.ffi : FFIArray, FFIOption;
2560 	import std.typecons : Nullable;
2561 
2562 	static if (is(T == From))
2563 		return v;
2564 	else static if (is(T == enum))
2565 	{
2566 		auto s = fieldInto!(string, errInfo, From)(v, error);
2567 		static if (is(OriginalType!T == string))
2568 			return cast(T)s;
2569 		else
2570 		{
2571 			switch (s)
2572 			{
2573 				static foreach (f; __traits(allMembers, T))
2574 				{
2575 				case f:
2576 					return __traits(getMember, T, f);
2577 				}
2578 				default:
2579 					error = ffi.RormError(ffi.RormError.Tag.ColumnDecodeError);
2580 					return T.init;
2581 			}
2582 		}
2583 	}
2584 	else static if (is(T == ModelRefImpl!(id, _TModel, _TSelect), alias id, _TModel, _TSelect))
2585 	{
2586 		T ret;
2587 		ret.foreignKey = fieldInto!(typeof(id), errInfo, From)(v, error);
2588 		return ret;
2589 	}
2590 	else static if (is(From == FFIArray!U, U))
2591 	{
2592 		static if (is(T == Res[], Res))
2593 		{
2594 			static if (is(immutable Res == immutable U))
2595 				return (() @trusted => cast(T)v.data.dup)();
2596 			else
2597 				static assert(false, "can't auto-wrap array element type " ~ Res.stringof ~ " into " ~ U.stringof ~ errInfo);
2598 		}
2599 		else static if (is(T == Nullable!V, V))
2600 		{
2601 			return T(fieldInto!(V, errInfo, From)(v, error));
2602 		}
2603 		else
2604 			static assert(false, "can't auto-wrap " ~ U.stringof ~ "[] into " ~ T.stringof ~ errInfo);
2605 	}
2606 	else static if (is(From == FFIOption!U, U))
2607 	{
2608 		static if (is(T == Nullable!V, V))
2609 		{
2610 			if (v.isNull)
2611 				return T.init;
2612 			else
2613 				return T(fieldInto!(V, errInfo)(v.raw_value, error));
2614 		}
2615 		else static if (__traits(compiles, T(null)))
2616 		{
2617 			if (v.isNull)
2618 				return T(null);
2619 			else
2620 				return fieldInto!(T, errInfo)(v.raw_value, error);
2621 		}
2622 		else
2623 		{
2624 			if (v.isNull)
2625 			{
2626 				error = ffi.RormError(ffi.RormError.Tag.ColumnDecodeError);
2627 				return T.init;
2628 			}
2629 			else
2630 			{
2631 				return fieldInto!(T, errInfo)(v.raw_value, error);
2632 			}
2633 		}
2634 	}
2635 	else static if (is(T == Nullable!U, U))
2636 	{
2637 		return T(fieldInto!(U, errInfo, From)(v, error));
2638 	}
2639 	else static if (isIntegral!From)
2640 	{
2641 		static if (isIntegral!T && From.sizeof >= T.sizeof)
2642 		{
2643 			if (v < cast(From)T.min || v > cast(From)T.max)
2644 			{
2645 				error = ffi.RormError(ffi.RormError.Tag.ColumnDecodeError);
2646 				return T.init;
2647 			}
2648 			else
2649 			{
2650 				return cast(T)v;
2651 			}
2652 		}
2653 		else static if (isFloatingPoint!T)
2654 		{
2655 			return cast(T)v;
2656 		}
2657 		else
2658 			static assert(false, "can't put " ~ From.stringof ~ " into " ~ T.stringof ~ errInfo);
2659 	}
2660 	else static if (isFloatingPoint!From)
2661 	{
2662 		static if (isFloatingPoint!T)
2663 			return cast(T)v;
2664 		else
2665 			static assert(false, "can't put " ~ From.stringof ~ " into " ~ T.stringof ~ errInfo);
2666 	}
2667 	else static if (is(From : ffi.FFITime))
2668 	{
2669 		static if (is(T == TimeOfDay))
2670 		{
2671 			try
2672 			{
2673 				return TimeOfDay(cast(int)v.hour, cast(int)v.min, cast(int)v.sec);
2674 			}
2675 			catch (DateTimeException)
2676 			{
2677 				error = ffi.RormError(ffi.RormError.Tag.InvalidTimeError);
2678 				return T.init;
2679 			}
2680 		}
2681 		else
2682 			static assert(false, "can't put " ~ From.stringof ~ " into " ~ T.stringof ~ errInfo);
2683 	}
2684 	else static if (is(From : ffi.FFIDate))
2685 	{
2686 		static if (is(T == Date))
2687 		{
2688 			try
2689 			{
2690 				return Date(cast(int)v.year, cast(int)v.month, cast(int)v.day);
2691 			}
2692 			catch (DateTimeException)
2693 			{
2694 				error = ffi.RormError(ffi.RormError.Tag.InvalidDateError);
2695 				return T.init;
2696 			}
2697 		}
2698 		else
2699 			static assert(false, "can't put " ~ From.stringof ~ " into " ~ T.stringof ~ errInfo);
2700 	}
2701 	else static if (is(From : ffi.FFIDateTime))
2702 	{
2703 		try
2704 		{
2705 			static if (is(T == DateTime))
2706 			{
2707 				return DateTime(cast(int)v.year, cast(int)v.month, cast(int)v.day,
2708 					cast(int)v.hour, cast(int)v.min, cast(int)v.sec);
2709 			}
2710 			else static if (is(T == SysTime))
2711 			{
2712 				return SysTime(DateTime(cast(int)v.year, cast(int)v.month, cast(int)v.day,
2713 					cast(int)v.hour, cast(int)v.min, cast(int)v.sec), UTC());
2714 			}
2715 			else static if (is(T == long) || is(T == ulong))
2716 			{
2717 				return cast(T)SysTime(DateTime(cast(int)v.year, cast(int)v.month, cast(int)v.day,
2718 					cast(int)v.hour, cast(int)v.min, cast(int)v.sec), UTC()).stdTime;
2719 			}
2720 			else
2721 				static assert(false, "can't put " ~ From.stringof ~ " into " ~ T.stringof ~ errInfo);
2722 		}
2723 		catch (DateTimeException)
2724 		{
2725 			error = ffi.RormError(ffi.RormError.Tag.InvalidDateTimeError);
2726 			return T.init;
2727 		}
2728 	}
2729 	else
2730 		static assert(false, "did not implement conversion from " ~ From.stringof ~ " into " ~ T.stringof ~ errInfo);
2731 }
2732 
2733 /// Sets up the DORM runtime that is required to use DORM (and its
2734 /// implementation library "RORM")
2735 ///
2736 /// You must use this mixin to use DORM. You can simply call
2737 /// ```d
2738 /// mixin SetupDormRuntime;
2739 /// ```
2740 /// in your entrypoint file to have the runtime setup automatically.
2741 ///
2742 /// Supports passing in a timeout (Duration or integer msecs)
2743 mixin template SetupDormRuntime(alias timeout = 10.seconds)
2744 {
2745 	__gshared bool _initializedDormRuntime;
2746 
2747 	shared static this() @trusted
2748 	{
2749 		import dorm.lib.util : sync_call;
2750 		import dorm.lib.ffi : rorm_runtime_start;
2751 
2752 		sync_call!(rorm_runtime_start)();
2753 		_initializedDormRuntime = true;
2754 	}
2755 
2756 	shared static ~this() @trusted
2757 	{
2758 		import core.time : Duration;
2759 		import dorm.lib.util;
2760 		import dorm.lib.ffi : rorm_runtime_shutdown;
2761 
2762 		if (_initializedDormRuntime)
2763 		{
2764 			static if (is(typeof(timeout) == Duration))
2765 				sync_call!(rorm_runtime_shutdown)(timeout.total!"msecs");
2766 			else
2767 				sync_call!(rorm_runtime_shutdown)(timeout);
2768 		}
2769 	}
2770 }