1 /**
2   BasicDatabase:  a common and generic front-end for database access
3 
4   Typically, this interface is impliclity used when import a specific database
5   driver as shown in this simple example:
6 
7   ---
8   import std.database.sqlite;
9   auto db = createDatabase("file:///testdb");
10   auto rows = db.connection.query("select name,score from score").rows;
11   foreach (r; rows) writeln(r[0], r[1]);
12   ---
13 
14   BasicDatabase, and it's chain of types, provides a common,  easy to use,
15   and flexibe front end for client interactions with a database. it carefully
16   manages lifetimes and states, making the front end easy to use and the driver layer
17   easy to implement.
18 
19   For advanced usage (such as library implementers), you can also explicitly
20   instantiate a BasicDatabase with a specific Driver type:
21   ---
22   struct MyDriver {
23     struct Database {//...}
24     struct Connection {//...}
25     struct Statement {//...}
26     struct Bind {//...}
27     struct Result {//...}
28   }
29 
30   import std.database;
31   alias DB = BasicDatabase!(MyDriver);
32   auto db = DB("mysql://127.0.0.1");
33   ---
34 
35 */
36 module std.database.BasicDatabase;
37 import std.experimental.logger;
38 import std.database.exception;
39 import std.datetime;
40 
41 import std.typecons;
42 import std.database.common;
43 import std.database.pool;
44 import std.database.resolver;
45 import std.database.source;
46 
47 import std.database.allocator;
48 import std.traits;
49 import std.container.array;
50 import std.variant;
51 
52 import std.range.primitives;
53 import std.database.option;
54 
55 public import std.database.array;
56 
57 enum ValueType {
58     Int,
59     String,
60     Date,
61     Variant,
62 }
63 
64 // improve
65 struct TypeInfo(T:int) {static auto type() {return ValueType.Int;}}
66 struct TypeInfo(T:string) {static auto type() {return ValueType.String;}}
67 struct TypeInfo(T:Date) {static auto type() {return ValueType.Date;}}
68 struct TypeInfo(T:Variant) {static auto type() {return ValueType.Variant;}}
69 
70 
71 enum Feature {
72     InputBinding,
73     DateBinding,
74     ConnectionPool,
75     OutputArrayBinding,
76 }
77 
78 alias FeatureArray = Feature[];
79 
80 /**
81   A root type for interacting with databases. It's primary purpose is act as
82   a factory for database connections. This type can be shared across threads.
83 */
84 struct BasicDatabase(D) {
85     alias Driver = D;
86     alias Policy = Driver.Policy;
87     alias Database = Driver.Database;
88     alias Allocator = Policy.Allocator;
89     alias Connection = BasicConnection!(Driver);
90     alias Cell = BasicCell!(Driver);
91     alias Pool = .Pool!(Driver.Connection);
92     alias ScopedResource = .ScopedResource!Pool;
93 
94     alias queryVariableType = Database.queryVariableType;
95 
96     static auto create() {return BasicDatabase(null);}
97     static auto create(string url) {return BasicDatabase(url);}
98 
99     /**
100       return connection for default database
101     */
102     auto connection() {return Connection(this);}
103 
104     /**
105       return connection for database specified by URI
106     */
107     auto connection(string uri) {return Connection(this, uri);}
108 
109     /**
110       return statement 
111     */
112     auto statement(string sql) {return connection().statement(sql);}
113 
114     /**
115       return statement with specified input binds
116     */
117     auto statement(X...) (string sql, X args) {return connection.statement(sql,args);}
118 
119     /**
120       return executed statement
121     */
122     auto query(string sql) {return connection().query(sql);}
123 
124     /**
125       return executed statement object with specified input binds
126     */
127     auto query(T...) (string sql, T args) {return statement(sql).query(args);}
128 
129     //static bool hasFeature(Feature feature);
130     // go with non-static hasFeature for now to accomidate poly driver
131 
132     bool hasFeature(Feature feature) {return hasFeature(data_.database, feature);}
133     auto ref driverDatabase() {return data_.database;}
134 
135     private struct Payload {
136         string defaultURI;
137         Database database;
138         Pool pool;
139         this(string defaultURI_) {
140             defaultURI = defaultURI_;
141             database = Database(defaultURI_);
142             bool poolEnable = hasFeature(database, Feature.ConnectionPool);
143             pool = Pool(poolEnable);
144         }
145 
146     }
147 
148     this(string defaultURI) {
149         data_ = Data(defaultURI);
150     }
151 
152     private alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data;
153     private Data data_;
154 
155     // for poly
156     static if (hasMember!(Database, "register")) {
157         static void register(DB) (string name = "") {
158             Database.register!DB(name); 
159         }
160 
161         auto database(string name) {
162             auto db = BasicDatabase(data_.defaultURI); 
163             db.data_.database.setDatabase(name);
164             return db;
165         }
166     }
167 
168     private static bool hasFeature(ref Database db, Feature feature) {
169         import std.algorithm;
170         import std.range.primitives: empty;
171         //auto r = find(Database.features, feature);
172         auto r = find(db.features, feature);
173         return !r.empty;
174     }
175 }
176 
177 /**
178   Database connection class
179 */
180 struct BasicConnection(D) {
181     alias Driver = D;
182     alias Policy = Driver.Policy;
183     alias DriverConnection = Driver.Connection;
184     alias Statement = BasicStatement!(Driver);
185     alias Database = BasicDatabase!(Driver);
186     alias Pool = Database.Pool;
187     alias ScopedResource = Database.ScopedResource;
188     alias DatabaseImpl = Driver.Database;
189 
190     auto statement(string sql) {return Statement(this,sql);}
191     auto statement(X...) (string sql, X args) {return Statement(this,sql,args);}
192     auto query(string sql) {return statement(sql).query();}
193     auto query(T...) (string sql, T args) {return statement(sql).query(args);}
194 
195     auto rowArraySize(int rows) {
196         rowArraySize_ = rows;
197         return this;
198     }
199 
200     private alias RefCounted!(ScopedResource, RefCountedAutoInitialize.no) Data;
201 
202     //private Database db_; // problem (fix in 2.072)
203     private Data data_;
204     private Pool* pool_;
205     string uri_;
206     int rowArraySize_ = 1;
207 
208     package this(Database db) {this(db,"");}
209 
210     package this(Database db, string uri) {
211         //db_ = db;
212         uri_ = uri.length != 0 ? uri : db.data_.defaultURI;
213         pool_ = &db.data_.pool;
214         Source source = resolve(uri_);
215         data_ = Data(*pool_, pool_.acquire(&db.driverDatabase(), source));
216     }
217 
218     auto autoCommit(bool enable) {
219         return this;
220     }
221 
222     auto begin() {
223         return this;
224     }
225 
226     auto commit() {
227         return this;
228     }
229 
230     auto rollback() {
231         return this;
232     }
233 
234     private auto ref driverConnection() {
235         return data_.resource.resource;
236     }
237 
238     static if (hasMember!(Database, "socket")) {
239         auto socket() {return driverConnection.socket;}
240     }
241 
242     // handle()
243     // be carful about Connection going out of scope or being destructed
244     // while a handle is in use  (Use a connection variable to extend scope)
245 
246     static if (hasMember!(Database, "handle")) {
247         auto handle() {return driverConnection.handle;}
248     }
249 
250     /*
251        this(Database db, ref Allocator allocator, string uri="") {
252 //db_ = db;
253 data_ = Data(&db.data_.refCountedPayload(),uri);
254 }
255      */
256 
257 
258 }
259 
260 /**
261   Manages statement details such as query execution and input binding.
262 */
263 struct BasicStatement(D) {
264     alias Driver = D;
265     alias Policy = Driver.Policy;
266     alias DriverStatement = Driver.Statement;
267     alias Connection = BasicConnection!(Driver);
268     alias Result = BasicResult!(Driver);
269     alias RowSet = BasicRowSet!(Driver);
270     alias ColumnSet = BasicColumnSet!(Driver);
271     //alias Allocator = Policy.Allocator;
272     alias ScopedResource = Connection.ScopedResource;
273 
274     /*
275        auto result() {
276        if (state != State.Executed) throw new DatabaseException("statement not executed");
277        return Result(this);
278        }
279      */
280     //auto opSlice() {return result();} //fix
281 
282     enum State {
283         Undef,
284         Prepared,
285         Executed,
286     }
287 
288 
289     this(Connection con, string sql) {
290         con_ = con;
291         data_ = Data(con_.driverConnection,sql);
292         rowArraySize_ = con.rowArraySize_;
293         prepare();
294     }
295 
296     /*
297     // determine if needed and how to do
298     this(X...) (Connection con, string sql, X args) {
299     con_ = con;
300     data_ = Data(&con.data_.refCountedPayload(),sql);
301     prepare();
302     bindAll(args);
303     }
304      */
305 
306     string sql() {return data_.sql;}
307     //int binds() {return data_.binds;}
308 
309     void bind(int n, int value) {data_.bind(n, value);}
310     void bind(int n, const char[] value){data_.bind(n,value);}
311 
312     auto query() {
313         data_.query();
314         state = State.Executed;
315         return this;
316     }
317 
318     auto query(X...) (X args) {
319         data_.query(args);
320         state = State.Executed;
321         return this;
322     }
323 
324     auto into(A...) (ref A args) {
325         if (state != State.Executed) throw new DatabaseException("not executed");
326         rows.into(args);
327         return this;
328     }
329 
330     bool hasRows() {return data_.hasRows;}
331 
332     // rows()
333     // accessor for single rowSet returned
334     // length should be the number of rows
335     // alternate name: rowSet, table
336 
337     auto rows() {
338         if (state != State.Executed) query();
339         return RowSet(Result(this, rowArraySize_));
340     }
341 
342     auto columns() {
343         if (state != State.Executed) query();
344         return ColumnSet(Result(this, rowArraySize_));
345     }
346 
347     // results()
348     // returns range for one or more things returned by a query
349     auto results() {
350         if (state != State.Executed) throw new DatabaseException("not executed");
351         return 0; // fill in
352     }
353 
354 
355     private:
356     alias RefCounted!(DriverStatement, RefCountedAutoInitialize.no) Data;
357 
358     Data data_;
359     Connection con_;
360     State state;
361     int rowArraySize_;
362 
363     void prepare() {
364         data_.prepare();
365         state = State.Prepared;
366     }
367 
368     void reset() {data_.reset();} //SQLCloseCursor
369 }
370 
371 
372 /**
373   An internal class for result access and iteration. See the RowSet type for range based access
374   to results
375 */
376 struct BasicResult(D) {
377     alias Driver = D;
378     alias Policy = Driver.Policy;
379     alias ResultImpl = Driver.Result;
380     alias Statement = BasicStatement!(Driver);
381     alias RowSet = BasicRowSet!(Driver);
382     //alias Allocator = Driver.Policy.Allocator;
383     alias Bind = Driver.Bind;
384     //alias Row = .Row;
385 
386     this(Statement stmt, int rowArraySize = 1) {
387         stmt_ = stmt;
388         rowArraySize_ = rowArraySize;
389         data_ = Data(&stmt.data_.refCountedPayload(), rowArraySize_);
390         if (!stmt_.hasRows) throw new DatabaseException("not a result query");
391         rowsFetched_ = data_.fetch();
392     }
393 
394     // disallow slicing to avoid confusion: must use rows/results
395     //auto opSlice() {return ResultRange(this);}
396 
397 package:
398     int rowsFetched() {return rowsFetched_;}
399 
400     bool next() {
401         if (++rowIdx_ == rowsFetched_) {
402             rowsFetched_ = data_.fetch();
403             if (!rowsFetched_) return false;
404             rowIdx_ = 0;
405         }
406         return true;
407     }
408 
409     auto ref result() {return data_.refCountedPayload();}
410 
411     private:
412     Statement stmt_;
413     int rowArraySize_; //maybe move into RC
414     int rowIdx_;
415     int rowsFetched_;
416 
417     alias RefCounted!(ResultImpl, RefCountedAutoInitialize.no) Data;
418     Data data_;
419 
420     // these need to move
421     int width() {return data_.columns;}
422 
423     private size_t index(string name) {
424         import std.uni;
425         // slow name lookup, all case insensitive for now
426         for(int i=0; i!=width; i++) {
427             if (sicmp(data_.name(i), name) == 0) return i;
428         }
429         throw new DatabaseException("column name not found:" ~ name);
430     }
431 }
432 
433 /**
434   A range over result column information
435 */
436 struct BasicColumnSet(D) {
437     alias Driver = D;
438     alias Policy = Driver.Policy;
439     alias Result = BasicResult!(Driver);
440     alias Column = BasicColumn!(Driver);
441     private Result result_;
442 
443     this(Result result) {
444         result_ = result;
445     }
446 
447     int width() {return result_.data_.columns;}
448 
449     struct Range {
450         private Result result_;
451         int idx;
452         this(Result result) {result_ = result;}
453         bool empty() {return idx == result_.data_.columns;}
454         auto front() {return Column(result_, idx);}
455         void popFront() {++idx;}
456     }
457 
458     auto opSlice() {return Range(result_);}
459 }
460 
461 
462 struct BasicColumn(D) {
463     alias Driver = D;
464     alias Policy = Driver.Policy;
465     alias Result = BasicResult!(Driver);
466     private Result result_;
467     private size_t idx_;
468 
469     this(Result result, size_t idx) {
470         result_ = result;
471         idx_ = idx;
472     }
473 
474     auto idx() {return idx_;}
475     auto name() {return "abc";}
476 
477 }
478 
479 
480 /**
481   A input range over the results of a query.
482 */
483 struct BasicRowSet(D) {
484     alias Driver = D;
485     alias Policy = Driver.Policy;
486     alias Result = BasicResult!(Driver);
487     alias Row = BasicRow!(Driver);
488     alias ColumnSet = BasicColumnSet!(Driver);
489 
490     void rowSetTag();
491 
492     private Result result_;
493 
494     this(Result result) {
495         result_ = result;
496     }
497 
498     int width() {return result_.data_.columns;}
499 
500     // length will be for the number of rows (if defined)
501     int length() {
502         throw new Exception("not a completed/detached rowSet");
503     }
504 
505     auto into(A...) (ref A args) {
506         if (!result_.rowsFetched()) throw new DatabaseException("no data");
507         auto row = front();
508         foreach(i, ref a; args) {
509             alias T = A[i];
510             static if (is(T == string)) {
511                 a = row[i].as!T.dup;
512             } else {
513                 a = row[i].as!T;
514             }
515         }
516 
517         // don't consume so we can do into at row level
518         // range.popFront;
519         //if (!range.empty) throw new DatabaseException("result has more than one row");
520 
521         return this;
522     }
523 
524 
525     // into for output range: experimental
526     //if (isOutputRange!(R,E))
527     auto into(R) (R range) 
528         if (hasMember!(R, "put")) {
529             // Row should have range
530             // needs lots of work
531             auto row = Row(this);
532             for(int i=0; i != width; i++) {
533                 auto f = row[i];
534                 switch (f.type) {
535                     case ValueType.Int: put(range, f.as!int); break;
536                     case ValueType.String: put(range, f.as!string); break;
537                                            //case ValueType.Date: put(range, f.as!Date); break;
538                     default: throw new DatabaseException("switch error");
539                 }
540             }
541         }
542 
543     auto columns() {
544         return ColumnSet(result_);
545     }
546 
547 
548     bool empty() {return result_.rowsFetched_ == 0;}
549     auto front() {return Row(this);}
550     void popFront() {result_.next();}
551 }
552 
553 /**
554   A row accessor for the current row in a RowSet input range.
555 */
556 struct BasicRow(D) {
557     alias Driver = D;
558     alias Policy = Driver.Policy;
559     alias Result = BasicResult!(Driver);
560     alias RowSet = BasicRowSet!(Driver);
561     alias Cell = BasicCell!(Driver);
562     alias Value = BasicValue!(Driver);
563     alias Column = BasicColumn!(Driver);
564 
565     private RowSet rows_;
566 
567     this(RowSet rows) {
568         rows_ = rows;
569     }
570 
571     int width() {return rows_.result_.data_.columns;}
572 
573     auto into(A...) (ref A args) {
574         rows_.into(args);
575         return this;
576     }
577 
578     auto opIndex(Column column) {return opIndex(column.idx);}
579 
580     // experimental
581     auto opDispatch(string s)() {
582         return opIndex(rows_.result_.index(s));
583     }
584 
585     Value opIndex(size_t idx) {
586         auto result = &rows_.result_;
587         // needs work
588         // sending a ptr to cell instead of reference (dangerous)
589         auto cell = Cell(result, &result.data_.bind[idx], idx);
590         return Value(result, cell);
591     }
592 }
593 
594 
595 /**
596   A value accessor for an indexed value in the current row in a RowSet input range.
597 */
598 struct BasicValue(D) {
599     alias Driver = D;
600     alias Policy = Driver.Policy;
601     //alias Result = Driver.Result;
602     alias Result = BasicResult!(Driver);
603     alias Cell = BasicCell!(Driver);
604     alias Bind = Driver.Bind;
605     private Result* result_;
606     private Bind* bind_;
607     private Cell cell_;
608     alias Converter = .Converter!(Driver,Policy);
609 
610     this(Result* result, Cell cell) {
611         result_ = result;
612         //bind_ = bind;
613         cell_ = cell;
614     }
615 
616     private auto resultPtr() {
617         return &result_.result(); // last parens matter here (something about delegate)
618     }
619 
620     auto type() {return cell_.bind.type;}
621 
622     auto as(T:int)() {return Converter.convert!T(resultPtr, cell_);}
623     auto as(T:string)() {return Converter.convert!T(resultPtr, cell_);}
624     auto as(T:Date)() {return Converter.convert!T(resultPtr, cell_);}
625     auto as(T:Nullable!T)() {return Nullable!T(as!T);}
626 
627     // should be nothrow?
628     auto as(T:Variant)() {return Converter.convert!T(resultPtr, cell_);}
629 
630     bool isNull() {return false;} //fix
631 
632     string name() {return resultPtr.name(cell_.idx_);}
633 
634     auto get(T)() {return Nullable!T(as!T);}
635 
636     // experimental
637     auto option(T)() {return Option!T(as!T);}
638 
639     // not sure if this does anything
640     const(char)[] chars() {return as!string;}
641 
642     string toString() {return as!string;}
643 
644 }
645 
646 
647 // extra stuff
648 
649 
650 struct EfficientValue(T) {
651     alias Driver = T.Driver;
652     alias Bind = Driver.Bind;
653     private Bind* bind_;
654     alias Converter = .Converter!Driver;
655 
656     this(Bind* bind) {bind_ = bind;}
657 
658     auto as(T:int)() {return Converter.convertDirect!T(bind_);}
659     auto as(T:string)() {return Converter.convertDirect!T(bind_);}
660     auto as(T:Date)() {return Converter.convertDirect!T(bind_);}
661     auto as(T:Variant)() {return Converter.convertDirect!T(bind_);}
662 
663 }
664 
665 
666 struct BasicCell(D) {
667     alias Driver = D;
668     alias Policy = Driver.Policy;
669     alias Result = BasicResult!(Driver);
670     alias Bind = Driver.Bind;
671     private Bind* bind_;
672     private int rowIdx_;
673     private size_t idx_;
674 
675     this(Result *r, Bind *b, size_t idx) {
676         bind_ = b;
677         rowIdx_ = r.rowIdx_;
678         idx_ = idx;
679     }
680 
681     auto bind() {return bind_;}
682     auto rowIdx() {return rowIdx_;}
683 }
684 
685 struct Converter(D,P) {
686     alias Driver = D;
687     alias Policy = P;
688     alias Result = Driver.Result;
689     alias Bind = Driver.Bind;
690     alias Cell = BasicCell!(Driver);
691 
692     static Y convert(Y)(Result *r, ref Cell cell) {
693         ValueType x = cell.bind.type, y = TypeInfo!Y.type;
694         if (x == y) return r.get!Y(&cell); // temporary
695         auto e = lookup(x,y);
696         if (!e) conversionError(x,y);
697         Y value;
698         e.convert(r, &cell, &value);
699         return value;
700     }
701 
702     static Y convert(Y:Variant)(Result *r, ref Cell cell) {
703         //return Y(123);
704         ValueType x = cell.bind.type, y = ValueType.Variant;
705         auto e = lookup(x,y);
706         if (!e) conversionError(x,y);
707         Y value;
708         e.convert(r, &cell, &value);
709         return value;
710     }
711 
712     static Y convertDirect(Y)(Result *r, ref Cell cell) {
713         assert(b.type == TypeInfo!Y.type);
714         return r.get!Y(&cell);
715     }
716 
717     private:
718 
719     struct Elem {
720         ValueType from,to;
721         void function(Result*,void*,void*) convert;
722     }
723 
724     // only cross converters, todo: all converters
725     static Elem[6] converters = [
726     {from: ValueType.Int, to: ValueType.String, &generate!(int,string).convert},
727     {from: ValueType.String, to: ValueType.Int, &generate!(string,int).convert},
728     {from: ValueType.Date, to: ValueType.String, &generate!(Date,string).convert},
729     // variants
730     {from: ValueType.Int, to: ValueType.Variant, &generate!(int,Variant).convert},
731     {from: ValueType.String, to: ValueType.Variant, &generate!(string,Variant).convert},
732     {from: ValueType.Date, to: ValueType.Variant, &generate!(Date,Variant).convert},
733     ];
734 
735     static Elem* lookup(ValueType x, ValueType y) {
736         // rework into efficient array lookup
737         foreach(ref i; converters) {
738             if (i.from == x && i.to == y) return &i;
739         }
740         return null;
741     }
742 
743     struct generate(X,Y) {
744         static void convert(Result *r, void *x_, void *y_) {
745             import std.conv;
746             Cell* cell = cast(Cell*) x_;
747             *cast(Y*) y_ = to!Y(r.get!X(cell));
748         }
749     }
750 
751     struct generate(X,Y:Variant) {
752         static void convert(Result *r, void *x_, void *y_) {
753             Cell* cell = cast(Cell*) x_;
754             *cast(Y*) y_ = r.get!X(cell);
755         }
756     }
757 
758     static void conversionError(ValueType x, ValueType y) {
759         import std.conv;
760         string msg;
761         msg ~= "unsupported conversion from: " ~ to!string(x) ~ " to " ~ to!string(y);
762         throw new DatabaseException(msg);
763     }
764 
765 }
766