1 module indexedRelation;
2 
3 import std.functional: unaryFun;
4 import std.typecons: Nullable, Tuple, nullable;
5 
6 version (unittest) {
7     import std.exception: assertThrown;
8 }
9 
10 /// A bag of records with efficient lookup on multiple independent keys.
11 public class IndexedRelation(Record, Indices...) {
12     private Record[] records;
13     private Tuple!Indices indices;
14 
15     public this() {
16         static foreach (i; 0 .. Indices.length) {
17             indices[i] = new Indices[i];
18         }
19     }
20 
21     public void insert(Record record) {
22         records ~= [record];
23         static foreach (i; 0 .. Indices.length) {
24             indices[i].insert(record);
25         }
26     }
27 
28     public auto lookup(int i)(Indices[i].Key key) {
29         return indices[i].lookup(key);
30     }
31 }
32 
33 /// An index based on a hash table. Allows multiple records with the same key.
34 public class HashTableIndex(Record, string KeyString) {
35     private alias KeyFunction = unaryFun!KeyString;
36     public alias Key = typeof(KeyFunction(Record.init));
37 
38     private Record[][Key] records;
39 
40     public void insert(Record record) {
41         records[KeyFunction(record)] ~= [record];
42     }
43 
44     public Record[] lookup(Key key) {
45         if (key !in records) return [];
46         return records[key];
47     }
48 }
49 
50 /// An index based on a hash table. Prohibits multiple records with the same
51 /// key.
52 public class UniqueHashTableIndex(Record, string KeyString) {
53     private alias KeyFunction = unaryFun!KeyString;
54     public alias Key = typeof(KeyFunction(Record.init));
55 
56     private Record[Key] records;
57 
58     public void insert(Record record) {
59         auto key = KeyFunction(record);
60         if (key in records) {
61             throw new Exception("Key already present in unique index");
62         }
63         records[key] = record;
64     }
65 
66     public Nullable!Record lookup(Key key) {
67         if (key !in records) return Nullable!Record.init;
68         return records[key].nullable;
69     }
70 }
71 
72 unittest {
73     struct Employee {
74         public ulong id;
75         public string firstName;
76         public string lastName;
77         public ulong salary;
78     }
79 
80     auto employee1 = Employee(1, "John", "Backus", 1000);
81     auto employee2 = Employee(2, "Rob", "Pike", 1100);
82     auto employee3 = Employee(3, "Walter", "Bright", 1200);
83 
84     auto employees = new IndexedRelation!(
85         Employee,
86         UniqueHashTableIndex!(Employee, "a.id"),
87         HashTableIndex!(Employee, "a.firstName"),
88         HashTableIndex!(Employee, "a.firstName.toUpper")
89     );
90 
91     employees.insert(employee1);
92     employees.insert(employee2);
93     employees.insert(employee3);
94     assertThrown(employees.insert(employee1));
95 
96     assert(employees.lookup!0(1) == employee1.nullable);
97     assert(employees.lookup!1("Rob") == [employee2]);
98     assert(employees.lookup!2("WALTER") == [employee3]);
99 
100     assert(employees.lookup!0(4) == Nullable!Employee.init);
101     assert(employees.lookup!1("Guido") == []);
102     assert(employees.lookup!2("BJARNE") == []);
103 }