1 module dorm.lib.ffi_impl;
2 
3 extern(C):
4 
5 /**
6  * rorm FFI struct definition for arrays and strings (equivalent to D and Rust slices)
7  */
8 struct FFIArray(T)
9 {
10 	/**
11 	 * Pointer to the first item in the slice.
12 	 */
13 	@system T* content;
14 	/**
15 	 * The length of the slice. (count of elements)
16 	 */
17 	@system size_t size;
18 
19 	this(typeof(null)) @safe
20 	{
21 		this.content = null;
22 		this.size = 0;
23 	}
24 
25 	this(T* content, size_t size) @safe
26 	{
27 		this.content = content;
28 		this.size = size;
29 	}
30 
31 	/**
32 	 * Does a zero-copy conversion of this FFIArray to a D slice. Regular slice
33 	 * ownership semantics, e.g. variable lifetime, still apply. DIP1000 should
34 	 * help avoid lifetime issues.
35 	 */
36 	inout(T)[] data() @system inout nothrow pure @nogc return
37 	{
38 		return content[0 .. size];
39 	}
40 	/// ditto
41 	alias opSlice = data;
42 
43 	/**
44 	 * Zero-copy conversion of a native D slice to an FFIArray. The resulting
45 	 * FFIArray has the same lifetime as the native D slice, so a stack
46 	 * allocated slice will also cause the FFIArray to become invalid when
47 	 * leaving its scope. DIP1000 should help avoid such issues.
48 	 */
49 	static FFIArray fromData(return T[] data) @trusted nothrow pure @nogc
50 	{
51 		return FFIArray(data.ptr, data.length);
52 	}
53 
54 	/// ditto
55 	static FFIArray fromData(size_t n)(return ref T[n] data) @trusted nothrow pure @nogc
56 	{
57 		return FFIArray(data.ptr, data.length);
58 	}
59 
60 	string toString() const @trusted pure
61 	{
62 		import std.conv;
63 
64 		return data.to!string;
65 	}
66 }
67 
68 /// Representation of a string.
69 alias FFIString = FFIArray!(const(char));
70 
71 /// helper function to create an FFI slice of a D native array/slice type.
72 extern(D) FFIString ffi(string s) @safe { return FFIString.fromData(s); }
73 /// ditto
74 extern(D) FFIArray!T ffi(T)(T[] s) @safe { return FFIArray!T.fromData(s); }
75 /// ditto
76 extern(D) FFIArray!T ffi(T, size_t n)(ref T[n] s) @safe { return FFIArray!T.fromData(s); }
77 
78 /** 
79  * optional value returned by rorm functions.
80  */
81 struct FFIOption(T)
82 {
83 	import std.typecons : Nullable;
84 
85 	/// tagged union type
86 	enum State
87 	{
88 		/// raw_value is not valid (no value inside FFIOption)
89 		none,
90 		/// raw_value is the effective value
91 		some
92 	}
93 
94 	/// raw state access
95 	State state;
96 	/// raw value access
97 	T raw_value;
98 
99 	this(T value)
100 	{
101 		state = State.some;
102 		raw_value = value;
103 	}
104 
105 	ref auto opAssign(T value) return
106 	{
107 		state = State.some;
108 		raw_value = value;
109 		return this;
110 	}
111 
112 	/// Returns true if the value is set, otherwise false.
113 	bool opCast(T : bool)() const @safe nothrow @nogc
114 	{
115 		return state != State.none;
116 	}
117 
118 	bool isNull() const @safe nothrow @nogc
119 	{
120 		return state == State.none;
121 	}
122 
123 	alias asNullable this;
124 	/// Converts the FFIOption to a std Nullable!T
125 	auto asNullable() const @safe nothrow @nogc
126 	{
127 		static if (__traits(compiles, Nullable!(T)(raw_value)))
128 			return state == State.none
129 				? Nullable!(T).init
130 				: Nullable!(T)(raw_value);
131 		else
132 			return state == State.none
133 				? Nullable!(const T).init
134 				: Nullable!(const T)(raw_value);
135 	}
136 
137 	static if (__traits(compiles, { T v = null; }))
138 	{
139 		inout(T) embedNull() inout @safe nothrow @nogc
140 		{
141 			return state == State.none ? inout(T)(null) : raw_value;
142 		}
143 	}
144 }
145 
146 /**
147  * Representation of the database backend.
148  *
149  * This is used to determine the correct driver and the correct dialect to use.
150  */
151 enum DBBackend
152 
153 {
154 	/**
155 	 * This exists to forbid default initializations.
156 	 *
157 	 * Using this type will result in an Error with Type.ConfigurationError.
158 	 */
159 	Invalid,
160 	/**
161 	 * SQLite backend
162 	 */
163 	SQLite,
164 	/**
165 	 * MySQL / MariaDB backend
166 	 */
167 	MySQL,
168 	/**
169 	 * Postgres backend
170 	 */
171 	Postgres,
172 }
173 
174 /**
175  * Configuration operation to connect to a database.
176  */
177 struct DBConnectOptions
178 {
179 	/// Specifies the driver that will be used.
180 	DBBackend backend;
181 	/// Name of the database, in case of `DatabaseBackend.SQLite` name of the file.
182 	FFIString name;
183 	/// Host to connect to. Not used in case of `DatabaseBackend.SQLite`.
184 	FFIString host;
185 	/// Port to connect to. Not used in case of `DatabaseBackend.SQLite`.
186 	ushort port;
187 	/// Username to authenticate with. Not used in case of `DatabaseBackend.SQLite`.
188 	FFIString user;
189 	/// Password to authenticate with. Not used in case of `DatabaseBackend.SQLite`.
190 	FFIString password;
191 	/// Minimal connections to initialize upfront. Must not be 0.
192 	uint minConnections = 1;
193 	/// Maximum connections that allowed to be created. Must not be 0.
194 	uint maxConnections = 8;
195 }
196 
197 /// Type-safe alias for different handles to void*, to avoid using them in wrong
198 /// functions accidentally. Try not to use the init value, it's simply a null
199 /// pointer.
200 enum DBHandle : void* { init }
201 /// ditto
202 enum DBTransactionHandle : void* { init }
203 /// ditto
204 enum DBRowHandle : void* { init }
205 /// ditto
206 enum DBRowListHandle : void* { init }
207 /// ditto
208 enum DBStreamHandle : void* { init }
209 
210 /// Represents a (sub-)tree of one or more condition parts.
211 struct FFICondition
212 {
213 	/// tagged union type
214 	enum Type
215 	{
216 		/// A list of [Condition]s, that get expanded to "{} AND {} ..."
217 		Conjunction,
218 		/// A list of [Condition]s, that get expanded to "{} OR {} ..."
219 		Disjunction,
220 		/// Representation of a unary condition.
221 		UnaryCondition,
222 		/// Representation of a binary condition.
223 		BinaryCondition,
224 		/// Representation of a ternary condition.
225 		TernaryCondition,
226 		/// Representation of a value.
227 		Value,
228 	}
229 	/// ditto
230 	Type type;
231 
232 	union
233 	{
234 		/// Correpsonding value for Type.Conjunction
235 		FFIArray!FFICondition conjunction;
236 		/// Correpsonding value for Type.Disjunction
237 		FFIArray!FFICondition disjunction;
238 		/// Correpsonding value for Type.UnaryCondition
239 		FFIUnaryCondition unaryCondition;
240 		/// Correpsonding value for Type.BinaryCondition
241 		FFIBinaryCondition binaryCondition;
242 		/// Correpsonding value for Type.TernaryCondition
243 		FFITernaryCondition ternaryCondition;
244 		/// Correpsonding value for Type.Value
245 		FFIValue value;
246 	}
247 
248 	string toString() const @trusted pure
249 	{
250 		import std.algorithm;
251 		import std.array;
252 		import std.conv;
253 
254 		final switch (type)
255 		{
256 			case Type.Conjunction:
257 				return conjunction.data.map!(c => c.to!string).join(" AND ");
258 			case Type.Disjunction:
259 				return conjunction.data.map!(c => c.to!string).join(" OR ");
260 			case Type.UnaryCondition:
261 				return unaryCondition.toString;
262 			case Type.BinaryCondition:
263 				return binaryCondition.toString;
264 			case Type.TernaryCondition:
265 				return ternaryCondition.toString;
266 			case Type.Value:
267 				return value.toString;
268 		}
269 	}
270 }
271 
272 /// This condition subtype represents all available unary conditions.
273 /// (operations with a single operand)
274 struct FFIUnaryCondition
275 {
276 	/// tagged union type
277 	enum Type
278 	{
279 		/// Representation of "{} IS NULL" in SQL
280 		IsNull,
281 		/// Representation of "{} IS NOT NULL" in SQL
282 		IsNotNull,
283 		/// Representation of "EXISTS {}" in SQL
284 		Exists,
285 		/// Representation of "NOT EXISTS {}" in SQL
286 		NotExists,
287 		/// Representation of "NOT {}" in SQL
288 		Not
289 	}
290 	/// ditto
291 	Type type;
292 
293 	/// The operand for any unary condition on which to operate using the type.
294 	FFICondition* condition;
295 
296 	string toString() const @trusted pure
297 	{
298 		final switch (type)
299 		{
300 			case Type.IsNull:
301 				return condition.toString ~ " IS NULL";
302 			case Type.IsNotNull:
303 				return condition.toString ~ " IS NOT NULL";
304 			case Type.Exists:
305 				return condition.toString ~ " EXISTS";
306 			case Type.NotExists:
307 				return condition.toString ~ " NOT EXISTS";
308 			case Type.Not:
309 				return "NOT " ~ condition.toString;
310 		}
311 	}
312 }
313 
314 /// This condition subtype represents all available binary conditions.
315 /// (operations with two operands)
316 struct FFIBinaryCondition
317 {
318 	/// tagged union type
319 	enum Type
320 	{
321 		/// Representation of "{} = {}" in SQL
322 		Equals,
323 		/// Representation of "{} <> {}" in SQL
324 		NotEquals,
325 		/// Representation of "{} > {}" in SQL
326 		Greater,
327 		/// Representation of "{} >= {}" in SQL
328 		GreaterOrEquals,
329 		/// Representation of "{} < {}" in SQL
330 		Less,
331 		/// Representation of "{} <= {}" in SQL
332 		LessOrEquals,
333 		/// Representation of "{} LIKE {}" in SQL
334 		Like,
335 		/// Representation of "{} NOT LIKE {}" in SQL
336 		NotLike,
337 		/// Representation of "{} REGEXP {}" in SQL
338 		Regexp,
339 		/// Representation of "{} NOT REGEXP {}" in SQL
340 		NotRegexp,
341 		/// Representation of "{} IN {}" in SQL
342 		In,
343 		/// Representation of "{} NOT IN {}" in SQL
344 		NotIn,
345 	}
346 	/// ditto
347 	Type type;
348 
349 	/// The left-hand-side operand on which to operate based on the type.
350 	FFICondition* lhs;
351 	/// The right-hand-side operand on which to operate based on the type.
352 	FFICondition* rhs;
353 
354 	string toString() const @trusted pure
355 	{
356 		final switch (type)
357 		{
358 			case Type.Equals:
359 				return lhs.toString ~ " = " ~ rhs.toString;
360 			case Type.NotEquals:
361 				return lhs.toString ~ " != " ~ rhs.toString;
362 			case Type.Greater:
363 				return lhs.toString ~ " > " ~ rhs.toString;
364 			case Type.GreaterOrEquals:
365 				return lhs.toString ~ " >= " ~ rhs.toString;
366 			case Type.Less:
367 				return lhs.toString ~ " < " ~ rhs.toString;
368 			case Type.LessOrEquals:
369 				return lhs.toString ~ " <= " ~ rhs.toString;
370 			case Type.Like:
371 				return lhs.toString ~ " LIKE " ~ rhs.toString;
372 			case Type.NotLike:
373 				return lhs.toString ~ " NOT LIKE " ~ rhs.toString;
374 			case Type.Regexp:
375 				return lhs.toString ~ " REGEXP " ~ rhs.toString;
376 			case Type.NotRegexp:
377 				return lhs.toString ~ " NOT REGEXP " ~ rhs.toString;
378 			case Type.In:
379 				return lhs.toString ~ " IN " ~ rhs.toString;
380 			case Type.NotIn:
381 				return lhs.toString ~ " NOT IN " ~ rhs.toString;
382 		}
383 	}
384 }
385 
386 /// This condition subtype represents all available ternary conditions.
387 /// (operations with three operands)
388 struct FFITernaryCondition
389 {
390 	/// tagged union type
391 	enum Type
392 	{
393 		/// Representation of "{} BETWEEN {} AND {}" in SQL
394 		Between,
395 		/// Representation of "{} NOT BETWEEN {} AND {}" in SQL
396 		NotBetween
397 	}
398 	/// ditto
399 	Type type;
400 
401 	/// The first operand on which to operate based on the type.
402 	FFICondition* first;
403 	/// The second operand on which to operate based on the type.
404 	FFICondition* second;
405 	/// The third operand on which to operate based on the type.
406 	FFICondition* third;
407 
408 	string toString() const @trusted pure
409 	{
410 		final switch (type)
411 		{
412 			case Type.Between:
413 				return first.toString ~ " BETWEEN " ~ second.toString ~ " AND " ~ third.toString;
414 			case Type.NotBetween:
415 				return first.toString ~ " NOT BETWEEN " ~ second.toString ~ " AND " ~ third.toString;
416 		}
417 	}
418 }
419 
420 /// Represents a leaf node in a condition tree, effectively inserting a static
421 /// value like a string, identifier or number.
422 struct FFIValue
423 {
424 	/// tagged union type
425 	enum Type
426 	{
427 		/// This represents `NULL` in SQL.
428 		Null,
429 		/// Representation of an identifier.
430 		/// The value will not be escaped, so do not pass unchecked data to it.
431 		Identifier,
432 		/// Representation of a column.
433 		Column,
434 		/// The value represents a string, being escaped (e.g. quoted)
435 		String,
436 		/// The value represents a 64 bit signed integer
437 		I64,
438 		/// The value represents a 32 bit signed integer
439 		I32,
440 		/// The value represents a 16 bit signed integer
441 		I16,
442 		/// The value represents a boolean value (true or false)
443 		Bool,
444 		/// The value represents a 64 bit floating point value
445 		F64,
446 		/// The value represents a 32 bit floating point value
447 		F32,
448 		/// Binary representation
449 		Binary,
450 		/// Time of day representation
451 		NaiveTime,
452 		/// Date representation
453 		NaiveDate,
454 		/// Date and time representation without timezone
455 		NaiveDateTime,
456 	}
457 	/// ditto
458 	Type type;
459 
460 	union
461 	{
462 		/// Corresponds to Type.Identifier
463 		FFIString identifier;
464 		/// Corresponds to Type.Column
465 		FFIColumn column;
466 		/// Corresponds to Type.String
467 		FFIString str;
468 		/// Corresponds to Type.I64
469 		long i64;
470 		/// Corresponds to Type.I32
471 		int i32;
472 		/// Corresponds to Type.I16
473 		short i16;
474 		/// Corresponds to Type.Bool
475 		bool boolean;
476 		/// Corresponds to Type.F64
477 		double f64;
478 		/// Corresponds to Type.F32
479 		float f32;
480 		/// Corresponds to Type.Binary
481 		FFIArray!ubyte binary;
482 		/// Corresponds to Type.NaiveTime
483 		FFITime naiveTime;
484 		/// Corresponds to Type.NaiveDate
485 		FFIDate naiveDate;
486 		/// Corresponds to Type.NaiveDateTime
487 		FFIDateTime naiveDateTime;
488 	}
489 
490 	string toString() const @trusted pure
491 	{
492 		import std.conv : to;
493 
494 		final switch (type)
495 		{
496 			case Type.Null:
497 				return `FFIValue(null)`;
498 			case Type.Identifier:
499 				return `FFIValue(` ~ identifier.data.idup ~ ")";
500 			case Type.Column:
501 				return `FFIValue(` ~ column.to!string ~ ")";
502 			case Type.String:
503 				return `FFIValue("` ~ str.data.idup ~ `")`;
504 			case Type.I64:
505 				return `FFIValue(` ~ i64.to!string ~ ")";
506 			case Type.I32:
507 				return `FFIValue(` ~ i32.to!string ~ ")";
508 			case Type.I16:
509 				return `FFIValue(` ~ i16.to!string ~ ")";
510 			case Type.Bool:
511 				return `FFIValue(` ~ (boolean ? "true" : "false") ~ ")";
512 			case Type.F64:
513 				return `FFIValue(` ~ f64.to!string ~ ")";
514 			case Type.F32:
515 				return `FFIValue(` ~ f32.to!string ~ ")";
516 			case Type.Binary:
517 				return `FFIValue(` ~ binary.data.to!string ~ ")";
518 			case Type.NaiveTime:
519 				return `FFIValue(` ~ naiveTime.to!string ~ ")";
520 			case Type.NaiveDate:
521 				return `FFIValue(` ~ naiveDate.to!string ~ ")";
522 			case Type.NaiveDateTime:
523 				return `FFIValue(` ~ naiveDateTime.to!string ~ ")";
524 		}
525 	}
526 }
527 
528 /// Part of a table update, being one column with the new value.
529 struct FFIUpdate
530 {
531 	FFIString column;
532 	const FFIValue value;
533 }
534 
535 /// Representation of a time of the day.
536 struct FFITime {
537 	///
538 	uint hour, min, sec;
539 }
540 
541 /// Representation of any date
542 struct FFIDate {
543 	///
544 	int year;
545 	///
546 	uint month, day;
547 }
548 
549 /// Representation of a date and time (without timezone)
550 struct FFIDateTime {
551 	///
552 	int year;
553 	///
554 	uint month, day;
555 	///
556 	uint hour, min, sec;
557 }
558 
559 /**
560  * Error struct passed into rorm callbacks. Generally this may not escape the
561  * callback, so it must always be used with scope, unless otherwise documented.
562  *
563  * Usually it should not be neccessary to use this directly from user code.
564  */
565 struct RormError
566 {
567 	/**
568 	 * Representation of all error codes.
569 	 */
570 	enum Tag
571 	{
572 		/**
573 		 * Everything's fine, nothing to worry about. Other result data passed
574 		 * into callbacks, such as returned handles, may only be used and freed
575 		 * if there is no error.
576 		 */
577 		NoError,
578 		/**
579 		 * Runtime was destroyed or never created and can therefore not be
580 		 * accessed.
581 		 */
582 		MissingRuntimeError,
583 		/**
584 		 * An error occurred accessing the runtime.
585 		 */
586 		RuntimeError,
587 		/**
588 		 * String with invalid UTF8 content was passed into the function.
589 		 */
590 		InvalidStringError,
591 		/**
592 		 * Configuration error
593 		 */
594 		ConfigurationError,
595 		/**
596 		 * Database error
597 		 */
598 		DatabaseError,
599 		/**
600 		 * There are no rows left in the stream
601 		 */
602 		NoRowsLeftInStream,
603 		/**
604 		 * Column could not be converted in the given type
605 		 */
606 		ColumnDecodeError,
607 		/**
608 		 * Column was not found in row
609 		 */
610 		ColumnNotFoundError,
611 		/**
612 		 * The index in the row was out of bounds
613 		 */
614 		ColumnIndexOutOfBoundsError,
615 		/**
616 		 * The provided date could not be parsed
617 		 */
618 		InvalidDateError,
619 		/**
620 		 * The provided time could not be parsed
621 		 */
622 		InvalidTimeError,
623 		/**
624 		 * The provided datetime could not be parsed
625 		 */
626 		InvalidDateTimeError,
627 	}
628 	/// ditto
629 	Tag tag;
630 
631 	union {
632 		/// Error message for tag == Tag.RuntimeError
633 		FFIString runtime_error;
634 		/// Error message for tag == Tag.ConfigurationError
635 		FFIString configuration_error;
636 		/// Error message for tag == Tag.DatabaseError
637 		FFIString database_error;
638 	}
639 
640 	/**
641 	 * Returns false only when there is no error, otherwise true.
642 	 *
643 	 * Examples:
644 	 * ---
645 	 * void myCallback(Handle data, Error error) {
646 	 *     if (error) throw error.makeException;
647 	 *     // only start using `data` from this point on
648 	 * }
649 	 * ---
650 	 */
651 	bool opCast() const nothrow @nogc @safe
652 	{
653 		return tag != Tag.NoError;
654 	}
655 
656 	/// Makes a human readable exception that can be thrown or returns `null` if
657 	/// there is no error.
658 	Exception makeException(string suffix = null) const nothrow @safe
659 	{
660 		import std.conv : text;
661 		import std.utf : UTFException;
662 
663 		final switch (tag)
664 		{
665 			case Tag.NoError: return null;
666 			case Tag.MissingRuntimeError:
667 				return new Exception(
668 					"Runtime has not been created or has been destroyed, use `mixin SetupDormRuntime;` in your application code"
669 					~ suffix);
670 			case Tag.RuntimeError:
671 				return new Exception(
672 					text("A runtime error has occurred: ", (() @trusted => this.runtime_error.data)(), suffix));
673 			case Tag.InvalidStringError:
674 				return new UTFException(
675 					"an invalid string has been passed into a dorm function, perhaps corrupted memory? (submit a bug in this case)"
676 					~ suffix);
677 			case Tag.ConfigurationError:
678 				return new Exception(
679 					text("passed invalid configuration: ", (() @trusted => this.configuration_error.data)(), suffix));
680 			case Tag.DatabaseError:
681 				return new Exception(
682 					text("database error: ", (() @trusted => this.database_error.data)(), suffix));
683 			case Tag.NoRowsLeftInStream:
684 				return new Exception("There are no rows left in the stream"
685 					~ suffix);
686 			case Tag.ColumnDecodeError:
687 				return new Exception("Column could not be converted in the given type"
688 					~ suffix);
689 			case Tag.ColumnNotFoundError:
690 				return new Exception("Column was not found in row"
691 					~ suffix);
692 			case Tag.ColumnIndexOutOfBoundsError:
693 				return new Exception("The index in the row was out of bounds"
694 					~ suffix);
695 			case Tag.InvalidDateError:
696 				return new Exception("The provided date could not be parsed"
697 					~ suffix);
698 			case Tag.InvalidTimeError:
699 				return new Exception("The provided time could not be parsed"
700 					~ suffix);
701 			case Tag.InvalidDateTimeError:
702 				return new Exception("The provided datetime could not be parsed"
703 					~ suffix);
704 		}
705 	}
706 }
707 
708 // ------ Runtime management -------
709 
710 /**
711  * This function is used to initialize and start the async runtime.
712  *
713  * It is needed as rorm is completely written asynchronously.
714  *
715  * **Important**: Do not forget to stop the runtime using $(LREF rorm_runtime_shutdown)!
716  *
717  * For user code, use `mixin SetupDormRuntime;` instead.
718  *
719  * This function is called completely synchronously.
720  */
721 void rorm_runtime_start(RuntimeStartCallback callback, void* context);
722 /// ditto
723 alias RuntimeStartCallback = extern(C) void function(void* context, scope RormError);
724 
725 /**
726  * Shutdown the runtime.
727  *
728  * Returns:
729  * - If no runtime is currently existing, a `Error.Type.MissingRuntimeError` will be returned.
730  * - If the runtime could not be locked, a `Error.Type.RuntimeError` containing further information will be returned.
731  *
732  * Params:
733  *     timeoutMsecs = Specify the amount of time to wait in milliseconds.
734  *     callback = Callback to call when finished, only passing in error information.
735  *     context = context pointer to pass through as-is into the callback.
736  *
737  * This function is called completely synchronously.
738  */
739 void rorm_runtime_shutdown(ulong timeoutMsecs, RuntimeShutdownCallback callback, void* context);
740 /// ditto
741 alias RuntimeShutdownCallback = extern(C) void function(void* context, scope RormError);
742 
743 // ------ DB Management -------
744 
745 /**
746  * Connect to the database using the provided $(LREF DBConnectOptions).
747  *
748  * To free the handle, use [rorm_db_free].
749  *
750  * Params:
751  *     options = connection and behavior options for the DB connection handle
752  *     callback = Callback that's called when the connection is established
753  *         either successfully or unsuccessfully. The callback parameters are:
754  *       data = the context pointer as passed into the function call by the user.
755  *       handle = if error is NoError, a valid handle to a DB connection,
756  *         otherwise not a valid handle and should not be freed.
757  *       error = if not successful, error information what exactly happened.
758  *     context = context pointer to pass through as-is into the callback.
759  *
760  * This function is running in an asynchronous context.
761  */
762 void rorm_db_connect(DBConnectOptions options, DBConnectCallback callback, void* context);
763 /// ditto
764 alias DBConnectCallback = extern(C) void function(void* context, DBHandle handle, scope RormError error) nothrow;
765 
766 /**
767  * Closes all of the database connections and frees the handle. No database
768  * operations may be pending when calling this!
769  *
770  * Takes the pointer to the database instance. Must not be called with an
771  * invalid handle. (when the `error` field is set to anything other than NoError)
772  *
773  * **Important**: Do not call this function more than once!
774  *
775  * This function is called completely synchronously.
776  */
777 void rorm_db_free(DBHandle handle);
778 
779 // ------ Querying -------
780 
781 /**
782  * Allows limiting query response count and offsetting which row is the first row.
783  */
784 struct FFILimitClause
785 {
786 	/// Limit how many rows may be returned at most.
787 	ulong limit;
788 	/// Optionally specify after how many rows to start the selection.
789 	FFIOption!ulong offset;
790 }
791 
792 /// Contains a column name / reference that can be used in condition values.
793 struct FFIColumn
794 {
795 	/// Optionally specify which table this column refers to.
796 	FFIOption!FFIString tableName;
797 	/// The name of the column in the table.
798 	FFIString columnName;
799 
800 	this(FFIString columnName)
801 	{
802 		this.columnName = columnName;
803 	}
804 
805 	this(FFIString tableName, FFIString columnName)
806 	{
807 		this.tableName = tableName;
808 		this.columnName = columnName;
809 	}
810 
811 	string toString() const @trusted pure nothrow
812 	{
813 		import std.conv : text;
814 	
815 		if (tableName.isNull)
816 			return text("Column(", columnName.data, ")");
817 		else
818 			return text("Column(", tableName.raw_value.data, ".", columnName.data, ")");
819 	}
820 }
821 
822 /**
823  * Allows specifying SQL `table_name.column_name as select_alias` syntax in a
824  * DB-agnostic way.
825  */
826 struct FFIColumnSelector
827 {
828 @safe:
829 	/// Optionally define which table or join alias this column comes from.
830 	FFIOption!FFIString tableName;
831 	/// The column name to select.
832 	FFIString columnName;
833 	/// Optionally rename to a different output name than `column_name`.
834 	FFIOption!FFIString selectAlias;
835 
836 	this(FFIString columnName)
837 	{
838 		this.columnName = columnName;
839 	}
840 
841 	this(FFIString tableName, FFIString columnName)
842 	{
843 		this.tableName = tableName;
844 		this.columnName = columnName;
845 	}
846 
847 	this(FFIString tableName, FFIString columnName, FFIString selectAlias)
848 	{
849 		this.tableName = tableName;
850 		this.columnName = columnName;
851 		this.selectAlias = selectAlias;
852 	}
853 }
854 
855 /// For use with `FFIOrderByEntry`
856 enum FFIOrdering
857 {
858 	/// Ascending
859 	asc,
860 	/// Descending
861 	desc,
862 }
863 
864 /// Represents a single part of an `ORDER BY` clause in SQL.
865 struct FFIOrderByEntry
866 {
867 @safe:
868 	/// Specifies if this is ordered in ascending or descending order.
869 	FFIOrdering ordering;
870 	/// Specifies the table for the column.
871 	FFIOption!FFIString tableName;
872 	/// Specifies on which column to order on.
873 	FFIString columnName;
874 
875 	this(FFIOrdering ordering, FFIString columnName)
876 	{
877 		this.ordering = ordering;
878 		this.columnName = columnName;
879 	}
880 
881 	this(FFIOrdering ordering, FFIString tableName, FFIString columnName)
882 	{
883 		this.ordering = ordering;
884 		this.tableName = tableName;
885 		this.columnName = columnName;
886 	}
887 }
888 
889 /**
890  * This function queries the database given the provided parameters.
891  *
892  * Parameters:
893  *     handle = Reference to the Database, provided by $(LREF rorm_db_connect).
894  *     transaction = Mutable pointer to a transaction, can be null.
895  *     model = Name of the table to query.
896  *     columns = Array of columns to retrieve from the database.
897  *     joins = Array of joins to add to the query.
898  *     condition = Pointer to an $(LREF FFICondition).
899  *     orderBy = Allows to specify columns to order the result by.
900  *     limit = Optionally limit and offset which rows are returned.
901  *     callback = callback function. Takes the `context`, a row handle and an
902  *         error that must be checked first.
903  *     context = context pointer to pass through as-is into the callback.
904  *
905  * Important: - Make sure that `db`, `model`, `columns` and `condition` are allocated until the callback is executed.
906  *
907  * Differences between these methods:
908  * - `rorm_db_query_all` gets an array of rows, which all need to be processed inside the callback.
909  * - `rorm_db_query_one` gets the first row of the query, throwing if no rows are present.
910  * - `rorm_db_query_optional` gets the first row of the query, or null if there are none.
911  *
912  * This function is called completely synchronously.
913  */
914 void rorm_db_query_all(
915 	DBHandle handle,
916 	DBTransactionHandle transaction,
917 	FFIString model,
918 	FFIArray!FFIColumnSelector columns,
919 	FFIArray!FFIJoin joins,
920 	scope const(FFICondition)* condition,
921 	FFIArray!FFIOrderByEntry orderBy,
922 	FFIOption!FFILimitClause limit,
923 	DBQueryAllCallback callback,
924 	void* context);
925 /// ditto
926 void rorm_db_query_one(
927 	DBHandle handle,
928 	DBTransactionHandle transaction,
929 	FFIString model,
930 	FFIArray!FFIColumnSelector columns,
931 	FFIArray!FFIJoin joins,
932 	scope const(FFICondition)* condition,
933 	FFIArray!FFIOrderByEntry orderBy,
934 	FFIOption!ulong offset,
935 	DBQueryOneCallback callback,
936 	void* context);
937 /// ditto
938 void rorm_db_query_optional(
939 	DBHandle handle,
940 	DBTransactionHandle transaction,
941 	FFIString model,
942 	FFIArray!FFIColumnSelector columns,
943 	FFIArray!FFIJoin joins,
944 	scope const(FFICondition)* condition,
945 	FFIArray!FFIOrderByEntry orderBy,
946 	FFIOption!ulong offset,
947 	DBQueryOptionalCallback callback,
948 	void* context);
949 /// ditto
950 alias DBQueryOneCallback = extern(C) void function(void* context, scope DBRowHandle row, scope RormError);
951 /// ditto
952 alias DBQueryOptionalCallback = extern(C) void function(void* context, scope DBRowHandle row, scope RormError);
953 /// ditto
954 alias DBQueryAllCallback = extern(C) void function(void* context, scope FFIArray!DBRowHandle row, scope RormError);
955 
956 // ------ Querying (Streams) -------
957 
958 /**
959  * This function queries the database given the provided parameters.
960  *
961  * Parameters:
962  *     handle = Reference to the Database, provided by $(LREF rorm_db_connect).
963  *     transaction = Mutable pointer to a transaction, can be null.
964  *     model = Name of the table to query.
965  *     columns = Array of columns to retrieve from the database.
966  *     joins = Array of joins to add to the query.
967  *     condition = Pointer to an $(LREF FFICondition).
968  *     orderBy = Allows to specify columns to order the result by.
969  *     limit = Optionally limit and offset which rows are returned.
970  *     callback = callback function. Takes the `context`, a stream handle and an
971  *         error that must be checked first.
972  *     context = context pointer to pass through as-is into the callback.
973  *
974  * Important: - Make sure that `db`, `model`, `columns` and `condition` are allocated until the callback is executed.
975  *
976  * This function is called completely synchronously.
977  */
978 void rorm_db_query_stream(
979 	DBHandle handle,
980 	DBTransactionHandle transaction,
981 	FFIString model,
982 	FFIArray!FFIColumnSelector columns,
983 	FFIArray!FFIJoin joins,
984 	scope const(FFICondition)* condition,
985 	FFIArray!FFIOrderByEntry orderBy,
986 	FFIOption!FFILimitClause limit,
987 	DBQueryStreamCallback callback,
988 	void* context);
989 /// ditto
990 alias DBQueryStreamCallback = extern(C) void function(void* context, DBStreamHandle stream, scope RormError);
991 
992 /**
993  * Frees the stream given as parameter. The stream must be in a freeable state.
994  *
995  * **Important**: Do not call this function more than once or with an invalid
996  * stream!
997  *
998  * This function is called completely synchronously.
999  */
1000 void rorm_stream_free(DBStreamHandle handle);
1001 
1002 /**
1003  * Fetches the next row from the stream. Must not be called in parallel on the
1004  * same stream. Returns Error.NoRowsLeftInStream when the stream is finished.
1005  *
1006  * Params:
1007  *     stream = handle of a queried stream. (must have ownership)
1008  *     callback = called when a row is fetched, contains either an error that
1009  *         can be accessed within the callback or a row handle that can be moved
1010  *         out the callback, but must be freed with $(LREF rorm_row_free).
1011  *     context = context pointer to pass through as-is into the callback.
1012  *
1013  * This function is running in an asynchronous context.
1014  */
1015 void rorm_stream_get_row(DBStreamHandle stream, scope DBStreamGetRowCallback callback, void* context);
1016 /// ditto
1017 alias DBStreamGetRowCallback = extern(C) void function(void* context, DBRowHandle row, scope RormError) nothrow;
1018 
1019 /**
1020  * Frees the row handle given as parameter.
1021  *
1022  * **Important**: Do not call this function more than once or with an invalid
1023  * row handle!
1024  *
1025  * This function is called completely synchronously.
1026  */
1027 void rorm_row_free(DBRowHandle row);
1028 
1029 /**
1030 Params:
1031 	handle = row handle to read from
1032 	columnIndex = the column index (array index from the `columns` parameter in
1033 		the corresponding $(LREF rorm_db_query_stream) call)
1034 
1035 Returns:
1036 	The extracted value from the row at the given column index. FFIArray and
1037 	FFIString values must be copied if using them outside the lifetime of the
1038 	row.
1039 */
1040 short rorm_row_get_i16(DBRowHandle handle, FFIString column, ref RormError ref_error);
1041 int rorm_row_get_i32(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1042 long rorm_row_get_i64(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1043 float rorm_row_get_f32(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1044 double rorm_row_get_f64(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1045 bool rorm_row_get_bool(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1046 FFIString rorm_row_get_str(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1047 FFIArray!(const ubyte) rorm_row_get_binary(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1048 FFIDate rorm_row_get_date(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1049 FFIDateTime rorm_row_get_datetime(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1050 FFITime rorm_row_get_time(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1051 FFIOption!short rorm_row_get_null_i16(DBRowHandle handle, FFIString column, ref RormError ref_error);
1052 FFIOption!int rorm_row_get_null_i32(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1053 FFIOption!long rorm_row_get_null_i64(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1054 FFIOption!float rorm_row_get_null_f32(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1055 FFIOption!double rorm_row_get_null_f64(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1056 FFIOption!bool rorm_row_get_null_bool(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1057 FFIOption!FFIString rorm_row_get_null_str(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1058 FFIOption!(FFIArray!(const ubyte)) rorm_row_get_null_binary(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1059 FFIOption!FFIDate rorm_row_get_null_date(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1060 FFIOption!FFIDateTime rorm_row_get_null_datetime(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1061 FFIOption!FFITime rorm_row_get_null_time(DBRowHandle handle, FFIString column, ref RormError ref_error); /// ditto
1062 
1063 // ------ Insertion -------
1064 
1065 /**
1066  * This function inserts one row (rorm_db_insert) or multiple rows
1067  * (rorm_db_inset_bulk) into the given the database with the given values.
1068  *
1069  * Params:
1070  *     db = Reference to the Database, provided by $(LREF rorm_db_connect).
1071  *     transaction = Mutable pointer to a transaction, can be null.
1072  *     model = Name of the table to insert into.
1073  *     columns = Array of columns, corresponding to `row` values to insert into the database.
1074  *     row = List of values to insert, indexes matching the `columns` parameter.
1075  *     rows = List of list of values to insert, each row's indexes matching the `columns` parameter.
1076  *     callback = Callback to call when finished, only passing in error information.
1077  *     context = context pointer to pass through as-is into the callback.
1078  *
1079  * **Important**: - Make sure that `db`, `model` and `condition` are allocated until the callback is executed.
1080  *
1081  * This function is called from an asynchronous context.
1082  */
1083 void rorm_db_insert(
1084 	DBHandle db,
1085 	DBTransactionHandle transaction,
1086 	FFIString model,
1087 	FFIArray!FFIString columns,
1088 	FFIArray!FFIValue row,
1089 	DBInsertCallback callback,
1090 	void* context
1091 );
1092 /// ditto
1093 void rorm_db_insert_bulk(
1094 	DBHandle db,
1095 	DBTransactionHandle transaction,
1096 	FFIString model,
1097 	FFIArray!FFIString columns,
1098 	FFIArray!(FFIArray!FFIValue) rows,
1099 	DBInsertCallback callback,
1100 	void* context
1101 );
1102 /// ditto
1103 alias DBInsertCallback = extern(C) void function(void* context, scope RormError);
1104 
1105 // ------ Deletion -------
1106 
1107 /**
1108  * This function deletes rows from the database based on the given conditions.
1109  *
1110  * Params:
1111  *     db = Reference to the Database, provided by $(LREF rorm_db_connect).
1112  *     transaction = Mutable pointer to a transaction, can be null.
1113  *     model = Name of the table to query.
1114  *     condition = Query / condition to filter what to delete on.
1115  *     callback = Callback to call when finished, only passing in error information.
1116  *     context = context pointer to pass through as-is into the callback.
1117  *
1118  * **Important**: - Make sure that `db`, `model` and `condition` are allocated until the callback is executed.
1119  *
1120  * This function is called from an asynchronous context.
1121  */
1122 void rorm_db_delete(
1123 	DBHandle db,
1124 	DBTransactionHandle transaction,
1125 	FFIString model,
1126 	scope const(FFICondition)* condition,
1127 	DBDeleteCallback callback,
1128 	void* context
1129 );
1130 /// ditto
1131 alias DBDeleteCallback = extern(C) void function(void* context, ulong rowsAffected, scope RormError);
1132 
1133 /**
1134  * This function executes a raw SQL statement.
1135  *
1136  * Statements are executed as prepared statements, if possible.
1137  *
1138  * To define placeholders, use `?` in SQLite and MySQL and $1, $n in Postgres.
1139  * The corresponding parameters are bound in order to the query.
1140  *
1141  * The number of placeholder must match with the number of provided bind
1142  * parameters.
1143  *
1144  * To include the statement in a transaction specify `transaction` as a valid
1145  * Transaction. As the Transaction needs to be mutable, it is important to not
1146  * use the Transaction anywhere else until the callback is finished.
1147  *
1148  * If the statement should be executed **not** in a Transaction, specify a null
1149  * pointer.
1150  *
1151  * Params:
1152  *     db = Reference to the Database, provided by [rorm_db_connect].
1153  *     transaction = Mutable pointer to a Transaction. Can be a null pointer
1154  *          to ignore this parameter.
1155  *     query_string = SQL statement to execute.
1156  *     bind_params = Optional slice of FFIValues to bind to the query.
1157  *     callback = callback function. Takes the `context`, a pointer to a slice
1158  *          of rows and an [Error].
1159  *     context = Pass through void pointer.
1160  *
1161  * **Important**:
1162  * Make sure `db`, `query_string` and `bind_params` are allocated until the
1163  * callback was executed.
1164  *
1165  * The FFIArray returned in the callback is freed after the callback has ended.
1166  *
1167  * This function is called from an asynchronous context.
1168  */
1169 void rorm_db_raw_sql(
1170 	const DBHandle db,
1171 	DBTransactionHandle transaction,
1172 	FFIString query_string,
1173 	FFIArray!FFIValue bind_params,
1174 	DBRawSQLCallback callback,
1175 	void* context);
1176 /// ditto
1177 alias DBRawSQLCallback = extern(C) void function(void* context, scope FFIArray!DBRowHandle rows, scope RormError);
1178 
1179 /**
1180  * This function updates rows in the database.
1181  *
1182  * Params: 
1183  *     db = Reference to the Database, provided by [rorm_db_connect]. 
1184  *     transaction = Mutable pointer to a Transaction. Can be a null pointer to ignore this parameter. 
1185  *     model = Name of the table to query. 
1186  *     updates = List of [FFIUpdate] to apply. 
1187  *     condition = Pointer to a [Condition]. 
1188  *     callback = callback function. Takes the `context`, the rows affected and an [Error]. 
1189  *     context = Pass through void pointer.
1190  *
1191  * **Important**: - Make sure that `db`, `model`, `updates` and `condition` are
1192  * allocated until the callback is executed.
1193  *
1194  * This function is called from an asynchronous context.
1195  */
1196 void rorm_db_update(
1197 	const DBHandle db,
1198 	DBTransactionHandle transaction,
1199 	FFIString model,
1200 	FFIArray!FFIUpdate updates,
1201 	const(FFICondition)* condition,
1202 	DBUpdateCallback callback,
1203 	void* context);
1204 /// ditto
1205 alias DBUpdateCallback = extern(C) void function(void* context, ulong rowsAffected, scope RormError);
1206 
1207 /**
1208  * Starts a transaction on the current database connection.
1209  *
1210  * Params:
1211  *     db = Reference to the Database, provided by [rorm_db_connect]. 
1212  *     callback = callback function. Takes the `context`, a pointer to a transaction and an [Error]. 
1213  *     context = Pass through void pointer.
1214  *
1215  * **Important**: Rust does not manage the memory of the transaction. To properly free it, use [rorm_transaction_free], [rorm_transaction_commit] or [rorm_transaction_abort].
1216  *
1217  * This function is called from an asynchronous context.
1218  */
1219 void rorm_db_start_transaction(
1220 	const DBHandle db,
1221 	DBStartTransactionCallback callback,
1222 	void* context);
1223 /// ditto
1224 alias DBStartTransactionCallback = extern(C) void function(void* context, DBTransactionHandle handle, scope RormError);
1225 
1226 /**
1227  * Commits a transaction.
1228  *
1229  * All previous operations will be applied to the database.
1230  *
1231  * Params:
1232  *     transaction = Pointer to a valid transaction, provided by
1233  *         [rorm_db_start_transaction].
1234  *     callback = callback function. Takes the `context` and an [Error].
1235  *     context = Pass through void pointer.
1236  *
1237  * **Important**: Rust takes ownership of `transaction` and frees it after using.
1238  * Don't use it anywhere else after calling this function!
1239  *
1240  * This function is called from an asynchronous context.
1241  */
1242 void rorm_transaction_commit(
1243 	DBTransactionHandle transaction,
1244 	DBTransactionCommitCallback callback,
1245 	void* context);
1246 /// ditto
1247 alias DBTransactionCommitCallback = extern(C) void function(void* context, scope RormError);
1248 
1249 /**
1250  * Rollback a transaction and abort it.
1251  *
1252  * All previous operations will be discarded.
1253  *
1254  * Params:
1255  *     transaction = Pointer to a valid transaction, provided by
1256  *         [rorm_db_start_transaction].
1257  *     callback = callback function. Takes the `context` and an [Error].
1258  *     context = Pass through void pointer.
1259  *
1260  * **Important**: Rust takes ownership of `transaction` and frees it after using.
1261  * Don't use it anywhere else after calling this function!
1262  *
1263  * This function is called from an asynchronous context.
1264  */
1265 void rorm_transaction_rollback(
1266 	DBTransactionHandle transaction,
1267 	DBTransactionRollbackCallback callback,
1268 	void* context);
1269 /// ditto
1270 alias DBTransactionRollbackCallback = extern(C) void function(void* context, scope RormError);
1271 
1272 
1273 unittest
1274 {
1275 	import dorm.lib.util;
1276 
1277 	sync_call!rorm_runtime_start();
1278 	scope (exit)
1279 		sync_call!rorm_runtime_shutdown(1000);
1280 
1281 	DBConnectOptions options = {
1282 		backend: DBBackend.SQLite,
1283 		name: "test_read.sqlite3".ffi,
1284 	};
1285 	scope dbHandleAsync = FreeableAsyncResult!DBHandle.make;
1286 	rorm_db_connect(options, dbHandleAsync.callback.expand);
1287 	scope dbHandle = dbHandleAsync.result;
1288 	scope (exit)
1289 		rorm_db_free(dbHandle);
1290 
1291 	scope stream = sync_call!rorm_db_query_stream(
1292 		dbHandle, // db handle
1293 		null, // tx
1294 		"foo".ffi, // table name
1295 		[
1296 			FFIColumnSelector("name".ffi),
1297 			FFIColumnSelector("notes".ffi)
1298 		].ffi, // columns
1299 		FFIArray!FFIJoin.init, // joins
1300 		null, // condition
1301 		FFIArray!FFIOrderByEntry.init,
1302 		FFIOption!FFILimitClause.init, // limit, offset
1303 	);
1304 	scope (exit)
1305 		rorm_stream_free(stream);
1306 
1307 	import std.stdio;
1308 	writeln("| Name \t| Notes \t|");
1309 
1310 	Exception e;
1311 	while (true)
1312 	{
1313 		scope rowHandleAsync = RowHandleState(FreeableAsyncResult!DBRowHandle.make);
1314 		rorm_stream_get_row(stream, &rowCallback, cast(void*)&rowHandleAsync);
1315 		scope rowHandle = rowHandleAsync.result;
1316 		if (rowHandleAsync.done)
1317 			break;
1318 		scope (exit)
1319 			rorm_row_free(rowHandle);
1320 
1321 		RormError rowError;
1322 		auto name = rorm_row_get_str(rowHandle, "name".ffi, rowError);
1323 		auto notes = rorm_row_get_null_str(rowHandle, "notes".ffi, rowError).embedNull;
1324 		if (rowError)
1325 		{
1326 			e = rowError.makeException;
1327 			break;
1328 		}
1329 		writefln("| %s\t| %s\t|", name[], notes[]);
1330 	}
1331 
1332 	// while (!rorm_stream_empty(stream))
1333 	// {
1334 	// 	async_call!rorm_stream_next(stream, (rowResult) {
1335 	// 		writeln("Hello ", rorm_row_get_data_varchar(rowResult.expect, FFIString)column, ref RormError ref_error
1336 	// 	}).wait;
1337 	// }
1338 }
1339 
1340 version (unittest)
1341 {
1342 	import dorm.lib.util;
1343 
1344 	private struct RowHandleState
1345 	{
1346 		FreeableAsyncResult!DBRowHandle impl;
1347 		alias impl this;
1348 		bool done;
1349 	}
1350 
1351 	extern(C) private static void rowCallback(void* data, DBRowHandle result, scope RormError error) nothrow
1352 	{
1353 		auto res = cast(RowHandleState*)data;
1354 		if (error.tag == RormError.Tag.NoRowsLeftInStream)
1355 			res.done = true;
1356 		else if (error)
1357 			res.error = error.makeException;
1358 		else
1359 			res.raw_result = result;
1360 		res.awaiter.set();
1361 	}
1362 }
1363 
1364 /**
1365  * Representation of a join type.
1366  */
1367 enum FFIJoinType
1368 {
1369 	/**
1370 	* Normal join operation.
1371 	*
1372 	* Equivalent to INNER JOIN
1373 	*/
1374 	join,
1375 	/**
1376 	* Cartesian product of the tables
1377 	*/
1378 	crossJoin,
1379 	/**
1380 	* Given:
1381 	* T1 LEFT JOIN T2 ON ..
1382 	*
1383 	* First, an inner join is performed.
1384 	* Then, for each row in T1 that does not satisfy the join condition with any row in T2,
1385 	* a joined row is added with null values in columns of T2.
1386 	*/
1387 	leftJoin,
1388 	/**
1389 	* Given:
1390 	* T1 RIGHT JOIN T2 ON ..
1391 	*
1392 	* First, an inner join is performed.
1393 	* Then, for each row in T2 that does not satisfy the join condition with any row in T1,
1394 	* a joined row is added with null values in columns of T1.
1395 	*/
1396 	rightJoin,
1397 	/**
1398 	* Given:
1399 	* T1 FULL JOIN T2 ON ..
1400 	*
1401 	* First, an inner join is performed.
1402 	* Then, for each row in T2 that does not satisfy the join condition with any row in T1,
1403 	* a joined row is added with null values in columns of T1.
1404 	* Also, for each row in T1 that does not satisfy the join condition with any row in T2,
1405 	* a joined row is added with null values in columns of T2.
1406 	*/
1407 	fullJoin,
1408 }
1409 
1410 
1411 /**
1412  * FFI representation of a Join expression.
1413  */
1414 struct FFIJoin
1415 {
1416 	/**
1417 	 * Type of the join operation
1418 	 */
1419 	FFIJoinType joinType;
1420 	/**
1421 	 * Name of the join table
1422 	 */
1423 	FFIString tableName;
1424 	/**
1425 	 * Alias for the join table
1426 	 */
1427 	FFIString joinAlias;
1428 	/**
1429 	 * Condition to apply the join on
1430 	 */
1431 	const(FFICondition)* joinCondition;
1432 
1433 	string toString() const @trusted pure
1434 	{
1435 		import std.conv;
1436 
1437 		return "FFIJoin("
1438 			~ joinType.to!string ~ ", "
1439 			~ tableName.data.idup ~ ", "
1440 			~ joinAlias.data.idup ~ ", "
1441 			~ (joinCondition ? joinCondition.toString : "(no condition)") ~ ")";
1442 	}
1443 }