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