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