1 
2 
3 module mars.defs;
4 
5 import std.meta;
6 import std.traits;
7 import std.algorithm;
8 import std.array;
9 import std.meta;
10 import std.typecons;
11 import std.datetime;
12 
13 import mars.starwars;
14 
15 /+
16 struct Schema2 
17 {
18     this(string name, string[] tt) {
19         this.name = name;
20         foreach(t; tt){
21             this.tables ~= Table2(&this, t);
22         }
23     }
24 
25     string name;
26     Table2[] tables;
27 }
28 struct Table2 {
29     Schema2* schema;
30     string name;
31 }
32 enum s1 = Schema2("s1", ["t1", "t2"]);
33 static assert(s1.tables.length == 2);
34 static assert(s1.tables[0].schema is s1);
35 
36 auto schema(string name){ return Schema2(name, []); }
37 auto table(Schema2 schema, string name){
38     string[] tt; foreach(t; schema.tables){ tt ~= t.name; }
39     return Schema2(schema.name, schema.tables.map!"a.name"().array ~ name);
40 }
41 
42 enum ss = schema("foo");
43 enum s2 = schema("foo").table("bar");
44 +/
45 
46 struct Schema {
47     string name;
48     immutable(Table)[] tables;
49 
50     immutable(Table) tableNamed(string name) const @safe {
51         return tables.find!((t) => t.name == name)[0];
52     }
53 }
54 /+
55 Schema table(immutable(Schema) schema, string name, immutable(Col)[] columns, immutable(size_t)[] primaryKey, immutable(Reference)[] references, size_t index){
56     auto s = Schema(schema.name, schema.tables ~ immutable(Table)(name, columns, primaryKey, references, index));
57     return s;
58 }
59 unittest {
60     enum s = Schema("foo").table("bar", [immutable(Col)("c1", Type.integer)], [], [], 0);
61     static assert( s.tables.length == 1 );
62     enum s2 = Schema("foo")
63         .table("bar", [immutable(Col)("c1", Type.integer)], [], [], 0)
64         .table("bar", [immutable(Col)("c1", Type.integer)], [], [], 1)
65         ;
66     static assert( s.tables.length == 2 );
67 }
68 +/
69 
70 struct Sync {
71     string mars_who, mars_what, mars_when;
72 }
73 
74 enum SyncCols = [
75     immutable(Col)("mars_who", Type.text, false), 
76     immutable(Col)("mars_what", Type.text, false), 
77     immutable(Col)("mars_when", Type.text, false),
78 ];
79 
80 struct Table { 
81     string name;
82     immutable(Col)[] columns;
83     size_t[] primaryKey;
84     Reference[] references;
85     size_t index; /// the unique index of the table in the system.
86     bool durable = true;
87     bool decorateRows = false;
88     bool cacheRows = true;
89     bool journal = false;
90 
91     immutable(Schema)* schema;
92 
93     @property immutable(Col)[] decoratedCols() const { return columns ~ SyncCols; }
94 
95     /*
96     If the table primary key is set by the terver, we need to return it to the client. */
97     const(Col)[] returningCols() const
98     {
99         import std.algorithm : canFind, filter;
100         import std.array : array;
101         import std.range : indexed;
102         
103         return pkCols
104             .filter!( (c) => [Type.smallserial, Type.serial].canFind(c.type) )
105             .array;
106     }
107     
108     /**
109      * returns the primary keys columns of a table */
110     immutable(Col)[] pkCols() const
111     {
112         import std.array : array;
113         import std.range : indexed;
114 
115         return columns.indexed(primaryKey).array;
116     }
117     unittest {
118         static assert(Landings.pkCols.length == Landings.primaryKey.length);
119     }
120 
121 }
122 unittest
123 {
124     immutable static t1 = immutable(Table)("t", [
125             Col("c1", Type.integer), Col("c2", Type.text), Col("c3", Type.integer)
126     ], [0,2], []);
127 
128     static assert( t1.pkCols() == [t1.columns[0], t1.columns[2]] );
129     
130     enum columns = t1.columns;
131     void f1(asD!columns cols){ int i = cols[0]; string s = cols[1]; int j = cols[2]; } 
132     f1(1, "foo", 2);
133 }
134 
135 struct Col {
136     string name;
137     Type type;
138     bool null_;
139 }
140 
141 struct Reference {
142     size_t[] referenceCols;
143     string referencedTable;
144     size_t[] referencedCols;
145 }
146 
147 enum Type { 
148     unknown, 
149     boolean,
150     date, 
151     real_, doublePrecision, 
152     smallint, integer, bigint, // 16, 32, 64 bit, postgresql
153     smallserial, serial, 
154     text, varchar,
155     bytea 
156 }
157 
158 /**
159  * returns the D type for the passed SQL type. */
160 template asD(alias t) if( is(Unqual!(typeof(t)) == Type) )
161 {
162 
163     static if( t == Type.integer )              alias asD = int;
164     else static if( t == Type.bigint )          alias asD = long;
165     else static if( t == Type.text )            alias asD = string;
166     else static if( t == Type.serial )          alias asD = int;
167     else static if( t == Type.boolean )         alias asD = bool;
168     else static if( t == Type.smallint )        alias asD = short;
169     else static if( t == Type.smallserial )     alias asD = short;
170     else static if( t == Type.real_ )           alias asD = float;
171     else static if( t == Type.doublePrecision ) alias asD = double;
172     else static if( t == Type.date )            alias asD = string; //Date;
173     else static if( t == Type.bytea )           alias asD = ubyte[];
174     else static assert(false);
175 }
176 
177 /**
178  * returns the D type for a column. */
179 template asD(alias c) if( is(Unqual!(typeof(c)) == Col) )
180 {
181     enum t = c.type;
182     alias asD = .asD!(t);
183 }
184 static assert( is(asD!( Col("c", Type.integer) ) == int) );
185 static assert( is(asD!( Col("c", Type.real_) ) == float ) );
186 
187 /**
188  * returns the D type for a sequence of columns. */
189 template asD(alias c) if( is(Unqual!(typeof(c)) : immutable(Col)[]) || is(Unqual!(typeof(c)) : Col[]) )
190 {
191 
192     enum cols = c;
193     static if(cols.length == 1){
194         alias asD = AliasSeq!(asD!(cols[0]));
195     }
196     else static if(cols.length >1){
197         alias asD = AliasSeq!(asD!(cols[0]), asD!(cols[1 .. $]));
198     }
199     else static assert(false);
200 }
201 static assert( is(asD!( [Col("c", Type.integer), Col("d", Type.text)] ) == AliasSeq!(int, string)) );
202 
203 /** 
204  * returns the D type and the name of the column. */
205 private template asStruct_(alias c, string p ="") if( is(Unqual!(typeof(c)) : immutable(Col)[]) || is(Unqual!(typeof(c)) : Col[]) )
206 {
207 
208     enum cols = c;
209     enum prefix = p;
210     static if(cols.length == 1){
211         alias t = asD!(cols[0]);
212         enum string n = cols[0].name;
213         enum string asStruct_ = t.stringof ~ " " ~ prefix ~ n ~ ";"; //AliasSeq!(asD!(cols[0])););
214     }
215     else static if(cols.length >1){
216         enum string asStruct_ = (asD!(cols[0])).stringof ~ " " ~ prefix ~ cols[0].name ~ "; " ~ asStruct_!(cols[1 .. $], prefix);
217     }
218     else static assert(false, cols.length);
219 }
220 
221 
222 
223 template asStruct(alias t)
224 {
225     enum cols = t.columns;
226     enum string structName = t.name ~ "Row";
227     enum string def = "struct " ~ structName ~ " {" ~ asStruct_!(cols) ~ "}";
228     mixin(def ~"; alias asStruct = " ~ structName ~ ";");
229 }
230 static assert(is( asStruct!(Table("t", [immutable(Col)("c1", Type.integer), immutable(Col)("c2", Type.text)], [], [])) == struct )); 
231 
232 /// I can't use here an 'alias this' composition, as it would be serialised wrongly.
233 template asSyncStruct(alias t)
234 {
235     enum cols = t.decoratedCols;
236     enum string structName = t.name ~ "SyncRow";
237     enum string def = "struct " ~ structName ~ " {" ~ asStruct_!(cols) ~ "}";
238     mixin(def ~"; alias asSyncStruct = " ~ structName ~ ";");
239 }
240 
241 template asPkStruct(alias t)
242 {
243     static if( t.pkCols.length >0 ){
244         enum cols = t.pkCols;
245     }
246     else {
247         enum cols = t.columns;
248     }
249     enum string structName = t.name ~ "PkRow";
250     enum string def = "struct " ~ structName ~ " {" ~ asStruct_!(cols) ~ "}";
251     mixin(def ~"; alias asPkStruct = " ~ structName ~ ";");
252 }
253 unittest {
254     static assert(is(asPkStruct!(Table("t", [immutable(Col)("c1", Type.integer), immutable(Col)("c2", Type.text)], [0], [])) == struct ));
255     static assert( FieldNameTuple!(asPkStruct!Landings) == AliasSeq!("person_name", "planet_name"));
256 }
257 
258 template asPkParamStruct(alias t)
259 {
260     static if( t.pkCols.length >0 ){
261         enum cols = t.pkCols;
262     }
263     else {
264         enum cols = t.columns;
265     }
266     enum string structName = t.name ~ "PkRow";
267     enum string def = "struct " ~ structName ~ " {" ~ asStruct_!(cols, "key") ~ "}";
268     mixin(def ~"; alias asPkParamStruct = " ~ structName ~ ";");
269 }
270 unittest {
271     static assert(is(asPkStruct!(Table("t", [immutable(Col)("c1", Type.integer), immutable(Col)("c2", Type.text)], [0], [])) == struct ));
272     static assert( FieldNameTuple!(asPkParamStruct!Landings) == AliasSeq!("keyperson_name", "keyplanet_name"));
273 
274 }
275 
276 template asSyncPkParamStruct(alias t)
277 {
278     enum string structName = t.name ~ "SyncPkParamRow";
279     static if( t.pkCols.length >0 ){
280         enum pkCols = t.pkCols;
281         enum string def = "struct " ~ structName ~ " {" ~ asStruct_!(pkCols, "key") ~ asStruct_!(SyncCols) ~ "}";
282     }
283     else {
284         enum cols = t.decoratedCols;
285         enum string def = "struct " ~ structName ~ " {" ~ asStruct_!(cols, "key") ~ "}";
286     }
287     mixin(def ~"; alias asSyncPkParamStruct = " ~ structName ~ ";");
288 }
289 static assert(is(asPkParamStruct!(Table("t", [immutable(Col)("c1", Type.integer), immutable(Col)("c2", Type.text)], [0], [])) == struct ));
290 
291 
292 
293 /**
294  * returns the values of the primary keys of this table row. */
295 auto pkValues(alias table)(const asStruct!table fixture)
296 {
297     asPkStruct!table keys;
298     assignCommonFields(keys, fixture);
299     return keys;
300 }
301 unittest {
302     static assert(luke.pkValues!People.name == "Luke");
303 }
304 
305 /**
306  * returns the values of the primary keys of this table row. */
307 auto pkParamValues(alias table)(const asStruct!table fixture)
308 {
309     asPkStruct!table keys;
310     asPkParamStruct!table paramKeys;
311     assignCommonFields(keys, fixture);
312     assignFields(paramKeys, keys);
313     return paramKeys;
314 }
315 unittest {
316     static assert(luke.pkParamValues!People.keyname == "Luke");
317 }
318 
319 void assignCommonFields(R, V, size_t i = 0)(ref R r, V v)
320 {
321     alias NamesR = FieldNameTuple!R;
322     alias NamesV = FieldNameTuple!V;
323 
324     enum string Name = NamesV[i];
325 
326 
327     enum j = staticIndexOf!(Name, NamesR);
328     static if( j != -1 ){
329         r.tupleof[j] = v.tupleof[i];
330     }
331     static if(i+1 < NamesV.length){
332         assignCommonFields!(R, V, i+1)(r, v);
333     }
334 }
335 unittest 
336 {
337     struct V { string a; string b; string c; }
338     struct R { string b; string a; }
339     struct X { int b; string a; }
340 
341     V v = V("a", "b", "c");
342     R r;
343     X x;
344 
345     static assert(!__traits(compiles, assignCommonFields!(X, V)(x, v) ));
346 
347     assignCommonFields!(R, V)(r, v);
348     assert( r == R("b", "a")) ;
349 
350 }
351 
352 void assignFields(R, V, size_t i = 0)(ref R r, V v)
353 {
354     r.tupleof[i] = v.tupleof[i];
355     static if(i+1 < v.tupleof.length) assignFields!(R, V, i+1)(r, v);
356 }
357 unittest
358 {
359     struct V { string a; int b;}
360     struct R { string c; int d;}
361     auto v = V("hello", 42);
362     R r;
363     assignFields(r, v);
364     assert( r == R("hello", 42) );
365 }