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 }