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 }