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