Skip to content

Commit 5c54321

Browse files
committed
implement NullableModelRef
1 parent 62bf06e commit 5c54321

File tree

5 files changed

+105
-11
lines changed

5 files changed

+105
-11
lines changed

integration-tests/one-to-many/source/app.d

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import std.datetime.systime;
88
import std.exception;
99
import std.range;
1010
import std.stdio;
11+
import std.typecons;
1112

1213
import dorm.api.db;
1314
import dorm.declarative.conversion;
15+
import dorm.types.relations;
1416

1517
mixin SetupDormRuntime;
1618

@@ -32,10 +34,18 @@ void main()
3234
db.insert(user2);
3335

3436
Toot toot = new Toot();
37+
toot.id = 1;
3538
toot.message = "Hello world!";
3639
toot.author = user;
3740
db.insert(toot);
3841

42+
Toot toot2 = new Toot();
43+
toot2.id = 2;
44+
toot2.message = "Hello world!";
45+
toot2.author = user;
46+
toot2.dmTo = ModelRef!User(user2);
47+
db.insert(toot2);
48+
3949
Comment.Fields comment;
4050
comment.replyTo = toot;
4151
comment.message = "Very cool!";
@@ -48,6 +58,13 @@ void main()
4858
comment2.author.foreignKey = user2.id;
4959
db.insert(comment2);
5060

61+
auto allToots = db.select!Toot
62+
.array;
63+
assert(allToots.length == 2);
64+
assert(allToots[0].dmTo.isNull);
65+
assert(!allToots[1].dmTo.isNull);
66+
assert(allToots[1].dmTo.get.foreignKey == user2.id);
67+
5168
auto allComments = db.select!Comment
5269
.array;
5370
assert(allComments.length == 2);

integration-tests/one-to-many/source/models.d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Toot : Model
2222
@autoCreateTime
2323
SysTime createdAt;
2424
ModelRef!User author;
25+
NullableModelRef!User dmTo;
2526
}
2627

2728
@DormPatch!User

source/dorm/api/db.d

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,22 @@ import dorm.types;
99
import ffi = dorm.lib.ffi;
1010

1111
import std.algorithm : any, move;
12-
import std.range : chain;
1312
import std.conv : text, to;
1413
import std.datetime : Clock, Date, DateTime, DateTimeException, SysTime, TimeOfDay, UTC;
1514
import std.meta;
15+
import std.range : chain;
1616
import std.range.primitives;
1717
import std.traits;
1818
import std.typecons : Nullable;
1919

20-
import mir.serde;
2120
import mir.algebraic;
21+
import mir.serde;
2222

2323
import core.attribute;
2424
import core.time;
2525

26-
public import dorm.types : DormPatch;
2726
public import dorm.lib.ffi : DBBackend;
27+
public import dorm.types : DormPatch;
2828

2929
public import dorm.api.condition;
3030

@@ -58,7 +58,7 @@ struct BareConfiguration
5858
T parseTomlConfig(T)(string filename)
5959
{
6060
import mir.toml;
61-
import std.file : readText, exists;
61+
import std.file : exists, readText;
6262

6363
if (!exists(filename))
6464
throw new DormException("TOML Configuration file '" ~ filename
@@ -342,7 +342,7 @@ struct DormDB
342342
return RawSQLIterator(&this, null, queryString, bindParams);
343343
}
344344

345-
/**
345+
/**
346346
* Using a `ModelRef` as an argument this queries for the value referenced
347347
* to by the foreign key. Assigns the result into the `ModelRef` field, so
348348
* `field.populated` can be called by the user afterwards to work on the
@@ -723,12 +723,15 @@ struct DormTransaction
723723
return remove!T.single(instance);
724724
}
725725

726-
/**
726+
/**
727727
* Using a `ModelRef` as an argument this queries for the value referenced
728728
* to by the foreign key. Assigns the result into the `ModelRef` field, so
729729
* `field.populated` can be called by the user afterwards to work on the
730730
* data that was queried with `populate`.
731731
*
732+
* Also supports `Nullable!ModelRef`, ignoring it if it is null, otherwise
733+
* unwrapping it.
734+
*
732735
* Params:
733736
* field = a reference to a `ModelRef` variable or multiple by reference.
734737
*/
@@ -752,6 +755,15 @@ if (isModelRef!T)
752755
.findOne();
753756
}
754757

758+
private void populateImpl(DB, T)(ref DB db, ref Nullable!T field) @safe
759+
if (isModelRef!T)
760+
{
761+
if (!field.isNull)
762+
field = db.select!(T.TSelect)
763+
.condition(c => __traits(getMember, c, T.primaryKeySourceName).equals(field.get.foreignKey))
764+
.findOne();
765+
}
766+
755767
private void populateImpl(DB, T)(ref DB db, T*[] fields) @safe
756768
if (isModelRef!T)
757769
{
@@ -768,6 +780,24 @@ if (isModelRef!T)
768780
})();
769781
}
770782

783+
private void populateImpl(DB, T)(ref DB db, Nullable!T*[] fields) @safe
784+
if (isModelRef!T)
785+
{
786+
import std.algorithm : filter, map;
787+
788+
auto q = db.select!(T.TSelect)
789+
.condition(c => __traits(getMember, c, T.primaryKeySourceName).among(fields
790+
.filter!"!a.isNull"
791+
.map!"a.get.foreignKey"));
792+
793+
(() @trusted {
794+
foreach (row; q.stream())
795+
foreach (field; fields)
796+
if (!field.foreignKey.isNull && field.foreignKey.get == mixin("row.", T.primaryKeySourceName))
797+
field.opAssign(row);
798+
})();
799+
}
800+
771801
private string makePatchAccessPrefix(Patch, DB)()
772802
{
773803
string ret;
@@ -2821,8 +2851,8 @@ private T extractField(alias field, T, string errInfo)(
28212851
scope const(char)[] columnPrefix
28222852
) @trusted
28232853
{
2824-
import std.conv;
28252854
import dorm.declarative;
2855+
import std.conv;
28262856

28272857
scope columnName = ffi.ffi(columnPrefix.length
28282858
? columnPrefix ~ field.columnName
@@ -3073,8 +3103,8 @@ mixin template SetupDormRuntime(alias timeout = 10.seconds)
30733103

30743104
shared static this() @trusted
30753105
{
3076-
import dorm.lib.util : sync_call;
30773106
import dorm.lib.ffi : rorm_runtime_start;
3107+
import dorm.lib.util : sync_call;
30783108

30793109
sync_call!(rorm_runtime_start)();
30803110
_initializedDormRuntime = true;
@@ -3083,8 +3113,8 @@ mixin template SetupDormRuntime(alias timeout = 10.seconds)
30833113
shared static ~this() @trusted
30843114
{
30853115
import core.time : Duration;
3086-
import dorm.lib.util;
30873116
import dorm.lib.ffi : rorm_runtime_shutdown;
3117+
import dorm.lib.util;
30883118

30893119
if (_initializedDormRuntime)
30903120
{

source/dorm/declarative/conversion.d

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,51 @@ unittest
10301030
]);
10311031
}
10321032

1033+
unittest
1034+
{
1035+
struct Mod
1036+
{
1037+
class User : Model
1038+
{
1039+
@maxLength(255) @primaryKey
1040+
string username;
1041+
}
1042+
1043+
class Toot : Model
1044+
{
1045+
@Id long id;
1046+
1047+
NullableModelRef!(User.username) author;
1048+
}
1049+
}
1050+
1051+
static assert(DormForeignKeys!(Mod.Toot).length == 1);
1052+
static assert(DormForeignKeys!(Mod.Toot)[0].columnName == "author");
1053+
1054+
auto mod = processModelsToDeclarations!Mod;
1055+
assert(mod.models.length == 2);
1056+
auto m = mod.models[0];
1057+
assert(m.fields.length == 1);
1058+
1059+
assert(m.fields[0].columnName == "username");
1060+
assert(m.fields[0].annotations == [
1061+
DBAnnotation(maxLength(255)),
1062+
DBAnnotation(AnnotationFlag.primaryKey)
1063+
]);
1064+
1065+
m = mod.models[1];
1066+
assert(m.fields.length == 2);
1067+
1068+
assert(m.fields[1].columnName == "author");
1069+
assert(m.fields[1].annotations == [
1070+
DBAnnotation(maxLength(255)),
1071+
DBAnnotation(ForeignKeyImpl(
1072+
"user", "username",
1073+
restrict, restrict
1074+
))
1075+
]);
1076+
}
1077+
10331078
unittest
10341079
{
10351080
// cyclic ref

source/dorm/types/relations.d

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,6 @@ version(none) static struct ManyToManyField(alias idOrModel)
9292
* supporting proactively fetched joined data from the database. Trying to
9393
* access the populated data without having it populated will result in a
9494
* program crash through `assert(false)`.
95-
*
96-
* Bugs: `db.populate(ModelRef)` is not yet implemented
9795
*/
9896
static template ModelRef(alias idOrPatch)
9997
{
@@ -199,6 +197,9 @@ static struct ModelRefImpl(alias id, _TModel, _TSelect)
199197
}
200198
}
201199

200+
/// Alias for `Nullable!(ModelRef!T)`, indicating a reference which can be null.
201+
alias NullableModelRef(alias T) = Nullable!(ModelRef!T);
202+
202203
static template ModelRefOf(alias field)
203204
{
204205
alias T = typeof(field);

0 commit comments

Comments
 (0)