├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dub.sdl ├── freetds ├── dub.json └── test.d ├── mysql ├── dub.json └── test.d ├── odbc ├── dub.json └── test.d ├── oracle ├── dub.json └── test.d ├── poly ├── dub.json └── test.d ├── postgres ├── dub.json └── test.d ├── reference ├── dub.json └── test.d ├── sqlite ├── dub.json └── test.d ├── src └── std │ └── database │ ├── BasicDatabase.d │ ├── allocator.d │ ├── array.d │ ├── common.d │ ├── exception.d │ ├── freetds │ ├── bindings.d │ ├── database.d │ └── package.d │ ├── mysql │ ├── bindings.d │ ├── database.d │ └── package.d │ ├── odbc │ ├── database.d │ └── package.d │ ├── option.d │ ├── oracle │ ├── bindings.d │ ├── database.d │ ├── package.d │ └── stubs.d │ ├── package.d │ ├── poly │ ├── database.d │ └── package.d │ ├── pool.d │ ├── postgres │ ├── bindings.d │ ├── database.d │ └── package.d │ ├── reference │ ├── database.d │ └── package.d │ ├── resolver.d │ ├── rowset.d │ ├── source.d │ ├── sqlite │ ├── database.d │ └── package.d │ ├── testsuite.d │ ├── uri.d │ ├── util.d │ ├── variant.d │ └── vibehandler.d ├── travis-ci.sh ├── util ├── dub.json └── test.d └── vibe ├── dub.json └── test.d /.gitignore: -------------------------------------------------------------------------------- 1 | *.[oa] 2 | *.so 3 | *.lib 4 | *.dll 5 | 6 | .dub 7 | dub.selections.json 8 | 9 | .DS_Store 10 | 11 | #unit test binaries 12 | tests/*/tests 13 | __test__*__ 14 | 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | d: 3 | - dmd 4 | addons: 5 | apt: 6 | packages: 7 | - sqlite3 8 | - libsqlite3-dev 9 | script: ./travis-ci.sh 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Erik Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dstddb 2 | ## A proposed standard database client interface and implementation for the [D](http://dlang.org) Language 3 | 4 | Status: early stage project - unstable and minimally tested 5 | 6 | Available in [DUB](https://code.dlang.org/packages/dstddb), the D package registry 7 | 8 | [![Build Status](https://travis-ci.org/cruisercoder/dstddb.svg?branch=master)](https://travis-ci.org/cruisercoder/dstddb) 9 | 10 | ### Quickstart with Dub 11 | 12 | Add a dub.sdl file 13 | ``` 14 | name "demo" 15 | libs "sqlite3" 16 | dependency "dstddb" version="*" 17 | versions "StdLoggerDisableLogging" 18 | targetType "executable" 19 | ``` 20 | 21 | Add a simple example in src/demo.d 22 | ```D 23 | void main() { 24 | import std.database.sqlite; 25 | auto db = createDatabase("file:///testdb.sqlite"); 26 | auto con = db.connection; 27 | con.query("drop table if exists score"); 28 | con.query("create table score(name varchar(10), score integer)"); 29 | con.query("insert into score values('Knuth',71)"); 30 | con.query("insert into score values('Dijkstra',82)"); 31 | con.query("insert into score values('Hopper',98)"); 32 | auto rows = con.query("select name,score from score").rows; 33 | foreach (r; rows) writeln(r[0].as !string, ",", r[1].as !int); 34 | } 35 | ``` 36 | 37 | Run it: 38 | ```sh 39 | dub 40 | ``` 41 | 42 | 43 | ### Roadmap Highlights 44 | - A database and driver neutral interface specification 45 | - Reference counted value objects provide ease of use 46 | - Templated implementations for Phobos compatibility 47 | - Support for direct and polymorphic interfaces 48 | - A range interface for query result sets 49 | - Support a for [fluent](http://en.wikipedia.org/wiki/Fluent_interface) style interface 50 | - URL style connection strings 51 | - Reference implementations so far: ODBC, sqlite, mysql, oracle, postgres, freetds (MS SQL) 52 | - Support for allocators 53 | - Support for runtime driver registration 54 | - Input variable binding support 55 | - Array input/output binding support 56 | - Connection pooling 57 | 58 | ## Examples 59 | 60 | #### simple query 61 | ```D 62 | import std.database.mysql; 63 | auto db = createDatabase("mysql://database"); 64 | db.query("insert into table('name',123)"); 65 | ``` 66 | 67 | #### expanded classic style select 68 | ```D 69 | import std.database.mysql; 70 | auto db = createDatabase("mysql://127.0.0.1/test"); 71 | auto con = db.connection(); 72 | auto stmt = con.statement("select * from table"); 73 | auto rows = stmt.query.rows; 74 | foreach (row; rows) { 75 | for(size_t col = 0; col != row.width; ++col) write(row[col], " "); 76 | writeln(); 77 | } 78 | 79 | ``` 80 | #### [fluent](http://en.wikipedia.org/wiki/Fluent_interface) style select 81 | ```D 82 | import std.database.sqlite; 83 | createDatabase("file:///demo.sqlite") 84 | .connection 85 | .query("select * from t1") 86 | .writeRows; 87 | ``` 88 | 89 | #### field access 90 | ```D 91 | import std.database.sqlite; 92 | auto db = createDatabase("file:///testdb"); 93 | auto rows = db.connection.query("select name,score from score").rows; 94 | foreach (r; rows) { 95 | writeln(r[0].as!string,",",r[1].as!int); 96 | } 97 | ``` 98 | 99 | #### select with input binding 100 | ```D 101 | import std.database.sqlite; 102 | int minScore = 50; 103 | createDatabase("file:///demo.sqlite") 104 | .connection 105 | .query("select * from t1 where score >= ?", minScore) 106 | .writeRows; 107 | ``` 108 | 109 | #### insert with input binding 110 | ```D 111 | import std.database; 112 | auto db = createDatabase("mydb"); 113 | auto con = db.connection(); 114 | auto stmt = con.statement("insert into table values(?,?)"); 115 | stmt.query("a",1); 116 | stmt.query("b",2); 117 | stmt.query("c",3); 118 | ``` 119 | 120 | #### poly database setup (driver registration) 121 | ```D 122 | import std.database.poly; 123 | Database.register!(std.database.sqlite.Database)(); 124 | Database.register!(std.database.mysql.Database)(); 125 | Database.register!(std.database.oracle.Database)(); 126 | auto db = createDatabase("mydb"); 127 | ``` 128 | ### Notes 129 | 130 | - The reference implementations use logging (std.experimental.logger). To hide the info logging, add this line to your package.json file: "versions": ["StdLoggerDisableInfo"]. 131 | 132 | ### Related Work 133 | [CPPSTDDB](https://github.com/cruisercoder/cppstddb) is a related project with 134 | similar objectives tailored to the constraints of the C++ language. The aim is 135 | for both projects to complement each other by proving the validity of specific 136 | design choices that apply to both and to draw on implementation correctness 137 | re-enforced from dual language development. 138 | 139 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "dstddb" 2 | description "A D standard database proposal and implementation" 3 | copyright "Copyright © 2018, Erik Smith" 4 | authors "Erik Smith" 5 | license "MIT" 6 | sourcePaths "src" 7 | subPackage "sqlite" 8 | 9 | -------------------------------------------------------------------------------- /freetds/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "freetds", 3 | "description": "Phobos std.database candidate", 4 | "homepage": "", 5 | "copyright": "Copyright © 2015-, Erik Smith", 6 | "authors": [ 7 | "Erik Smith" 8 | ], 9 | "sourcePaths": [".", "../src"], 10 | "libs" : ["sybdb"], 11 | "dependencies": { 12 | }, 13 | "configurations": [ 14 | { 15 | "name": "library", 16 | "targetType": "executable", 17 | }, 18 | { 19 | "name": "unittest", 20 | "targetType": "executable", 21 | "targetName": "unittest", 22 | "dflags": ["-main"], 23 | }, 24 | ], 25 | } 26 | 27 | -------------------------------------------------------------------------------- /freetds/test.d: -------------------------------------------------------------------------------- 1 | module std.database.freetds.test; 2 | import std.database.util; 3 | import std.database.freetds; 4 | import std.experimental.logger; 5 | import std.stdio; 6 | 7 | import std.database.testsuite; 8 | 9 | unittest { 10 | import std.database.testsuite; 11 | alias DB = Database!DefaultPolicy; 12 | testAll!DB("freetds"); 13 | 14 | dateTest(); 15 | //example(); 16 | //backTest(); 17 | } 18 | 19 | void dateTest() { 20 | import std.database.freetds; 21 | import std.datetime; 22 | auto db = createDatabase("freetds"); 23 | auto con = db.connection(); 24 | con.query("drop table d1"); 25 | con.query("create table d1(a date)"); 26 | con.query("insert into d1 values ('2016-01-15')"); 27 | auto rows = con.query("select * from d1").rows; 28 | rows.writeRows; 29 | } 30 | 31 | void example() { 32 | import std.database.freetds; 33 | //auto db = createDatabase("freetds://server/test?username=sa&password=admin"); 34 | auto db = createDatabase("freetds://10.211.55.3:1433/test?username=sa&password=admin"); 35 | auto rows = db.query("SELECT 1,2,'abc'").rows; 36 | foreach (r; rows) { 37 | for(int c = 0; c != r.width; ++c) writeln("column: ",c,", value: ",r[c].as!string); 38 | } 39 | } 40 | 41 | /* 42 | void backTest() { 43 | //string sql = "SELECT * FROM dbo.spt_monitor"; 44 | string sql = "SELECT 1,2,3"; 45 | 46 | auto db = Impl.Database!DefaultPolicy(); 47 | auto con = Impl.Connection!DefaultPolicy(&db,"freetds"); 48 | auto stmt = Impl.Statement!DefaultPolicy(&con,sql); 49 | stmt.prepare(); 50 | stmt.query(); 51 | auto result = Impl.Result!DefaultPolicy(&stmt); 52 | do { 53 | writeln("first: ", result.get!string(&result.bind[0])); 54 | writeln("second: ", result.get!string(&result.bind[1])); 55 | } while (result.next()); 56 | } 57 | */ 58 | 59 | 60 | -------------------------------------------------------------------------------- /mysql/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mysql", 3 | "description": "Phobos std.database candidate", 4 | "homepage": "", 5 | "copyright": "Copyright © 2015-, Erik Smith", 6 | "authors": [ 7 | "Erik Smith" 8 | ], 9 | "sourcePaths": [".", "../src"], 10 | "libs" : ["mysqlclient"], 11 | "configurations": [ 12 | { 13 | "name": "unittest", 14 | "targetType": "executable", 15 | "targetName": "unittest", 16 | "dflags": ["-main"], 17 | }, 18 | ], 19 | } 20 | 21 | -------------------------------------------------------------------------------- /mysql/test.d: -------------------------------------------------------------------------------- 1 | module std.database.mysql.test; 2 | import std.database.util; 3 | import std.database.mysql; 4 | import std.experimental.logger; 5 | import std.stdio; 6 | 7 | import std.database.rowset; 8 | 9 | unittest { 10 | import std.database.testsuite; 11 | alias DB = Database!DefaultPolicy; 12 | testAll!DB("mysql"); 13 | 14 | //negativeNotExecuteTest(); 15 | } 16 | 17 | 18 | void negativeNotExecuteTest() { 19 | auto db = createDatabase("mysql://127.0.0.1/test"); 20 | auto con = db.connection(); 21 | //con.statement("select * from score").writeRows; 22 | con.statement("select * from score").writeRows; 23 | } 24 | 25 | unittest { 26 | //perf1(); 27 | //con.query("select * from tuple").writeRows; 28 | } 29 | 30 | //auto db = createDatabase("mysql://127.0.0.1/test"); 31 | 32 | /* 33 | void perf1() { 34 | import std.datetime; 35 | import std.conv; 36 | 37 | auto db = createDatabase("mysql://127.0.0.1/test"); 38 | auto con = db.connection(); 39 | 40 | if (0) { 41 | db.query("drop table if exists tuple"); 42 | con.query("create table tuple (a int, b int, c int)"); 43 | QueryVariable!(Database!DefaultPolicy.queryVariableType) v; 44 | auto stmt = con.statement( 45 | "insert into tuple values(" ~ v.next() ~ "," ~ v.next() ~ "," ~ v.next() ~ ")"); 46 | 47 | for(int i = 0; i != 1000; ++i) { 48 | if ((i % 10000) ==0) warning(i); 49 | stmt.query(i, i+1, i+2); 50 | } 51 | } 52 | 53 | auto result = con.query("select * from tuple"); 54 | 55 | StopWatch sw; 56 | sw.start(); 57 | auto rs = RowSet(result); 58 | writeln("rowSec ctor time: ", to!Duration(sw.peek)); 59 | 60 | StopWatch sw2; 61 | sw2.start(); 62 | 63 | int sum; 64 | 65 | uint n = 1; 66 | foreach(i; 0..n) { 67 | foreach (r; rs[]) sum = r[0].as!int + r[1].as!int + r[2].as!int; 68 | } 69 | 70 | writeln("sum: ", sum); 71 | writeln("time: ", to!Duration(sw2.peek)); 72 | } 73 | */ 74 | -------------------------------------------------------------------------------- /odbc/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "odbc", 3 | "description": "Phobos std.database candidate", 4 | "homepage": "", 5 | "copyright": "Copyright © 2015-, Erik Smith", 6 | "authors": [ 7 | "Erik Smith" 8 | ], 9 | "sourcePaths": [".", "../src"], 10 | "libs" : ["odbc","sqlite3"], 11 | "dependencies": { 12 | }, 13 | "configurations": [ 14 | { 15 | "name": "library", 16 | "targetType": "executable", 17 | }, 18 | { 19 | "name": "unittest", 20 | "targetType": "executable", 21 | "targetName": "unittest", 22 | "dflags": ["-main"], 23 | }, 24 | ], 25 | } 26 | 27 | -------------------------------------------------------------------------------- /odbc/test.d: -------------------------------------------------------------------------------- 1 | module std.database.odbc.test; 2 | import std.database.util; 3 | import std.stdio; 4 | 5 | unittest { 6 | import std.database.testsuite; 7 | import std.database.odbc; 8 | alias DB = Database!DefaultPolicy; 9 | testAll!DB("odbc"); 10 | 11 | /* 12 | //auto stmt = Statement(con, "select name,score from score"); 13 | auto stmt = con.statement( 14 | "select name,score from score where score>? and name=?", 15 | 1,"jo"); 16 | writeln("binds: ", stmt.binds()); 17 | auto res = stmt.result(); 18 | writeln("columns: ", res.columns()); 19 | auto range = res.range(); 20 | */ 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /oracle/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oracle", 3 | "description": "Phobos std.database candidate", 4 | "homepage": "", 5 | "copyright": "Copyright © 2015-, Erik Smith", 6 | "authors": [ 7 | "Erik Smith" 8 | ], 9 | "sourcePaths": [".", "../src"], 10 | "libs" : ["occi","clntsh"], 11 | "dependencies": { 12 | }, 13 | "configurations": [ 14 | { 15 | "name": "library", 16 | "targetType": "executable", 17 | }, 18 | { 19 | "name": "unittest", 20 | "targetType": "executable", 21 | "targetName": "unittest", 22 | "dflags": ["-main"], 23 | }, 24 | ], 25 | } 26 | 27 | -------------------------------------------------------------------------------- /oracle/test.d: -------------------------------------------------------------------------------- 1 | import std.database.util; 2 | import std.stdio; 3 | import std.database.oracle; 4 | 5 | unittest { 6 | import std.database.testsuite; 7 | alias DB = Database!DefaultPolicy; 8 | testAll!DB("oracle"); 9 | 10 | //testArrayOutputBinding(); 11 | 12 | //dateTest(); 13 | 14 | //auto database1 = Database!()(); 15 | //auto database2 = Database!()("oracle"); 16 | 17 | /* 18 | auto database3 = createDatabase(); 19 | auto database4 = std.database.oracle.createDatabase(); 20 | 21 | //auto database = Database!DefaultPolicy.create("oracle"); 22 | //auto database = Database.create("oracle"); 23 | */ 24 | 25 | } 26 | 27 | void testArrayOutputBinding() { 28 | // test different combinations 29 | import std.conv; 30 | import std.datetime; 31 | 32 | auto db = createDatabase("oracle"); 33 | auto con = db.connection(); 34 | 35 | 36 | if (false) { 37 | con.query("drop table t1"); 38 | con.query("create table t1(a integer, b integer)"); 39 | for(int i = 0; i != 1000; ++i) { 40 | con.query("insert into t1 values(" ~ to!string(i) ~ "," ~ to!string(i+1) ~ ")"); 41 | } 42 | } 43 | 44 | TickDuration[] durations; 45 | 46 | static const int N = 4; 47 | real[N] durationsUsecs; 48 | 49 | foreach(i; 0..N) { 50 | auto rows = con.rowArraySize(100).query("select * from t1").rows; 51 | StopWatch sw; 52 | sw.start(); 53 | 54 | int s; 55 | foreach(r;rows) { 56 | s += r[0].as!int + r[1].as!int; 57 | } 58 | 59 | durations ~= sw.peek; 60 | writeln("time: ", to!Duration(sw.peek)); 61 | } 62 | 63 | real sum = 0; 64 | 65 | foreach (TickDuration duration; durations) { 66 | real usecs = duration.usecs(); 67 | sum += usecs; 68 | } 69 | 70 | real avg = sum / N; 71 | writeln("avg time: ", avg); 72 | } 73 | 74 | void dateTest() { 75 | import std.datetime; 76 | auto db = createDatabase("oracle"); 77 | auto con = db.connection(); 78 | con.query("drop table d1"); 79 | con.query("create table d1(a date, b int)"); 80 | con.query("insert into d1 values(to_date('2015/04/05','yyyy/mm/dd'), 123)"); 81 | auto rows = con.query("select * from d1").rows; 82 | assert(rows.front()[0].as!Date == Date(2015,4,5)); 83 | } 84 | 85 | -------------------------------------------------------------------------------- /poly/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oracle", 3 | "description": "Phobos std.database candidate", 4 | "homepage": "", 5 | "copyright": "Copyright © 2015-, Erik Smith", 6 | "authors": [ 7 | "Erik Smith" 8 | ], 9 | "sourcePaths": [".", "../src"], 10 | "libs" : ["occi","clntsh"], 11 | "dependencies": { 12 | }, 13 | "configurations": [ 14 | { 15 | "name": "library", 16 | "targetType": "executable", 17 | }, 18 | { 19 | "name": "unittest", 20 | "targetType": "executable", 21 | "targetName": "unittest", 22 | "dflags": ["-main"], 23 | }, 24 | ], 25 | } 26 | 27 | -------------------------------------------------------------------------------- /poly/test.d: -------------------------------------------------------------------------------- 1 | import std.database.util; 2 | import std.stdio; 3 | import std.database.oracle; 4 | import std.experimental.logger; 5 | import std.datetime; 6 | import std.variant; 7 | 8 | struct Resource(T) {} 9 | struct Policy {} 10 | 11 | auto create(alias T)() { 12 | T!Policy r; 13 | return r; 14 | } 15 | 16 | auto r = create!Resource; 17 | 18 | struct TestOutputRange { 19 | void put(int i) { 20 | log("I: ", i); 21 | } 22 | void put(string s) { 23 | log("S: ", s); 24 | } 25 | } 26 | 27 | 28 | unittest { 29 | import std.database.testsuite; 30 | //alias DB = Database!DefaultPolicy; 31 | //testAll!DB("oracle"); 32 | 33 | // these are all heading to test suite 34 | 35 | if (true) { //opDispatch 36 | auto db = createDatabase("oracle"); 37 | auto con = db.connection; 38 | auto rows = db.query("select * from t").rows; 39 | 40 | foreach (row; rows) { 41 | for(int c = 0; c != row.width; ++c) { 42 | //log("NAME: ", row[c].name); 43 | writeln("a:", row.A); 44 | writeln("b:", row.B); 45 | } 46 | writeln; 47 | } 48 | 49 | } 50 | 51 | if (false) { 52 | auto db = createDatabase; 53 | auto con1 = db.connection("oracle").autoCommit(false); 54 | auto con2 = db.connection("oracle").autoCommit(false); 55 | 56 | scope(failure) con1.rollback; 57 | scope(failure) con2.rollback; 58 | 59 | //con1.begin.query("insert into account(id,amount) values(1,100)"); 60 | //con2.begin.query("insert into account(id,amount) values(2,-100)"); 61 | 62 | con1.commit; 63 | con2.commit; 64 | } 65 | 66 | if (false) { 67 | auto db = createDatabase("oracle"); 68 | auto con = db.connection; 69 | auto stmt = con.statement("select * from score"); 70 | auto row = stmt.query.rows.front(); 71 | //stmt.query.rows.writeRows; 72 | 73 | TestOutputRange r; 74 | row.into(r); 75 | } 76 | 77 | if (false) { 78 | auto db = createDatabase("oracle"); 79 | auto con = db.connection; 80 | auto stmt = con.statement("select * from score"); 81 | auto rows = stmt.query.rows; 82 | //stmt.query.rows.writeRows; 83 | 84 | foreach (row; rows) { 85 | string name; 86 | int score; 87 | row.into(name,score); 88 | writeln("name: ", name, ", score: ", score); 89 | } 90 | } 91 | 92 | if (false) { // classic example 2 93 | auto db = createDatabase("oracle"); 94 | auto con = db.connection; 95 | auto stmt = con.statement("select * from t"); 96 | auto rows = stmt.query.rows; 97 | 98 | foreach (row; rows) { 99 | foreach (column; rows.columns) { 100 | auto field = row[column]; 101 | writeln("name: ", column.name, ", value: ", field); 102 | } 103 | writeln; 104 | } 105 | } 106 | 107 | if (false) { // classic example 108 | auto db = createDatabase("oracle"); 109 | auto con = db.connection; 110 | auto stmt = con.statement("select * from t"); 111 | auto rows = stmt.query.rows; 112 | 113 | foreach (row; rows) { 114 | for(int c = 0; c != row.width; ++c) { 115 | auto field = row[c]; 116 | write(field," "); 117 | } 118 | writeln; 119 | } 120 | } 121 | 122 | 123 | if (false) { 124 | auto db = createDatabase; 125 | //auto db = Database.create(); 126 | //auto db = DB.create; 127 | 128 | auto con = db.connection("oracle"); 129 | con.query("drop table t"); 130 | con.query("create table t (a varchar(10), b integer, c integer, d date)"); 131 | con.query("insert into t values('one', 2, 3, TO_DATE('2015/02/03', 'yyyy/mm/dd'))"); 132 | auto row = con.query("select * from t").rows.front; 133 | assert(row[0].option!string == "one"); 134 | assert(row[1].option!int == 2); 135 | } 136 | 137 | if (false) { 138 | auto db = createDatabase; 139 | auto con = db.connection("oracle"); 140 | con.query("drop table t"); 141 | con.query("create table t (a varchar(10), b integer, c integer, d date)"); 142 | con.query("insert into t values('one', 2, 3, TO_DATE('2015/02/03', 'yyyy/mm/dd'))"); 143 | auto row = con.query("select * from t").rows.front; 144 | assert(row[0].get!string == "one"); 145 | assert(row[1].get!int == 2); 146 | } 147 | 148 | if (false) { 149 | // into 150 | auto db = createDatabase; 151 | auto con = db.connection("oracle"); 152 | con.query("drop table t"); 153 | con.query("create table t (a varchar(10), b integer, c integer, d date)"); 154 | con.query("insert into t values('one', 2, 3, TO_DATE('2015/02/03', 'yyyy/mm/dd'))"); 155 | string a; 156 | int b; 157 | Variant c; 158 | Date d; 159 | con.query("select * from t").into(a,b,c,d); 160 | assert(a == "one"); 161 | assert(b == 2); 162 | 163 | auto v = Variant(234); 164 | assert(v == 234); 165 | 166 | assert(c == "3"); // fix variant to support string 167 | assert(d == Date(2015,2,3)); 168 | } 169 | 170 | if (false) { 171 | alias DB = Database!DefaultPolicy; 172 | polyTest!DB("oracle"); 173 | 174 | if (false) { 175 | auto db = createDatabase; 176 | auto con = db.connection("oracle"); 177 | con.query("select * from score").writeRows; 178 | } 179 | 180 | } 181 | } 182 | 183 | void polyTest(DB) (string source) { 184 | import std.database.poly; 185 | import std.experimental.logger; 186 | import std.database.front: Feature; 187 | 188 | registerDatabase!DB("oracle"); 189 | auto polyDB = createDatabase; 190 | 191 | auto db = polyDB.database("oracle"); 192 | 193 | log("ConnectionPool: ", db.hasFeature(Feature.ConnectionPool)); 194 | log("OutputArrayBinding: ", db.hasFeature(Feature.OutputArrayBinding)); 195 | 196 | auto con = db.connection("oracle"); 197 | auto stmt = con.statement("select * from score"); 198 | 199 | stmt.query("joe",20); 200 | stmt.query(); 201 | 202 | } 203 | 204 | -------------------------------------------------------------------------------- /postgres/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postgres", 3 | "description": "Phobos std.database candidate", 4 | "homepage": "", 5 | "copyright": "Copyright © 2015-, Erik Smith", 6 | "authors": [ 7 | "Erik Smith" 8 | ], 9 | "sourcePaths": [".", "../src"], 10 | "libs" : ["pq","pgtypes"], 11 | "dependencies": { 12 | }, 13 | "configurations": [ 14 | { 15 | "name": "library", 16 | "targetType": "executable", 17 | }, 18 | { 19 | "name": "unittest", 20 | "targetType": "executable", 21 | "targetName": "unittest", 22 | "dflags": ["-main"], 23 | }, 24 | ], 25 | } 26 | 27 | -------------------------------------------------------------------------------- /postgres/test.d: -------------------------------------------------------------------------------- 1 | module std.database.postgres.test; 2 | import std.database.util; 3 | import std.database.common; 4 | import std.database.postgres; 5 | import std.stdio; 6 | 7 | import std.experimental.logger; 8 | 9 | unittest { 10 | import std.database.testsuite; 11 | alias DB = Database!DefaultPolicy; 12 | testAll!DB("postgres"); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /reference/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reference", 3 | "description": "Phobos std.database candidate", 4 | "homepage": "", 5 | "copyright": "Copyright © 2015-, Erik Smith", 6 | "authors": [ 7 | "Erik Smith" 8 | ], 9 | "sourcePaths": [".", "../src"], 10 | "dependencies": { 11 | }, 12 | "configurations": [ 13 | { 14 | "name": "library", 15 | "targetType": "executable", 16 | }, 17 | { 18 | "name": "unittest", 19 | "targetType": "executable", 20 | "targetName": "unittest", 21 | "dflags": ["-main"], 22 | }, 23 | ], 24 | } 25 | 26 | -------------------------------------------------------------------------------- /reference/test.d: -------------------------------------------------------------------------------- 1 | module std.database.reference.test; 2 | import std.database.reference; 3 | import std.database.util; 4 | import std.stdio; 5 | import std.experimental.logger; 6 | 7 | unittest { 8 | import std.database.reference; 9 | import std.database.util; 10 | 11 | auto db = createDatabase(); 12 | auto db1 = createDatabase("defaultUrl"); 13 | auto db2 = createDatabase!DefaultPolicy(); 14 | 15 | auto con = db.connection("source"); 16 | 17 | con.query("drop table t"); 18 | 19 | auto stmt = statement(con, "select * from t"); 20 | auto rows = stmt.query().rows; 21 | 22 | //auto range = res[]; 23 | //writeRows(range); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /sqlite/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sqlite", 3 | "description": "Phobos std.database candidate", 4 | "homepage": "", 5 | "copyright": "Copyright © 2015-, Erik Smith", 6 | "authors": [ 7 | "Erik Smith" 8 | ], 9 | "sourcePaths": [".", "../src"], 10 | "libs" : ["sqlite3"], 11 | "dependencies": { 12 | }, 13 | "configurations": [ 14 | { 15 | "name": "library", 16 | "targetType": "executable", 17 | }, 18 | { 19 | "name": "unittest", 20 | "targetType": "executable", 21 | "targetName": "unittest", 22 | "dflags": ["-main"], 23 | }, 24 | ], 25 | } 26 | 27 | -------------------------------------------------------------------------------- /sqlite/test.d: -------------------------------------------------------------------------------- 1 | module std.database.sqlite.test; 2 | import std.database.sqlite; 3 | import std.database.util; 4 | import std.stdio; 5 | 6 | unittest { 7 | import std.database.testsuite; 8 | alias DB = Database!DefaultPolicy; 9 | testAll!DB("sqlite"); 10 | } 11 | 12 | unittest { 13 | auto db = createDatabase("path:///testdb"); 14 | //auto rows = db.connection().statement("select name,score from score").query.rows; 15 | auto rows = db.connection().query("select name,score from score").rows; 16 | foreach (r; rows) { 17 | writeln(r[0].as!string,",",r[1].as!int); 18 | //writeln(r[0],", ",r[1]); 19 | } 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/std/database/BasicDatabase.d: -------------------------------------------------------------------------------- 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 | 767 | -------------------------------------------------------------------------------- /src/std/database/allocator.d: -------------------------------------------------------------------------------- 1 | module std.database.allocator; 2 | //import std.experimental.allocator.common; 3 | import std.experimental.logger; 4 | 5 | struct MyMallocator { 6 | //enum uint alignment = platformAlignment; 7 | 8 | @trusted // removed @nogc and nothrow for logging 9 | void[] allocate(size_t bytes) { 10 | import core.stdc.stdlib : malloc; 11 | if (!bytes) return null; 12 | auto p = malloc(bytes); 13 | return p ? p[0 .. bytes] : null; 14 | } 15 | 16 | /// Ditto 17 | @system // removed @nogc and nothrow for logging 18 | bool deallocate(void[] b) { 19 | import core.stdc.stdlib : free; 20 | //log("deallocate: ptr: ", b.ptr, "size: ", b.length); 21 | free(b.ptr); 22 | return true; 23 | } 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/std/database/array.d: -------------------------------------------------------------------------------- 1 | module std.database.array; 2 | import std.traits; 3 | 4 | // not usable yet 5 | 6 | struct S { 7 | int a, b; 8 | } 9 | 10 | auto toStaticArray(alias array)() 11 | { 12 | struct S { int a, b; } 13 | 14 | immutable tab = { 15 | static enum S[] s = array; 16 | return cast(typeof(s[0])[s.length])s; 17 | }(); 18 | return tab; 19 | } 20 | 21 | //enum a = toStaticArray!([{1,2},{3,4}]); 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/std/database/common.d: -------------------------------------------------------------------------------- 1 | module std.database.common; 2 | 3 | enum QueryVariableType { 4 | QuestionMark, 5 | Dollar, 6 | Named, 7 | } 8 | 9 | // this does not work currently 10 | // there is one with arrays in in forum 11 | // but waiting for better options 12 | /* Support empty Args? */ 13 | @nogc pure nothrow auto toInputRange(Args...)() { 14 | struct Range { 15 | size_t index; 16 | 17 | bool empty() {return index >= Args.length;} 18 | void popFront() {++index;} 19 | 20 | import std.traits : CommonType; 21 | alias E = CommonType!Args; 22 | 23 | E front() { 24 | final switch (index) { 25 | /* static */ foreach (i, arg; Args) { 26 | case i: 27 | writeln("FRONT: ", arg, "INDEX: ", i); 28 | return arg; 29 | } 30 | } 31 | } 32 | } 33 | 34 | return Range(); 35 | } 36 | 37 | /* 38 | unittest { 39 | import std.traits; 40 | import std.range; 41 | 42 | 43 | static assert(isInputRange!(ReturnType!(toInputRange!(1)))); 44 | 45 | static auto foo(Args...)(Args args) { 46 | import std.container.array; 47 | 48 | import std.traits : CommonType; 49 | alias E = CommonType!Args; 50 | writeln("common:", typeid(E)); 51 | 52 | return Array!E(toInputRange!(args)); 53 | } 54 | auto a = foo(1,2,3); 55 | //assert(a.length == 3 && a[0] == 1 && a[1] == 2 && a[2] == 3); 56 | //assert(a.length == 3 && a[0] -); 57 | 58 | 59 | for(int i = 0; i != a.length; ++i) { 60 | writeln("===================HERE: ", a[i]); 61 | } 62 | } 63 | */ 64 | -------------------------------------------------------------------------------- /src/std/database/exception.d: -------------------------------------------------------------------------------- 1 | module std.database.exception; 2 | import std.exception; 3 | 4 | struct DatabaseError { 5 | string message; 6 | } 7 | 8 | class DatabaseException : Exception { 9 | this(string msg, string file = __FILE__, size_t line = __LINE__) { 10 | super(msg, file, line); 11 | } 12 | 13 | this(ref DatabaseError error, string msg, string file = __FILE__, size_t line = __LINE__) { 14 | super(msg ~ ": " ~ error.message, file, line); 15 | } 16 | } 17 | 18 | class ConnectionException : DatabaseException { 19 | this(string msg, string file = __FILE__, size_t line = __LINE__) { 20 | super(msg, file, line); 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/std/database/freetds/bindings.d: -------------------------------------------------------------------------------- 1 | module std.database.freetds.bindings; 2 | import core.stdc.config; 3 | 4 | extern(System) { 5 | alias RETCODE = int; 6 | 7 | alias int DBINT; 8 | alias ubyte BYTE; 9 | alias int STATUS; 10 | 11 | enum { 12 | REG_ROW = -1, 13 | MORE_ROWS = -1, 14 | NO_MORE_ROWS = -2, 15 | BUF_FULL = -3, 16 | NO_MORE_RESULTS = 2, 17 | SUCCEED = 1, 18 | FAIL = 0 19 | } 20 | 21 | enum { 22 | INT_EXIT = 0, 23 | INT_CONTINUE = 1, 24 | INT_CANCEL = 2, 25 | INT_TIMEOUT = 3 26 | } 27 | 28 | enum { 29 | DBSETHOST = 1, /* this sets client host name */ 30 | DBSETUSER = 2, 31 | DBSETPWD = 3, 32 | DBSETHID = 4, /* not implemented */ 33 | DBSETAPP = 5, 34 | DBSETBCP = 6, 35 | DBSETNATLANG = 7, 36 | DBSETNOSHORT = 8, /* not implemented */ 37 | DBSETHIER = 9, /* not implemented */ 38 | DBSETCHARSET = 10, 39 | DBSETPACKET = 11, 40 | DBSETENCRYPT = 12, 41 | DBSETLABELED = 13, 42 | DBSETDBNAME = 14 43 | } 44 | 45 | enum { 46 | SYBCHAR = 47, 47 | SYBVARCHAR = 39, 48 | SYBINTN = 38, 49 | SYBINT1 = 48, 50 | SYBINT2 = 52, 51 | SYBINT4 = 56, 52 | SYBINT8 = 127, 53 | SYBFLT8 = 62, 54 | SYBDATETIME = 61, 55 | SYBBIT = 50, 56 | SYBBITN = 104, 57 | SYBTEXT = 35, 58 | SYBNTEXT = 99, 59 | SYBIMAGE = 34, 60 | SYBMONEY4 = 122, 61 | SYBMONEY = 60, 62 | SYBDATETIME4 = 58, 63 | SYBREAL = 59, 64 | SYBBINARY = 45, 65 | SYBVOID = 31, 66 | SYBVARBINARY = 37, 67 | SYBNUMERIC = 108, 68 | SYBDECIMAL = 106, 69 | SYBFLTN = 109, 70 | SYBMONEYN = 110, 71 | SYBDATETIMN = 111, 72 | SYBNVARCHAR = 103, 73 | 74 | SYBMSDATE = 40 75 | }; 76 | 77 | enum { 78 | NTBSTRINGBIND = 2, 79 | DATETIMEBIND = 11 80 | } 81 | 82 | struct DBDATETIME { 83 | DBINT dtdays; 84 | DBINT dttime; 85 | } 86 | 87 | struct tds_microsoft_dbdaterec { 88 | DBINT year; /* 1753 - 9999 */ 89 | DBINT quarter; /* 1 - 4 */ 90 | DBINT month; /* 1 - 12 */ 91 | DBINT day; /* 1 - 31 */ 92 | DBINT dayofyear; /* 1 - 366 */ 93 | DBINT week; /* 1 - 54 (for leap years) */ 94 | DBINT weekday; /* 1 - 7 (Mon. - Sun.) */ 95 | DBINT hour; /* 0 - 23 */ 96 | DBINT minute; /* 0 - 59 */ 97 | DBINT second; /* 0 - 59 */ 98 | DBINT millisecond; /* 0 - 999 */ 99 | DBINT tzone; /* -840 - 840 */ 100 | }; 101 | 102 | struct tds_microsoft_dbdaterec2 { 103 | DBINT year; /* 1753 - 9999 */ 104 | DBINT quarter; /* 1 - 4 */ 105 | DBINT month; /* 1 - 12 */ 106 | DBINT day; /* 1 - 31 */ 107 | DBINT dayofyear; /* 1 - 366 */ 108 | DBINT week; /* 1 - 54 (for leap years) */ 109 | DBINT weekday; /* 1 - 7 (Mon. - Sun.) */ 110 | DBINT hour; /* 0 - 23 */ 111 | DBINT minute; /* 0 - 59 */ 112 | DBINT second; /* 0 - 59 */ 113 | DBINT nanosecond; /* 0 - 999999999 */ 114 | DBINT tzone; /* 0 - 127 (Sybase only) */ 115 | }; 116 | 117 | alias tds_microsoft_dbdaterec DBDATEREC; 118 | alias tds_microsoft_dbdaterec2 DBDATEREC2; 119 | 120 | RETCODE dbinit(); 121 | void dbexit(); 122 | 123 | alias EHANDLEFUNC = int function(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr); 124 | 125 | alias MHANDLEFUNC = int function(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, 126 | char *proc, int line); 127 | 128 | void dbsetuserdata(DBPROCESS * dbproc, BYTE * ptr); 129 | BYTE *dbgetuserdata(DBPROCESS * dbproc); 130 | 131 | EHANDLEFUNC dberrhandle(EHANDLEFUNC handler); 132 | MHANDLEFUNC dbmsghandle(MHANDLEFUNC handler); 133 | 134 | struct TDSLOGIN; 135 | 136 | struct LOGINREC { 137 | TDSLOGIN *tds_login; 138 | }; 139 | 140 | LOGINREC *dblogin(); 141 | void dbloginfree(LOGINREC * login); 142 | 143 | RETCODE dbsetlname(LOGINREC * login, const char *value, int which); 144 | 145 | struct DBPROCESS; 146 | alias tds_dblib_dbprocess = DBPROCESS; 147 | 148 | DBPROCESS *dbopen(LOGINREC * login, const char *server); 149 | DBPROCESS *tdsdbopen(LOGINREC * login, const char *server, int msdblib); 150 | void dbclose(DBPROCESS * dbproc); 151 | 152 | RETCODE dbuse(DBPROCESS* dbproc, const char *name); 153 | 154 | // something about array def is a problem 155 | //RETCODE dbcmd(DBPROCESS * dbproc, const char[] cmdstring); 156 | RETCODE dbcmd(DBPROCESS * dbproc, const char* cmdstring); 157 | 158 | RETCODE dbsqlexec(DBPROCESS * dbproc); 159 | 160 | RETCODE dbresults(DBPROCESS * dbproc); 161 | int dbnumcols(DBPROCESS * dbproc); 162 | 163 | char *dbcolname(DBPROCESS * dbproc, int column); 164 | int dbcoltype(DBPROCESS * dbproc, int column); 165 | DBINT dbcollen(DBPROCESS * dbproc, int column); 166 | 167 | RETCODE dbbind(DBPROCESS * dbproc, int column, int vartype, DBINT varlen, BYTE * varaddr); 168 | RETCODE dbnullbind(DBPROCESS * dbproc, int column, DBINT * indicator); 169 | 170 | STATUS dbnextrow(DBPROCESS * dbproc); 171 | 172 | // date functions 173 | RETCODE dbdatecrack(DBPROCESS * dbproc, DBDATEREC * di, DBDATETIME * dt); 174 | RETCODE dbanydatecrack(DBPROCESS * dbproc, DBDATEREC2 * di, int type, const void *data); 175 | 176 | // tds functions (from tds.h), not accessible from dblib :) 177 | alias ubyte TDS_TINYINT; 178 | 179 | void tds_set_packet(TDSLOGIN * tds_login, int packet_size); 180 | void tds_set_port(TDSLOGIN * tds_login, int port); 181 | bool tds_set_passwd(TDSLOGIN * tds_login, const char *password); 182 | void tds_set_bulk(TDSLOGIN * tds_login, TDS_TINYINT enabled); 183 | bool tds_set_user(TDSLOGIN * tds_login, const char *username); 184 | bool tds_set_app(TDSLOGIN * tds_login, const char *application); 185 | bool tds_set_host(TDSLOGIN * tds_login, const char *hostname); 186 | bool tds_set_library(TDSLOGIN * tds_login, const char *library); 187 | bool tds_set_server(TDSLOGIN * tds_login, const char *server); 188 | bool tds_set_client_charset(TDSLOGIN * tds_login, const char *charset); 189 | bool tds_set_language(TDSLOGIN * tds_login, const char *language); 190 | void tds_set_version(TDSLOGIN * tds_login, TDS_TINYINT major_ver, TDS_TINYINT minor_ver); 191 | //int tds_connect_and_login(TDSSOCKET * tds, TDSLOGIN * login); 192 | 193 | } 194 | 195 | 196 | -------------------------------------------------------------------------------- /src/std/database/freetds/database.d: -------------------------------------------------------------------------------- 1 | module std.database.freetds.database; 2 | pragma(lib, "sybdb"); 3 | 4 | import std.database.common; 5 | import std.database.exception; 6 | import std.database.source; 7 | 8 | import std.database.freetds.bindings; 9 | 10 | import std.string; 11 | import core.stdc.stdlib; 12 | import std.conv; 13 | import std.typecons; 14 | import std.container.array; 15 | import std.experimental.logger; 16 | public import std.database.allocator; 17 | import std.database.BasicDatabase; 18 | import std.datetime; 19 | 20 | struct DefaultPolicy { 21 | alias Allocator = MyMallocator; 22 | } 23 | 24 | alias Database(T) = BasicDatabase!(Driver!T); 25 | 26 | auto createDatabase()(string defaultURI="") { 27 | return Database!DefaultPolicy(defaultURI); 28 | } 29 | 30 | 31 | struct Driver(P) { 32 | alias Policy = P; 33 | alias Allocator = Policy.Allocator; 34 | alias Cell = BasicCell!(Driver); 35 | 36 | private static bool isError(RETCODE ret) { 37 | return 38 | ret != SUCCEED && 39 | ret != REG_ROW && 40 | ret != NO_MORE_ROWS && 41 | ret != NO_MORE_RESULTS; 42 | } 43 | 44 | static T* check(T)(string msg, T* object) { 45 | info(msg); 46 | if (object == null) throw new DatabaseException("error: " ~ msg); 47 | return object; 48 | } 49 | 50 | static RETCODE check(string msg, RETCODE ret) { 51 | info(msg, " : ", ret); 52 | if (isError(ret)) throw new DatabaseException("error: " ~ msg); 53 | return ret; 54 | } 55 | 56 | struct Database { 57 | alias queryVariableType = QueryVariableType.QuestionMark; 58 | 59 | static const FeatureArray features = [ 60 | //Feature.InputBinding, 61 | //Feature.DateBinding, 62 | //Feature.ConnectionPool, 63 | ]; 64 | 65 | Allocator allocator; 66 | 67 | this(string defaultURI_) { 68 | info("Database"); 69 | allocator = Allocator(); 70 | check("dbinit", dbinit()); 71 | dberrhandle(&errorHandler); 72 | dbmsghandle(&msgHandler); 73 | } 74 | 75 | ~this() { 76 | info("~Database"); 77 | //dbexit(); // should this be called (only on process exit?) 78 | } 79 | 80 | 81 | static extern(C) int errorHandler( 82 | DBPROCESS* dbproc, 83 | int severity, 84 | int dberr, 85 | int oserr, 86 | char *dberrstr, 87 | char *oserrstr) { 88 | 89 | auto con = cast(Connection *) dbgetuserdata(dbproc); 90 | if (con) { 91 | con.error.message = to!string(dberrstr); 92 | } 93 | info("error: ", 94 | "severity: ", severity, 95 | ", db error: ", to!string(dberrstr), 96 | ", os error: ", to!string(oserrstr)); 97 | 98 | return INT_CANCEL; 99 | } 100 | 101 | static extern(C) int msgHandler( 102 | DBPROCESS *dbproc, 103 | DBINT msgno, 104 | int msgstate, 105 | int severity, 106 | char *msgtext, 107 | char *srvname, 108 | char *procname, 109 | int line) { 110 | 111 | auto con = cast(Connection *) dbgetuserdata(dbproc); 112 | if (con) {} 113 | info("msg: ", to!string(msgtext), ", severity:", severity); 114 | return 0; 115 | } 116 | 117 | } 118 | 119 | struct Connection { 120 | Database* db; 121 | Source source; 122 | LOGINREC *login; 123 | DBPROCESS *con; 124 | 125 | DatabaseError error; 126 | 127 | this(Database* db_, Source source_) { 128 | db = db_; 129 | source = source_; 130 | 131 | //info("Connection: "); 132 | 133 | login = check("dblogin", dblogin()); 134 | 135 | check("dbsetlname", dbsetlname(login, toStringz(source.username), DBSETUSER)); 136 | check("dbsetlname", dbsetlname(login, toStringz(source.password), DBSETPWD)); 137 | 138 | // if host is present, then specify server as direct host:port 139 | // otherwise just past a name for freetds name lookup 140 | 141 | string server; 142 | if (source.host.length != 0) { 143 | server = source.host ~ ":" ~ to!string(source.port); 144 | } else { 145 | server = source.server; 146 | } 147 | 148 | //con = dbopen(login, toStringz(source.server)); 149 | con = check("tdsdbopen: " ~ server, tdsdbopen(login, toStringz(server), 1)); 150 | 151 | dbsetuserdata(con, cast(BYTE*) &this); 152 | 153 | check("dbuse", dbuse(con, toStringz(source.database))); 154 | } 155 | 156 | ~this() { 157 | //info("~Connection: ", source); 158 | if (con) dbclose(con); 159 | if (login) dbloginfree(login); 160 | } 161 | 162 | } 163 | 164 | struct Statement { 165 | Connection* con; 166 | string sql; 167 | Allocator *allocator; 168 | int binds; 169 | //Array!Bind inputbind_; 170 | 171 | this(Connection* con_, string sql_) { 172 | info("Statement"); 173 | con = con_; 174 | sql = sql_; 175 | allocator = &con.db.allocator; 176 | } 177 | 178 | ~this() { 179 | info("~Statement"); 180 | } 181 | 182 | void prepare() { 183 | check("dbcmd: " ~ sql, dbcmd(con.con, toStringz(sql))); 184 | } 185 | 186 | void query() { 187 | RETCODE status = check("dbsqlexec: ", dbsqlexec(con.con)); 188 | } 189 | 190 | void query(X...) (X args) { 191 | bindAll(args); 192 | query(); 193 | } 194 | 195 | //bool hasRows() {return status != NO_MORE_RESULTS;} 196 | bool hasRows() {return true;} 197 | 198 | private void bindAll(T...) (T args) { 199 | //int col; 200 | //foreach (arg; args) bind(++col, arg); 201 | } 202 | 203 | void bind(int n, int value) {} 204 | void bind(int n, const char[] value) {} 205 | 206 | void reset() { 207 | } 208 | 209 | private RETCODE check(string msg, RETCODE ret) { 210 | info(msg, " : ", ret); 211 | if (isError(ret)) throw new DatabaseException(con.error, msg); 212 | return ret; 213 | } 214 | } 215 | 216 | struct Describe { 217 | char *name; 218 | char *buffer; 219 | int type; 220 | int size; 221 | int status; 222 | } 223 | 224 | struct Bind { 225 | ValueType type; 226 | int bindType; 227 | int size; 228 | void[] data; 229 | DBINT status; 230 | } 231 | 232 | struct Result { 233 | //int columns() {return columns;} 234 | 235 | Statement* stmt; 236 | Allocator *allocator; 237 | int columns; 238 | Array!Describe describe; 239 | Array!Bind bind; 240 | RETCODE status; 241 | 242 | private auto con() {return stmt.con;} 243 | private auto dbproc() {return con.con;} 244 | 245 | this(Statement* stmt_, int rowArraySize_) { 246 | stmt = stmt_; 247 | allocator = stmt.allocator; 248 | status = check("dbresults", dbresults(dbproc)); 249 | columns = dbnumcols(dbproc); 250 | build_describe(); 251 | build_bind(); 252 | } 253 | 254 | ~this() { 255 | foreach(b; bind) allocator.deallocate(b.data); 256 | } 257 | 258 | void build_describe() { 259 | 260 | describe.reserve(columns); 261 | 262 | for(int i = 0; i < columns; ++i) { 263 | int c = i+1; 264 | describe ~= Describe(); 265 | auto d = &describe.back(); 266 | 267 | d.name = dbcolname(dbproc, c); 268 | d.type = dbcoltype(dbproc, c); 269 | d.size = dbcollen(dbproc, c); 270 | 271 | info("NAME: ", to!string(d.name), ", type: ", d.type); 272 | } 273 | } 274 | 275 | void build_bind() { 276 | import core.memory : GC; 277 | 278 | bind.reserve(columns); 279 | 280 | for(int i = 0; i < columns; ++i) { 281 | int c = i+1; 282 | bind ~= Bind(); 283 | auto b = &bind.back(); 284 | auto d = &describe[i]; 285 | 286 | int allocSize; 287 | switch (d.type) { 288 | case SYBCHAR: 289 | b.type = ValueType.String; 290 | b.size = 255; 291 | allocSize = b.size+1; 292 | b.bindType = NTBSTRINGBIND; 293 | break; 294 | case SYBMSDATE: 295 | b.type = ValueType.Date; 296 | b.size = DBDATETIME.sizeof; 297 | allocSize = b.size; 298 | b.bindType = DATETIMEBIND; 299 | break; 300 | default: 301 | b.type = ValueType.String; 302 | b.size = 255; 303 | allocSize = b.size+1; 304 | b.bindType = NTBSTRINGBIND; 305 | break; 306 | } 307 | 308 | b.data = allocator.allocate(allocSize); // make consistent acros dbs 309 | GC.addRange(b.data.ptr, b.data.length); 310 | 311 | check("dbbind", dbbind( 312 | dbproc, 313 | c, 314 | b.bindType, 315 | cast(DBINT) b.data.length, 316 | cast(BYTE*) b.data.ptr)); 317 | 318 | check("dbnullbind", dbnullbind(dbproc, c, &b.status)); 319 | 320 | info( 321 | "output bind: index: ", i, 322 | ", type: ", b.bindType, 323 | ", size: ", b.size, 324 | ", allocSize: ", b.data.length); 325 | } 326 | } 327 | 328 | int fetch() { 329 | status = check("dbnextrow", dbnextrow(dbproc)); 330 | if (status == REG_ROW) { 331 | return 1; 332 | } else if (status == NO_MORE_ROWS) { 333 | stmt.reset(); 334 | return 0; 335 | } 336 | return 0; 337 | } 338 | 339 | auto name(size_t idx) { 340 | return to!string(describe[idx].name); 341 | } 342 | 343 | auto get(X:string)(Cell* cell) { 344 | import core.stdc.string: strlen; 345 | checkType(cell.bind.bindType, NTBSTRINGBIND); 346 | auto ptr = cast(immutable char*) cell.bind.data.ptr; 347 | return cast(string) ptr[0..strlen(ptr)]; 348 | } 349 | 350 | auto get(X:int)(Cell* cell) { 351 | //if (b.bindType == SQL_C_CHAR) return to!int(as!string()); // tmp hack 352 | //checkType(b.bindType, SQL_C_LONG); 353 | //return *(cast(int*) b.data); 354 | return 0; 355 | } 356 | 357 | auto get(X:Date)(Cell* cell) { 358 | auto ptr = cast(DBDATETIME*) cell.bind.data.ptr; 359 | DBDATEREC d; 360 | check("dbdatecrack", dbdatecrack(dbproc, &d, ptr)); 361 | return Date(d.year, d.month, d.day); 362 | } 363 | 364 | void checkType(int a, int b) { 365 | if (a != b) throw new DatabaseException("type mismatch"); 366 | } 367 | 368 | } 369 | 370 | } 371 | 372 | -------------------------------------------------------------------------------- /src/std/database/freetds/package.d: -------------------------------------------------------------------------------- 1 | module std.database.freetds; 2 | public import std.database.freetds.database; 3 | 4 | -------------------------------------------------------------------------------- /src/std/database/mysql/bindings.d: -------------------------------------------------------------------------------- 1 | module std.database.mysql.bindings; 2 | import core.stdc.config; 3 | 4 | extern(System) { 5 | struct MYSQL; 6 | struct MYSQL_RES; 7 | struct NET; 8 | /* typedef */ alias const(ubyte)* cstring; 9 | alias ubyte my_bool; 10 | 11 | enum MYSQL_NO_DATA = 100; 12 | enum MYSQL_DATA_TRUNCATED = 101; 13 | 14 | //enum enum_field_types 15 | enum { 16 | MYSQL_TYPE_DECIMAL, 17 | MYSQL_TYPE_TINY, 18 | MYSQL_TYPE_SHORT, 19 | MYSQL_TYPE_LONG, 20 | MYSQL_TYPE_FLOAT, 21 | MYSQL_TYPE_DOUBLE, 22 | MYSQL_TYPE_NULL, 23 | MYSQL_TYPE_TIMESTAMP, 24 | MYSQL_TYPE_LONGLONG, 25 | MYSQL_TYPE_INT24, 26 | MYSQL_TYPE_DATE, 27 | MYSQL_TYPE_TIME, 28 | MYSQL_TYPE_DATETIME, 29 | MYSQL_TYPE_YEAR, 30 | MYSQL_TYPE_NEWDATE, 31 | MYSQL_TYPE_VARCHAR, 32 | MYSQL_TYPE_BIT, 33 | MYSQL_TYPE_TIMESTAMP2, 34 | MYSQL_TYPE_DATETIME2, 35 | MYSQL_TYPE_TIME2, 36 | MYSQL_TYPE_NEWDECIMAL=246, 37 | MYSQL_TYPE_ENUM=247, 38 | MYSQL_TYPE_SET=248, 39 | MYSQL_TYPE_TINY_BLOB=249, 40 | MYSQL_TYPE_MEDIUM_BLOB=250, 41 | MYSQL_TYPE_LONG_BLOB=251, 42 | MYSQL_TYPE_BLOB=252, 43 | MYSQL_TYPE_VAR_STRING=253, 44 | MYSQL_TYPE_STRING=254, 45 | MYSQL_TYPE_GEOMETRY=255 46 | }; 47 | 48 | 49 | struct MYSQL_FIELD { 50 | cstring name; /* Name of column */ 51 | cstring org_name; /* Original column name, if an alias */ 52 | cstring table; /* Table of column if column was a field */ 53 | cstring org_table; /* Org table name, if table was an alias */ 54 | cstring db; /* Database for table */ 55 | cstring catalog; /* Catalog for table */ 56 | cstring def; /* Default value (set by mysql_list_fields) */ 57 | c_ulong length; /* Width of column (create length) */ 58 | c_ulong max_length; /* Max width for selected set */ 59 | uint name_length; 60 | uint org_name_length; 61 | uint table_length; 62 | uint org_table_length; 63 | uint db_length; 64 | uint catalog_length; 65 | uint def_length; 66 | uint flags; /* Div flags */ 67 | uint decimals; /* Number of decimals in field */ 68 | uint charsetnr; /* Character set */ 69 | uint type; /* Type of field. See mysql_com.h for types */ 70 | // type is actually an enum btw 71 | 72 | //version(MySQL_51) { 73 | void* extension; 74 | //} 75 | 76 | version (webscalesql) { 77 | /* The position in buff we continue reads from when data is next 78 | * available */ 79 | ubyte *cur_pos; 80 | 81 | /* Blocking state */ 82 | net_async_block_state async_blocking_state; 83 | } 84 | 85 | } 86 | 87 | struct MYSQL_BIND { 88 | c_ulong *length; /* output length pointer */ 89 | my_bool *is_null; /* Pointer to null indicator */ 90 | void *buffer; /* buffer to get/put data */ 91 | /* set this if you want to track data truncations happened during fetch */ 92 | my_bool *error; 93 | ubyte *row_ptr; /* for the current data position */ 94 | void function(NET *net, MYSQL_BIND* param) store_param_func; 95 | void function(MYSQL_BIND*, MYSQL_FIELD *, ubyte **row) fetch_result; 96 | void function(MYSQL_BIND*, MYSQL_FIELD *, ubyte **row) skip_result; 97 | /* output buffer length, must be set when fetching str/binary */ 98 | c_ulong buffer_length; 99 | c_ulong offset; /* offset position for char/binary fetch */ 100 | c_ulong length_value; /* Used if length is 0 */ 101 | uint param_number; /* For null count and error messages */ 102 | uint pack_length; /* Internal length for packed data */ 103 | 104 | //enum enum_field_types buffer_type; /* buffer type */ 105 | int buffer_type; 106 | 107 | my_bool error_value; /* used if error is 0 */ 108 | my_bool is_unsigned; /* set if integer type is unsigned */ 109 | my_bool long_data_used; /* If used with mysql_send_long_data */ 110 | my_bool is_null_value; /* Used if is_null is 0 */ 111 | void *extension; 112 | } 113 | 114 | /* typedef */ alias cstring* MYSQL_ROW; 115 | 116 | const(char *) mysql_get_client_info(); 117 | 118 | MYSQL* mysql_init(MYSQL*); 119 | uint mysql_errno(MYSQL*); 120 | cstring mysql_error(MYSQL*); 121 | 122 | MYSQL* mysql_real_connect(MYSQL*, cstring, cstring, cstring, cstring, uint, cstring, c_ulong); 123 | 124 | int mysql_query(MYSQL*, cstring); 125 | 126 | void mysql_close(MYSQL*); 127 | 128 | ulong mysql_num_rows(MYSQL_RES*); 129 | uint mysql_num_fields(MYSQL_RES*); 130 | bool mysql_eof(MYSQL_RES*); 131 | 132 | ulong mysql_affected_rows(MYSQL*); 133 | ulong mysql_insert_id(MYSQL*); 134 | 135 | MYSQL_RES* mysql_store_result(MYSQL*); 136 | MYSQL_RES* mysql_use_result(MYSQL*); 137 | 138 | MYSQL_ROW mysql_fetch_row(MYSQL_RES *); 139 | c_ulong* mysql_fetch_lengths(MYSQL_RES*); 140 | MYSQL_FIELD* mysql_fetch_field(MYSQL_RES*); 141 | MYSQL_FIELD* mysql_fetch_fields(MYSQL_RES*); 142 | 143 | uint mysql_real_escape_string(MYSQL*, ubyte* to, cstring from, c_ulong length); 144 | 145 | void mysql_free_result(MYSQL_RES*); 146 | 147 | struct MYSQL_STMT; 148 | MYSQL_STMT* mysql_stmt_init(MYSQL *); 149 | my_bool mysql_stmt_close(MYSQL_STMT *); 150 | int mysql_stmt_prepare(MYSQL_STMT *, const char *, ulong); 151 | const(char *) mysql_stmt_error(MYSQL_STMT *); 152 | int mysql_stmt_execute(MYSQL_STMT *); 153 | 154 | MYSQL_RES* mysql_stmt_result_metadata(MYSQL_STMT *); 155 | 156 | my_bool mysql_stmt_bind_result(MYSQL_STMT *, MYSQL_BIND *); 157 | 158 | int mysql_stmt_fetch(MYSQL_STMT *); 159 | c_ulong mysql_stmt_field_count(MYSQL_STMT*); 160 | c_ulong mysql_stmt_param_count(MYSQL_STMT *); 161 | 162 | my_bool mysql_stmt_bind_param(MYSQL_STMT *, MYSQL_BIND *); 163 | 164 | enum MYSQL_TIMESTAMP_TYPE { 165 | MYSQL_TIMESTAMP_NONE = -2, 166 | MYSQL_TIMESTAMP_ERROR = -1, 167 | MYSQL_TIMESTAMP_DATE = 0, 168 | MYSQL_TIMESTAMP_DATETIME= 1, 169 | MYSQL_TIMESTAMP_TIME = 2 170 | }; 171 | 172 | struct MYSQL_TIME { 173 | uint year, month, day, hour, minute, second; 174 | uint second_part; 175 | my_bool neg; 176 | MYSQL_TIMESTAMP_TYPE time_type; 177 | }; 178 | 179 | version (webscalesql) { 180 | 181 | /* Is the async operation still pending? */ 182 | enum net_async_status_enum { 183 | NET_ASYNC_COMPLETE = 20100, 184 | NET_ASYNC_NOT_READY 185 | }; 186 | alias net_async_status_enum net_async_status; 187 | 188 | /* For an async operation, what we are waiting for, if anything. */ 189 | enum net_async_operation_enum { 190 | NET_ASYNC_OP_IDLE = 0, 191 | NET_ASYNC_OP_READING = 20200, 192 | NET_ASYNC_OP_WRITING, 193 | NET_ASYNC_OP_COMPLETE 194 | }; 195 | alias net_async_operation_enum net_async_operation; 196 | 197 | /* Reading a packet is a multi-step process, so we have a state machine. */ 198 | enum net_async_read_packet_state_enum { 199 | NET_ASYNC_PACKET_READ_IDLE = 0, 200 | NET_ASYNC_PACKET_READ_HEADER = 20300, 201 | NET_ASYNC_PACKET_READ_BODY, 202 | NET_ASYNC_PACKET_READ_COMPLETE 203 | }; 204 | alias net_async_read_packet_state_enum net_async_read_packet_state; 205 | 206 | /* As is reading a query result. */ 207 | enum net_read_query_result_status_enum { 208 | NET_ASYNC_READ_QUERY_RESULT_IDLE = 0, 209 | NET_ASYNC_READ_QUERY_RESULT_FIELD_COUNT = 20240, 210 | NET_ASYNC_READ_QUERY_RESULT_FIELD_INFO 211 | }; 212 | alias net_read_query_result_status_enum net_read_query_result_status; 213 | 214 | /* Sending a command involves the write as well as reading the status. */ 215 | enum net_send_command_status_enum { 216 | NET_ASYNC_SEND_COMMAND_IDLE = 0, 217 | NET_ASYNC_SEND_COMMAND_WRITE_COMMAND = 20340, 218 | NET_ASYNC_SEND_COMMAND_READ_STATUS = 20340 219 | }; 220 | alias net_send_command_status_enum net_send_command_status; 221 | 222 | enum net_async_block_state_enum { 223 | NET_NONBLOCKING_CONNECT = 20130, 224 | NET_NONBLOCKING_READ = 20140, 225 | NET_NONBLOCKING_WRITE = 20150 226 | }; 227 | 228 | alias net_async_block_state_enum net_async_block_state; 229 | 230 | MYSQL* mysql_real_connect_nonblocking_init( 231 | MYSQL *mysql, 232 | cstring host, 233 | cstring user, 234 | cstring passwd, 235 | cstring db, 236 | uint port, 237 | cstring unix_socket, 238 | c_ulong clientflag); 239 | 240 | net_async_status mysql_real_connect_nonblocking_run( 241 | MYSQL *mysql, 242 | int *error); 243 | 244 | net_async_status mysql_send_query_nonblocking( 245 | MYSQL* mysql, 246 | const char* query, 247 | int *error); 248 | 249 | net_async_status mysql_real_query_nonblocking( 250 | MYSQL *mysql, 251 | const char* query, 252 | ulong length, 253 | int *error); 254 | 255 | net_async_status mysql_next_result_nonblocking( 256 | MYSQL *mysql, 257 | int* error); 258 | 259 | net_async_status mysql_select_db_nonblocking( 260 | MYSQL *mysql, 261 | const char *db, 262 | my_bool* error); 263 | 264 | int mysql_get_file_descriptor(MYSQL *mysql); 265 | 266 | net_async_status mysql_free_result_nonblocking(MYSQL_RES *result); 267 | 268 | net_async_status mysql_fetch_row_nonblocking( 269 | MYSQL_RES *res, 270 | MYSQL_ROW* row); 271 | } 272 | 273 | } 274 | 275 | -------------------------------------------------------------------------------- /src/std/database/mysql/package.d: -------------------------------------------------------------------------------- 1 | module std.database.mysql; 2 | public import std.database.mysql.database; 3 | 4 | -------------------------------------------------------------------------------- /src/std/database/odbc/database.d: -------------------------------------------------------------------------------- 1 | module std.database.odbc.database; 2 | pragma(lib, "odbc"); 3 | 4 | import std.database.common; 5 | import std.database.exception; 6 | import std.database.source; 7 | import etc.c.odbc.sql; 8 | import etc.c.odbc.sqlext; 9 | import etc.c.odbc.sqltypes; 10 | import etc.c.odbc.sqlucode; 11 | 12 | import std.string; 13 | import core.stdc.stdlib; 14 | import std.conv; 15 | import std.typecons; 16 | import std.container.array; 17 | import std.experimental.logger; 18 | public import std.database.allocator; 19 | import std.database.BasicDatabase; 20 | import std.datetime; 21 | 22 | //alias long SQLLEN; 23 | //alias ubyte SQLULEN; 24 | 25 | alias SQLINTEGER SQLLEN; 26 | alias SQLUINTEGER SQLULEN; 27 | 28 | struct DefaultPolicy { 29 | alias Allocator = MyMallocator; 30 | } 31 | 32 | alias Database(T) = BasicDatabase!(Driver!T); 33 | 34 | auto createDatabase()(string defaultURI="") { 35 | return Database!DefaultPolicy(defaultURI); 36 | } 37 | 38 | struct Driver(P) { 39 | alias Policy = P; 40 | alias Allocator = Policy.Allocator; 41 | alias Cell = BasicCell!(Driver); 42 | 43 | struct Database { 44 | alias queryVariableType = QueryVariableType.QuestionMark; 45 | 46 | static const FeatureArray features = [ 47 | //Feature.InputBinding, 48 | //Feature.DateBinding, 49 | Feature.ConnectionPool, 50 | ]; 51 | 52 | Allocator allocator; 53 | SQLHENV env; 54 | 55 | this(string defaultURI_) { 56 | info("Database"); 57 | allocator = Allocator(); 58 | check( 59 | "SQLAllocHandle", 60 | SQLAllocHandle( 61 | SQL_HANDLE_ENV, 62 | SQL_NULL_HANDLE, 63 | &env)); 64 | SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, cast(void *) SQL_OV_ODBC3, 0); 65 | } 66 | 67 | ~this() { 68 | info("~Database"); 69 | if (!env) return; 70 | check("SQLFreeHandle", SQL_HANDLE_ENV, env, SQLFreeHandle(SQL_HANDLE_ENV, env)); 71 | env = null; 72 | } 73 | 74 | void showDrivers() { 75 | import core.stdc.string: strlen; 76 | 77 | SQLUSMALLINT direction; 78 | 79 | SQLCHAR[256] driver; 80 | SQLCHAR[256] attr; 81 | SQLSMALLINT driver_ret; 82 | SQLSMALLINT attr_ret; 83 | SQLRETURN ret; 84 | 85 | direction = SQL_FETCH_FIRST; 86 | info("DRIVERS:"); 87 | while(SQL_SUCCEEDED(ret = SQLDrivers( 88 | env, 89 | direction, 90 | driver.ptr, 91 | driver.sizeof, 92 | &driver_ret, 93 | attr.ptr, 94 | attr.sizeof, 95 | &attr_ret))) { 96 | direction = SQL_FETCH_NEXT; 97 | info(driver.ptr[0..strlen(driver.ptr)], ": ", attr.ptr[0..strlen(attr.ptr)]); 98 | //if (ret == SQL_SUCCESS_WITH_INFO) printf("\tdata truncation\n"); 99 | } 100 | } 101 | } 102 | 103 | struct Connection { 104 | Database* db; 105 | Source source; 106 | SQLHDBC con; 107 | bool connected; 108 | 109 | this(Database* db_, Source source_) { 110 | db = db_; 111 | source = source_; 112 | 113 | info("Connection: ", source); 114 | 115 | char[1024] outstr; 116 | SQLSMALLINT outstrlen; 117 | //string DSN = "DSN=testdb"; 118 | 119 | SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_DBC,db.env,&con); 120 | if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO)) { 121 | throw new DatabaseException("SQLAllocHandle error: " ~ to!string(ret)); 122 | } 123 | 124 | check("SQLConnect", SQL_HANDLE_DBC, con, SQLConnect( 125 | con, 126 | cast(SQLCHAR*) toStringz(source.server), 127 | SQL_NTS, 128 | cast(SQLCHAR*) toStringz(source.username), 129 | SQL_NTS, 130 | cast(SQLCHAR*) toStringz(source.password), 131 | SQL_NTS)); 132 | connected = true; 133 | } 134 | 135 | ~this() { 136 | info("~Connection: ", source); 137 | if (connected) check("SQLDisconnect()", SQL_HANDLE_DBC, con, SQLDisconnect(con)); 138 | check("SQLFreeHandle", SQLFreeHandle(SQL_HANDLE_DBC, con)); 139 | } 140 | } 141 | 142 | struct Statement { 143 | Connection* con; 144 | string sql; 145 | Allocator *allocator; 146 | SQLHSTMT stmt; 147 | bool hasRows_; // not working 148 | int binds; 149 | Array!Bind inputbind_; 150 | 151 | this(Connection* con_, string sql_) { 152 | info("Statement"); 153 | con = con_; 154 | sql = sql_; 155 | allocator = &con.db.allocator; 156 | check("SQLAllocHandle", SQLAllocHandle(SQL_HANDLE_STMT, con.con, &stmt)); 157 | } 158 | 159 | ~this() { 160 | info("~Statement"); 161 | for(int i = 0; i < inputbind_.length; ++i) { 162 | allocator.deallocate(inputbind_[i].data[0..inputbind_[i].allocSize]); 163 | } 164 | if (stmt) check("SQLFreeHandle", SQLFreeHandle(SQL_HANDLE_STMT, stmt)); 165 | // stmt = null? needed 166 | } 167 | 168 | void bind(int n, int value) { 169 | info("input bind: n: ", n, ", value: ", value); 170 | 171 | Bind b; 172 | b.bindType = SQL_C_LONG; 173 | b.dbtype = SQL_INTEGER; 174 | b.size = SQLINTEGER.sizeof; 175 | b.allocSize = b.size; 176 | b.data = cast(void*)(allocator.allocate(b.allocSize)); 177 | inputBind(n, b); 178 | 179 | *(cast(SQLINTEGER*) inputbind_[n-1].data) = value; 180 | } 181 | 182 | void bind(int n, const char[] value){ 183 | import core.stdc.string: strncpy; 184 | info("input bind: n: ", n, ", value: ", value); 185 | // no null termination needed 186 | 187 | Bind b; 188 | b.bindType = SQL_C_CHAR; 189 | b.dbtype = SQL_CHAR; 190 | b.size = cast(SQLSMALLINT) value.length; 191 | b.allocSize = b.size; 192 | b.data = cast(void*)(allocator.allocate(b.allocSize)); 193 | inputBind(n, b); 194 | 195 | strncpy(cast(char*) b.data, value.ptr, b.size); 196 | } 197 | 198 | void bind(int n, Date d) { 199 | } 200 | 201 | void inputBind(int n, ref Bind bind) { 202 | inputbind_ ~= bind; 203 | auto b = &inputbind_.back(); 204 | 205 | check("SQLBindParameter", SQLBindParameter( 206 | stmt, 207 | cast(SQLSMALLINT) n, 208 | SQL_PARAM_INPUT, 209 | b.bindType, 210 | b.dbtype, 211 | 0, 212 | 0, 213 | b.data, 214 | b.allocSize, 215 | null)); 216 | } 217 | 218 | 219 | void prepare() { 220 | //if (!data_.st) 221 | check("SQLPrepare", SQLPrepare( 222 | stmt, 223 | cast(SQLCHAR*) toStringz(sql), 224 | SQL_NTS)); 225 | 226 | SQLSMALLINT v; 227 | check("SQLNumParams", SQLNumParams(stmt, &v)); 228 | binds = v; 229 | check("SQLNumResultCols", SQLNumResultCols (stmt, &v)); 230 | info("binds: ", binds); 231 | } 232 | 233 | void query() { 234 | if (!binds) { 235 | info("sql execute direct: ", sql); 236 | SQLRETURN ret = SQLExecDirect( 237 | stmt, 238 | cast(SQLCHAR*) toStringz(sql), 239 | SQL_NTS); 240 | check("SQLExecuteDirect()", SQL_HANDLE_STMT, stmt, ret); 241 | hasRows_ = ret != SQL_NO_DATA; 242 | } else { 243 | info("sql execute prepared: ", sql); 244 | SQLRETURN ret = SQLExecute(stmt); 245 | check("SQLExecute()", SQL_HANDLE_STMT, stmt, ret); 246 | hasRows_ = ret != SQL_NO_DATA; 247 | } 248 | } 249 | 250 | void query(X...) (X args) { 251 | bindAll(args); 252 | query(); 253 | } 254 | 255 | bool hasRows() {return hasRows_;} 256 | 257 | void exec() { 258 | check("SQLExecDirect", SQLExecDirect(stmt,cast(SQLCHAR*) toStringz(sql), SQL_NTS)); 259 | } 260 | 261 | void reset() { 262 | //SQLCloseCursor 263 | } 264 | 265 | private void bindAll(T...) (T args) { 266 | int col; 267 | foreach (arg; args) { 268 | bind(++col, arg); 269 | } 270 | } 271 | } 272 | 273 | 274 | struct Describe { 275 | static const nameSize = 256; 276 | char[nameSize] name; 277 | SQLSMALLINT nameLen; 278 | SQLSMALLINT type; 279 | SQLULEN size; 280 | SQLSMALLINT digits; 281 | SQLSMALLINT nullable; 282 | SQLCHAR* data; 283 | //SQLCHAR[256] data; 284 | SQLLEN datLen; 285 | } 286 | 287 | struct Bind { 288 | ValueType type; 289 | 290 | SQLSMALLINT bindType; 291 | SQLSMALLINT dbtype; 292 | //SQLCHAR* data[maxData]; 293 | void* data; 294 | 295 | // apparently the crash problem 296 | //SQLULEN size; 297 | //SQLULEN allocSize; 298 | //SQLLEN len; 299 | 300 | SQLINTEGER size; 301 | SQLINTEGER allocSize; 302 | SQLINTEGER len; 303 | } 304 | 305 | struct Result { 306 | //int columns() {return columns;} 307 | 308 | static const maxData = 256; 309 | 310 | Statement* stmt; 311 | Allocator *allocator; 312 | int columns; 313 | Array!Describe describe; 314 | Array!Bind bind; 315 | SQLRETURN status; 316 | 317 | this(Statement* stmt_, int rowArraySize_) { 318 | stmt = stmt_; 319 | allocator = stmt.allocator; 320 | 321 | // check for result set (probably a better way) 322 | //if (!stmt.hasRows) return; 323 | SQLSMALLINT v; 324 | check("SQLNumResultCols", SQLNumResultCols (stmt.stmt, &v)); 325 | columns = v; 326 | build_describe(); 327 | build_bind(); 328 | } 329 | 330 | ~this() { 331 | for(int i = 0; i < bind.length; ++i) { 332 | free(bind[i].data); 333 | } 334 | } 335 | 336 | void build_describe() { 337 | 338 | describe.reserve(columns); 339 | 340 | for(int i = 0; i < columns; ++i) { 341 | describe ~= Describe(); 342 | auto d = &describe.back(); 343 | 344 | check("SQLDescribeCol", SQLDescribeCol( 345 | stmt.stmt, 346 | cast(SQLUSMALLINT) (i+1), 347 | cast(SQLCHAR *) d.name, 348 | cast(SQLSMALLINT) Describe.nameSize, 349 | &d.nameLen, 350 | &d.type, 351 | &d.size, 352 | &d.digits, 353 | &d.nullable)); 354 | 355 | //info("NAME: ", d.name, ", type: ", d.type); 356 | } 357 | } 358 | 359 | void build_bind() { 360 | import core.memory : GC; 361 | 362 | bind.reserve(columns); 363 | 364 | for(int i = 0; i < columns; ++i) { 365 | bind ~= Bind(); 366 | auto b = &bind.back(); 367 | auto d = &describe[i]; 368 | 369 | b.size = d.size; 370 | b.allocSize = cast(SQLULEN) (b.size + 1); 371 | b.data = cast(void*)(allocator.allocate(b.allocSize)); 372 | GC.addRange(b.data, b.allocSize); 373 | 374 | // just INT and VARCHAR for now 375 | switch (d.type) { 376 | case SQL_INTEGER: 377 | //b.type = SQL_C_LONG; 378 | b.type = ValueType.String; 379 | b.bindType = SQL_C_CHAR; 380 | break; 381 | case SQL_VARCHAR: 382 | b.type = ValueType.String; 383 | b.bindType = SQL_C_CHAR; 384 | break; 385 | default: 386 | throw new DatabaseException("bind error: type: " ~ to!string(d.type)); 387 | } 388 | 389 | check("SQLBINDCol", SQLBindCol ( 390 | stmt.stmt, 391 | cast(SQLUSMALLINT) (i+1), 392 | b.bindType, 393 | b.data, 394 | b.size, 395 | &b.len)); 396 | 397 | info( 398 | "output bind: index: ", i, 399 | ", type: ", b.bindType, 400 | ", size: ", b.size, 401 | ", allocSize: ", b.allocSize); 402 | } 403 | } 404 | 405 | int fetch() { 406 | //info("SQLFetch"); 407 | status = SQLFetch(stmt.stmt); 408 | if (status == SQL_SUCCESS) { 409 | return 1; 410 | } else if (status == SQL_NO_DATA) { 411 | stmt.reset(); 412 | return 0; 413 | } 414 | check("SQLFetch", SQL_HANDLE_STMT, stmt.stmt, status); 415 | return 0; 416 | } 417 | 418 | auto name(size_t idx) { 419 | auto d = &describe[idx]; 420 | return cast(string) d.name[0..d.nameLen]; 421 | } 422 | 423 | auto get(X:string)(Cell* cell) { 424 | checkType(cell.bind.bindType, SQL_C_CHAR); 425 | auto ptr = cast(immutable char*) cell.bind.data; 426 | return cast(string) ptr[0..cell.bind.len]; 427 | } 428 | 429 | auto get(X:int)(Cell* cell) { 430 | //if (b.bindType == SQL_C_CHAR) return to!int(as!string()); // tmp hack 431 | checkType(cell.bind.bindType, SQL_C_LONG); 432 | return *(cast(int*) cell.bind.data); 433 | } 434 | 435 | auto get(X:Date)(Cell* cell) { 436 | return Date(2016,1,1); // fix 437 | } 438 | 439 | void checkType(SQLSMALLINT a, SQLSMALLINT b) { 440 | if (a != b) throw new DatabaseException("type mismatch"); 441 | } 442 | 443 | } 444 | 445 | static void check()(string msg, SQLRETURN ret) { 446 | info(msg, ":", ret); 447 | if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO || ret == SQL_NO_DATA) return; 448 | throw new DatabaseException("odbc error: " ~ msg); 449 | } 450 | 451 | static void check()(string msg, SQLSMALLINT handle_type, SQLHANDLE handle, SQLRETURN ret) { 452 | info(msg, ":", ret); 453 | if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO || ret == SQL_NO_DATA) return; 454 | throw_detail(handle, handle_type, msg); 455 | } 456 | 457 | static void throw_detail()(SQLHANDLE handle, SQLSMALLINT type, string msg) { 458 | SQLSMALLINT i = 0; 459 | SQLINTEGER native; 460 | SQLCHAR[7] state; 461 | SQLCHAR[256] text; 462 | SQLSMALLINT len; 463 | SQLRETURN ret; 464 | 465 | string error; 466 | error ~= msg; 467 | error ~= ": "; 468 | 469 | do { 470 | ret = SQLGetDiagRec( 471 | type, 472 | handle, 473 | ++i, 474 | cast(char*) state, 475 | &native, 476 | cast(char*)text, 477 | text.length, 478 | &len); 479 | 480 | if (SQL_SUCCEEDED(ret)) { 481 | auto s = text[0..len]; 482 | info("error: ", s); 483 | error ~= s; 484 | //writefln("%s:%ld:%ld:%s\n", state, i, native, text); 485 | } 486 | } while (ret == SQL_SUCCESS); 487 | throw new DatabaseException(error); 488 | } 489 | 490 | } 491 | -------------------------------------------------------------------------------- /src/std/database/odbc/package.d: -------------------------------------------------------------------------------- 1 | module std.database.odbc; 2 | public import std.database.odbc.database; 3 | 4 | -------------------------------------------------------------------------------- /src/std/database/option.d: -------------------------------------------------------------------------------- 1 | module std.database.option; 2 | 3 | struct Option(T) { 4 | private T _value; 5 | private bool _isNull = true; 6 | 7 | this(inout T value) inout { 8 | _value = value; 9 | _isNull = false; 10 | } 11 | 12 | template toString() { 13 | import std.format : FormatSpec, formatValue; 14 | // Needs to be a template because of DMD @@BUG@@ 13737. 15 | void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) { 16 | if (isNull) 17 | { 18 | sink.formatValue("Nullable.null", fmt); 19 | } 20 | else 21 | { 22 | sink.formatValue(_value, fmt); 23 | } 24 | } 25 | 26 | // Issue 14940 27 | void toString()(scope void delegate(const(char)[]) @safe sink, FormatSpec!char fmt) { 28 | if (isNull) 29 | { 30 | sink.formatValue("Nullable.null", fmt); 31 | } 32 | else 33 | { 34 | sink.formatValue(_value, fmt); 35 | } 36 | } 37 | } 38 | 39 | @property bool isNull() const @safe pure nothrow { 40 | return _isNull; 41 | } 42 | 43 | void nullify()() { 44 | .destroy(_value); 45 | _isNull = true; 46 | } 47 | 48 | void opAssign()(T value) { 49 | _value = value; 50 | _isNull = false; 51 | } 52 | 53 | @property ref inout(T) get() inout @safe pure nothrow { 54 | enum message = "Called `get' on null Nullable!" ~ T.stringof ~ "."; 55 | assert(!isNull, message); 56 | return _value; 57 | } 58 | 59 | @property ref inout(T) front() inout @safe pure nothrow {return get;} 60 | @property bool empty() const @safe pure nothrow {return isNull;} 61 | @property void popFront() @safe pure nothrow {nullify;} 62 | 63 | //Implicitly converts to T: must not be in the null state. 64 | alias get this; 65 | 66 | 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/std/database/oracle/bindings.d: -------------------------------------------------------------------------------- 1 | module std.database.oracle.bindings; 2 | import core.stdc.config; 3 | 4 | enum OCI_SUCCESS = 0; 5 | enum OCI_SUCCESS_WITH_INFO = 1; 6 | enum OCI_NO_DATA = 100; 7 | 8 | enum OCI_ATTR_PREFETCH_ROWS = 13; 9 | enum OCI_ATTR_ROWS_FETCHED = 197; 10 | 11 | enum OCI_THREADED = 0x00000001; 12 | enum OCI_OBJECT = 0x00000002; 13 | 14 | enum OCI_HTYPE_ENV = 1; 15 | enum OCI_HTYPE_ERROR = 2; 16 | enum OCI_HTYPE_SVCCTX = 3; 17 | enum OCI_HTYPE_STMT = 4; 18 | 19 | enum OCI_NTV_SYNTAX = 1; 20 | 21 | enum OCI_DEFAULT = 0x00000000; 22 | enum OCI_COMMIT_ON_SUCCESS = 0x00000020; 23 | 24 | enum OCI_FETCH_CURRENT = 0x00000001; 25 | enum OCI_FETCH_NEXT = 0x00000002; 26 | enum OCI_FETCH_FIRST = 0x00000004; 27 | enum OCI_FETCH_LAST = 0x00000008; 28 | enum OCI_FETCH_PRIOR = 0x00000010; 29 | enum OCI_FETCH_ABSOLUTE = 0x00000020; 30 | enum OCI_FETCH_RELATIVE = 0x00000040; 31 | 32 | enum OCI_ATTR_PARAM_COUNT = 18; 33 | enum OCI_ATTR_STMT_TYPE = 24; 34 | enum OCI_ATTR_BIND_COUNT = 190; 35 | 36 | enum OCI_ATTR_DATA_SIZE = 1; 37 | enum OCI_ATTR_DATA_TYPE = 2; 38 | enum OCI_ATTR_DISP_SIZE = 3; 39 | enum OCI_ATTR_NAME = 4; 40 | enum OCI_ATTR_PRECISION = 5; 41 | enum OCI_ATTR_SCALE = 6; 42 | enum OCI_ATTR_IS_NULL = 7; 43 | enum OCI_ATTR_TYPE_NAME = 8; 44 | 45 | 46 | enum OCI_STMT_UNKNOWN = 0; 47 | enum OCI_STMT_SELECT = 1; 48 | enum OCI_STMT_UPDATE = 2; 49 | enum OCI_STMT_DELETE = 3; 50 | enum OCI_STMT_INSERT = 4; 51 | enum OCI_STMT_CREATE = 5; 52 | enum OCI_STMT_DROP = 6; 53 | enum OCI_STMT_ALTER = 7; 54 | enum OCI_STMT_BEGIN = 8; 55 | enum OCI_STMT_DECLARE = 9; 56 | enum OCI_STMT_CALL = 10; 57 | 58 | enum OCI_DTYPE_PARAM = 53; 59 | 60 | // from ocidfn.h 61 | 62 | enum SQLT_CHR = 1; /* (ORANET TYPE) character string */ 63 | enum SQLT_NUM = 2; /* (ORANET TYPE) oracle numeric */ 64 | enum SQLT_INT = 3; /* (ORANET TYPE) integer */ 65 | enum SQLT_FLT = 4; /* (ORANET TYPE) Floating point number */ 66 | enum SQLT_STR = 5; /* zero terminated string */ 67 | enum SQLT_VNU = 6; /* NUM with preceding length byte */ 68 | enum SQLT_PDN = 7; /* (ORANET TYPE) Packed Decimal Numeric */ 69 | enum SQLT_LNG = 8; /* long */ 70 | enum SQLT_VCS = 9; /* Variable character string */ 71 | enum SQLT_NON = 10; /* Null/empty PCC Descriptor entry */ 72 | enum SQLT_RID = 11; /* rowid */ 73 | enum SQLT_DAT = 12; /* date in oracle format */ 74 | enum SQLT_VBI = 15; /* binary in VCS format */ 75 | enum SQLT_BFLOAT = 21; /* Native Binary float*/ 76 | enum SQLT_BDOUBLE = 22; /* NAtive binary double */ 77 | enum SQLT_BIN = 23; /* binary data(DTYBIN) */ 78 | enum SQLT_LBI = 24; /* long binary */ 79 | enum SQLT_UIN = 68; /* unsigned integer */ 80 | enum SQLT_SLS = 91; /* Display sign leading separate */ 81 | enum SQLT_LVC = 94; /* Longer longs (char) */ 82 | enum SQLT_LVB = 95; /* Longer long binary */ 83 | enum SQLT_AFC = 96; /* Ansi fixed char */ 84 | enum SQLT_AVC = 97; /* Ansi Var char */ 85 | enum SQLT_IBFLOAT = 100; /* binary float canonical */ 86 | enum SQLT_IBDOUBLE = 101; /* binary double canonical */ 87 | enum SQLT_CUR = 102; /* cursor type */ 88 | enum SQLT_RDD = 104; /* rowid descriptor */ 89 | enum SQLT_LAB = 105; /* label type */ 90 | enum SQLT_OSL = 106; /* oslabel type */ 91 | 92 | enum SQLT_NTY = 108; /* named object type */ 93 | enum SQLT_REF = 110; /* ref type */ 94 | enum SQLT_CLOB= 112; /* character lob */ 95 | enum SQLT_BLOB = 113; /* binary lob */ 96 | enum SQLT_BFILEE = 114; /* binary file lob */ 97 | enum SQLT_CFILEE = 115; /* character file lob */ 98 | enum SQLT_RSET = 116; /* result set type */ 99 | enum SQLT_NCO = 122; /* named collection type (varray or nested table) */ 100 | enum SQLT_VST = 155; /* OCIString type */ 101 | enum SQLT_ODT = 156; /* OCIDate type */ 102 | 103 | extern(System) { 104 | /* typedef */ alias void* dvoid; 105 | /* typedef */ alias int sword; 106 | /* typedef */ alias ubyte ub1; 107 | /* typedef */ alias ushort ub2; 108 | /* typedef */ alias short sb2; 109 | /* typedef */ alias uint ub4; 110 | /* typedef */ alias int sb4; 111 | 112 | alias ubyte OraText; 113 | 114 | struct OCIEnv; 115 | struct OCIError; 116 | struct OCISvcCtx; 117 | struct OCIStmt; 118 | struct OCISnapshot; 119 | struct OCIParam; 120 | struct OCIDefine; 121 | struct OCIDateTime; 122 | 123 | sword OCIEnvCreate ( 124 | OCIEnv **envhpp, 125 | ub4 mode, 126 | const dvoid *ctxp, 127 | void* function(void *ctxp, size_t size) malocfp, 128 | void* function(void *ctxp, void *memptr, size_t newsize) ralocfp, 129 | void function(void *ctxp, void *memptr) mfreefp, 130 | size_t xtramem_sz, 131 | void** usrmempp ); 132 | 133 | sword OCIHandleAlloc(const void *parenth, void **hndlpp, const ub4 type, 134 | const size_t xtramem_sz, void **usrmempp); 135 | 136 | sword OCIHandleFree(void *hndlp, const ub4 type); 137 | 138 | sword OCILogon (OCIEnv *envhp, OCIError *errhp, OCISvcCtx **svchp, 139 | const OraText *username, ub4 uname_len, 140 | const OraText *password, ub4 passwd_len, 141 | const OraText *dbname, ub4 dbname_len); 142 | 143 | sword OCILogoff (OCISvcCtx *svchp, OCIError *errhp); 144 | 145 | sword OCIStmtPrepare (OCIStmt *stmtp, OCIError *errhp, const OraText *stmt, 146 | ub4 stmt_len, ub4 language, ub4 mode); 147 | 148 | sword OCIStmtExecute (OCISvcCtx *svchp, OCIStmt *stmtp, OCIError *errhp, 149 | ub4 iters, ub4 rowoff, const OCISnapshot *snap_in, 150 | OCISnapshot *snap_out, ub4 mode); 151 | 152 | sword OCIStmtFetch2 (OCIStmt *stmtp, OCIError *errhp, ub4 nrows, 153 | ub2 orientation, sb4 scrollOffset, ub4 mode); 154 | 155 | sword OCIAttrGet (const void *trgthndlp, ub4 trghndltyp, 156 | void *attributep, ub4 *sizep, ub4 attrtype, 157 | OCIError *errhp); 158 | 159 | sword OCIAttrSet (void *trgthndlp, ub4 trghndltyp, void *attributep, 160 | ub4 size, ub4 attrtype, OCIError *errhp); 161 | 162 | sword OCIParamGet (const void *hndlp, ub4 htype, OCIError *errhp, 163 | void **parmdpp, ub4 pos); 164 | 165 | sword OCIDefineByPos (OCIStmt *stmtp, OCIDefine **defnp, OCIError *errhp, 166 | ub4 position, void *valuep, sb4 value_sz, ub2 dty, 167 | void *indp, ub2 *rlenp, ub2 *rcodep, ub4 mode); 168 | 169 | sword OCIDefineArrayOfStruct( 170 | OCIDefine *defnp, 171 | OCIError *errhp, 172 | ub4 pvskip, 173 | ub4 indskip, 174 | ub4 rlskip, 175 | ub4 rcskip); 176 | 177 | sword OCIDateTimeGetDate(void *hndl, OCIError *err, const OCIDateTime *date, 178 | sb2 *year, ub1 *month, ub1 *day ); 179 | 180 | // from orl.h 181 | 182 | struct OCITime { 183 | ub1 OCITimeHH; /* hours; range is 0 <= hours <=23 */ 184 | ub1 OCITimeMI; /* minutes; range is 0 <= minutes <= 59 */ 185 | ub1 OCITimeSS; /* seconds; range is 0 <= seconds <= 59 */ 186 | } 187 | 188 | struct OCIDate { 189 | sb2 OCIDateYYYY; /* gregorian year; range is -4712 <= year <= 9999 */ 190 | ub1 OCIDateMM; /* month; range is 1 <= month < 12 */ 191 | ub1 OCIDateDD; /* day; range is 1 <= day <= 31 */ 192 | OCITime OCIDateTime; /* time */ 193 | } 194 | 195 | } 196 | 197 | -------------------------------------------------------------------------------- /src/std/database/oracle/database.d: -------------------------------------------------------------------------------- 1 | module std.database.oracle.database; 2 | pragma(lib, "occi"); 3 | pragma(lib, "clntsh"); 4 | 5 | import std.string; 6 | import core.stdc.stdlib; 7 | import std.conv; 8 | 9 | import std.database.oracle.bindings; 10 | import std.database.common; 11 | import std.database.exception; 12 | import std.database.source; 13 | import std.database.allocator; 14 | import std.experimental.logger; 15 | import std.container.array; 16 | 17 | import std.datetime; 18 | 19 | import std.database.BasicDatabase; 20 | 21 | struct DefaultPolicy { 22 | alias Allocator = MyMallocator; 23 | } 24 | 25 | alias Database(T) = BasicDatabase!(Driver!T); 26 | 27 | auto createDatabase()(string defaultURI="") { 28 | return Database!DefaultPolicy(defaultURI); 29 | } 30 | 31 | auto createDatabase(T)(string defaultURI="") { 32 | return Database!T(defaultURI); 33 | } 34 | 35 | void check()(string msg, sword status) { 36 | info(msg, ":", status); 37 | if (status == OCI_SUCCESS) return; 38 | throw new DatabaseException("OCI error: " ~ msg); 39 | } 40 | 41 | //struct Database() { 42 | //static auto create()(string uri="") {return Database!DefaultPolicy();} 43 | //} 44 | 45 | 46 | T attrGet(T)(OCIStmt *stmt, OCIError *error, ub4 attribute) { 47 | T value; 48 | ub4 sz = T.sizeof; 49 | attrGet!T(stmt, error, attribute, value); 50 | return value; 51 | } 52 | 53 | void attrGet(T)(OCIStmt *stmt, OCIError *error, ub4 attribute, ref T value) { 54 | return attrGet(stmt, OCI_HTYPE_STMT, error, attribute, value); 55 | } 56 | 57 | void attrGet(T)(void* handle, ub4 handleType, OCIError* error, ub4 attribute, ref T value) { 58 | ub4 sz = T.sizeof; 59 | check("OCIAttrGet", OCIAttrGet( 60 | handle, 61 | OCI_HTYPE_STMT, 62 | &value, 63 | &sz, 64 | attribute, 65 | error)); 66 | } 67 | 68 | 69 | 70 | 71 | struct Driver(P) { 72 | alias Policy = P; 73 | alias Allocator = Policy.Allocator; 74 | alias Cell = BasicCell!(Driver); 75 | 76 | 77 | 78 | static void attrSet(T)(OCIStmt *stmt, ub4 attribute, ref T value) { 79 | return attrSet(stmt, OCI_HTYPE_STMT, attribute, value); 80 | } 81 | 82 | static void attrSet(T)(void* handle, ub4 handleType, ub4 attribute, T value) { 83 | ub4 sz = T.sizeof; 84 | check("OCIAttrSet", OCIAttrSet( 85 | stmt, 86 | OCI_HTYPE_STMT, 87 | &value, 88 | sz, 89 | attribute, 90 | error)); 91 | } 92 | 93 | struct BindContext { 94 | Describe* describe; // const? 95 | Bind* bind; 96 | Allocator* allocator; 97 | int rowArraySize; 98 | } 99 | 100 | static void basicCtor(T) (ref BindContext ctx) { 101 | emplace(cast(T*) ctx.bind.data); 102 | } 103 | 104 | static void charBinder(ref BindContext ctx) { 105 | auto d = ctx.describe, b = ctx.bind; 106 | b.type = ValueType.String; 107 | b.oType = SQLT_STR; 108 | //b.allocSize = cast(sb4) (ctx.describe.size + 1); 109 | b.allocSize = 1024; // fix this 110 | 111 | // include byte for null terminator 112 | //d.buf_size = d.size ? d.size+1 : 1024*10; // FIX 113 | } 114 | 115 | static void dateBinder(int type, T)(ref BindContext ctx) { 116 | auto d = ctx.describe, b = ctx.bind; 117 | b.type = ValueType.Date; 118 | b.oType = type; 119 | b.allocSize = T.sizeof; 120 | b.ctor = &basicCtor!T; 121 | } 122 | 123 | struct Conversion { 124 | int colType, bindType; 125 | 126 | private static const Conversion[4] info = [ 127 | {SQLT_INT,SQLT_INT}, 128 | {SQLT_NUM,SQLT_STR}, 129 | {SQLT_CHR,SQLT_STR}, 130 | {SQLT_DAT,SQLT_DAT} 131 | ]; 132 | 133 | static int get(int colType) { 134 | // needs improvement 135 | foreach(ref i; info) { 136 | if (i.colType == colType) return i.bindType; 137 | } 138 | throw new DatabaseException( 139 | "unknown conversion for column: " ~ to!string(colType)); 140 | } 141 | } 142 | 143 | struct BindInfo { 144 | ub2 bindType; 145 | void function(ref BindContext ctx) binder; 146 | 147 | static const BindInfo[4] info = [ 148 | {SQLT_INT,&charBinder}, 149 | {SQLT_STR,&charBinder}, 150 | {SQLT_STR,&charBinder}, 151 | {SQLT_DAT,&dateBinder!(SQLT_ODT,OCIDate)} 152 | ]; 153 | 154 | static void bind(ref BindContext ctx) { 155 | import core.memory : GC; // needed? 156 | auto d = ctx.describe, b = ctx.bind; 157 | auto colType = ctx.describe.oType; 158 | auto bindType = Conversion.get(colType); 159 | auto allocator = ctx.allocator; 160 | 161 | // needs improvement 162 | foreach(ref i; info) { 163 | if (i.bindType == bindType) { 164 | i.binder(ctx); 165 | b.data = allocator.allocate(b.allocSize * ctx.rowArraySize); 166 | b.ind = allocator.allocate(sb2.sizeof * ctx.rowArraySize); 167 | b.length = allocator.allocate(ub2.sizeof * ctx.rowArraySize); 168 | if (b.ctor) b.ctor(ctx); 169 | GC.addRange(b.data.ptr, b.data.length); 170 | return; 171 | } 172 | } 173 | 174 | throw new DatabaseException( 175 | "bind not supported: " ~ to!string(colType)); 176 | } 177 | 178 | } 179 | 180 | 181 | struct Database { 182 | alias queryVariableType = QueryVariableType.Dollar; 183 | 184 | Allocator allocator; 185 | OCIEnv* env; 186 | OCIError *error; 187 | 188 | static const FeatureArray features = [ 189 | //Feature.InputBinding, 190 | //Feature.DateBinding, 191 | //Feature.ConnectionPool, 192 | Feature.OutputArrayBinding, 193 | ]; 194 | 195 | this(string defaultURI_) { 196 | allocator = Allocator(); 197 | info("oracle: opening database"); 198 | ub4 mode = OCI_THREADED | OCI_OBJECT; 199 | 200 | check("OCIEnvCreate", OCIEnvCreate( 201 | &env, 202 | mode, 203 | null, 204 | null, 205 | null, 206 | null, 207 | 0, 208 | null)); 209 | 210 | check("OCIHandleAlloc", OCIHandleAlloc( 211 | env, 212 | cast(void**) &error, 213 | OCI_HTYPE_ERROR, 214 | 0, 215 | null)); 216 | } 217 | 218 | ~this() { 219 | info("oracle: closing database"); 220 | if (env) { 221 | sword status = OCIHandleFree(env, OCI_HTYPE_ENV); 222 | env = null; 223 | } 224 | } 225 | 226 | } 227 | 228 | struct Connection { 229 | Database* db; 230 | Source source; 231 | OCIError *error; 232 | OCISvcCtx *svc_ctx; 233 | bool connected; 234 | 235 | this(Database* db_, Source source_) {hhkk: 236 | db = db_; 237 | source = source_; 238 | error = db.error; 239 | 240 | string dbname = getDBName(source); 241 | 242 | check("OCILogon: ", OCILogon( 243 | db_.env, 244 | error, 245 | &svc_ctx, 246 | cast(const OraText*) source.username.ptr, 247 | cast(ub4) source.username.length, 248 | cast(const OraText*) source.password.ptr, 249 | cast(ub4) source.password.length, 250 | cast(const OraText*) dbname.ptr, 251 | cast(ub4) dbname.length)); 252 | 253 | connected = true; 254 | } 255 | 256 | ~this() { 257 | if (svc_ctx) check("OCILogoff", OCILogoff(svc_ctx, error)); 258 | } 259 | 260 | static string getDBName(Source src) { 261 | string dbName = 262 | "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)" ~ 263 | "(HOST=" ~ src.server ~ ")" ~ 264 | "(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)))"; // need service name 265 | return dbName; 266 | } 267 | } 268 | 269 | static const nameSize = 256; 270 | 271 | struct Describe { 272 | //char[nameSize] name; 273 | 274 | OraText* ora_name; 275 | string name; 276 | int index; 277 | ub4 name_len; 278 | //string name; 279 | ub2 oType; 280 | ub2 size; 281 | //const nd_oracle_type_info *type_info; 282 | //oracle_bind_type bind_type; 283 | OCIParam *param; 284 | OCIDefine *define; 285 | } 286 | 287 | struct Bind { 288 | ValueType type; 289 | void[] data; 290 | sb4 allocSize; 291 | ub2 oType; 292 | void[] ind; 293 | void[] length; 294 | void function(ref BindContext ctx) ctor; 295 | void function(void[] data) dtor; 296 | } 297 | 298 | // non memeber needed to avoid forward error 299 | //auto range(T)(Statement!T stmt) {return Result!T(stmt).range();} 300 | 301 | struct Statement { 302 | Connection *con; 303 | string sql; 304 | Allocator *allocator; 305 | OCIError *error; 306 | OCIStmt *stmt; 307 | ub2 stmt_type; 308 | int binds; 309 | Array!Bind inputBind; 310 | 311 | this(Connection* con_, string sql_) { 312 | con = con_; 313 | sql = sql_; 314 | allocator = &con.db.allocator; 315 | error = con.error; 316 | 317 | check("OCIHandleAlloc", OCIHandleAlloc( 318 | con.db.env, 319 | cast(void**) &stmt, 320 | OCI_HTYPE_STMT, 321 | 0, 322 | null)); 323 | 324 | //attrSet!ub4(OCI_ATTR_PREFETCH_ROWS, 1000); 325 | } 326 | 327 | ~this() { 328 | for(int i = 0; i < inputBind.length; ++i) { 329 | allocator.deallocate(inputBind[i].data[0..inputBind[i].allocSize]); 330 | } 331 | if (stmt) check("OCIHandleFree", OCIHandleFree(stmt,OCI_HTYPE_STMT)); 332 | // stmt = null? needed 333 | } 334 | 335 | void exec() { 336 | //check("SQLExecDirect", SQLExecDirect(data_.stmt,cast(SQLCHAR*) toStringz(data_.sql), SQL_NTS)); 337 | } 338 | 339 | void prepare() { 340 | 341 | check("OCIStmtPrepare", OCIStmtPrepare( 342 | stmt, 343 | error, 344 | cast(OraText*) sql.ptr, 345 | cast(ub4) sql.length, 346 | OCI_NTV_SYNTAX, 347 | OCI_DEFAULT)); 348 | 349 | 350 | // get the type of statement 351 | stmt_type = attrGet!ub2(OCI_ATTR_STMT_TYPE); 352 | binds = attrGet!ub4(OCI_ATTR_BIND_COUNT); 353 | info("binds: ", binds); 354 | } 355 | 356 | void query() { 357 | ub4 iters = stmt_type == OCI_STMT_SELECT ? 0:1; 358 | info("iters: ", iters); 359 | info("execute sql: ", sql); 360 | 361 | check("OCIStmtExecute", OCIStmtExecute( 362 | con.svc_ctx, 363 | stmt, 364 | error, 365 | iters, 366 | 0, 367 | null, 368 | null, 369 | OCI_COMMIT_ON_SUCCESS)); 370 | } 371 | 372 | void query(X...) (X args) { 373 | foreach (arg; args) { 374 | info("ARG: ", arg); 375 | } 376 | 377 | info("variadic query not implemented yet"); 378 | //bindAll(args); 379 | //query(); 380 | } 381 | 382 | bool hasRows() { 383 | // need a better way 384 | int columns = attrGet!ub4(OCI_ATTR_PARAM_COUNT); 385 | return columns != 0; 386 | } 387 | 388 | private void bindAll(T...) (T args) { 389 | int col; 390 | foreach (arg; args) bind(++col, arg); 391 | } 392 | 393 | void bind(int n, int value) { 394 | /* 395 | info("input bind: n: ", n, ", value: ", value); 396 | 397 | Bind b; 398 | b.type = SQL_C_LONG; 399 | b.dbtype = SQL_INTEGER; 400 | b.size = SQLINTEGER.sizeof; 401 | b.allocSize = b.size; 402 | b.data = malloc(b.allocSize); 403 | inputBind(n, b); 404 | 405 | *(cast(SQLINTEGER*) data_.inputBind[n-1].data) = value; 406 | */ 407 | } 408 | 409 | void bind(int n, const char[] value){ 410 | /* 411 | import core.stdc.string: strncpy; 412 | info("input bind: n: ", n, ", value: ", value); 413 | // no null termination needed 414 | 415 | Bind b; 416 | b.type = SQL_C_CHAR; 417 | b.dbtype = SQL_CHAR; 418 | b.size = cast(SQLSMALLINT) value.length; 419 | b.allocSize = b.size; 420 | b.data = malloc(b.allocSize); 421 | inputBind(n, b); 422 | 423 | strncpy(cast(char*) b.data, value.ptr, b.size); 424 | */ 425 | } 426 | 427 | void bind(int n, Date d) { 428 | throw new DatabaseException("Date input binding not yet implemented"); 429 | } 430 | 431 | void reset() {} 432 | //: Error: template std.database.oracle.database.Driver!(DefaultPolicy).Driver.Statement.attrGet cannot deduce function from argument types !(ushort)(OCIStmt*, uint), candidates are: 433 | 434 | private T attrGet(T)(ub4 attribute) {return attrGet!T(stmt, error, attribute);} 435 | private void attrGet(T)(ub4 attribute, ref T value) { return attrGet(stmt, error, attribute, value);} 436 | private void attrSet(T)(ub4 attribute, T value) {return attrSet(stmt, error, attribute, value);} 437 | 438 | static T attrGet(T)(OCIStmt *stmt, OCIError *error, ub4 attribute) { 439 | T value; 440 | ub4 sz = T.sizeof; 441 | attrGet!T(stmt, error, attribute, value); 442 | return value; 443 | } 444 | 445 | static void attrGet(T)(OCIStmt *stmt, OCIError *error, ub4 attribute, ref T value) { 446 | return attrGet(stmt, OCI_HTYPE_STMT, error, attribute, value); 447 | } 448 | 449 | static void attrGet(T)(void* handle, ub4 handleType, OCIError* error, ub4 attribute, ref T value) { 450 | ub4 sz = T.sizeof; 451 | check("OCIAttrGet", OCIAttrGet( 452 | handle, 453 | OCI_HTYPE_STMT, 454 | &value, 455 | &sz, 456 | attribute, 457 | error)); 458 | } 459 | 460 | } 461 | 462 | struct Result { 463 | Statement *stmt; 464 | Allocator *allocator; 465 | OCIError *error; 466 | int columns; 467 | Array!Describe describe; 468 | Array!Bind bind; 469 | ub4 rowArraySize; 470 | sword status; 471 | 472 | this(Statement* stmt_, int rowArraySize_) { 473 | stmt = stmt_; 474 | rowArraySize = rowArraySize_; 475 | allocator = &stmt.con.db.allocator; 476 | error = stmt.error; 477 | columns = stmt_.attrGet!ub4(OCI_ATTR_PARAM_COUNT); 478 | build_describe(); 479 | build_bind(); 480 | } 481 | 482 | ~this() { 483 | foreach(ref b; bind) { 484 | if (b.dtor) b.dtor(b.data); 485 | allocator.deallocate(b.data); 486 | allocator.deallocate(b.ind); 487 | allocator.deallocate(b.length); 488 | } 489 | } 490 | 491 | void build_describe() { 492 | 493 | describe.reserve(columns); 494 | for(int i = 0; i < columns; ++i) { 495 | describe ~= Describe(); 496 | auto d = &describe.back(); 497 | 498 | OCIParam *col; 499 | check("OCIParamGet",OCIParamGet( 500 | stmt.stmt, 501 | OCI_HTYPE_STMT, 502 | error, 503 | cast(void**) &col, 504 | i+1)); 505 | 506 | check("OCIAttrGet", OCIAttrGet( 507 | col, 508 | OCI_DTYPE_PARAM, 509 | &d.ora_name, 510 | &d.name_len, 511 | OCI_ATTR_NAME, 512 | error)); 513 | 514 | check("OCIAttrGet", OCIAttrGet( 515 | col, 516 | OCI_DTYPE_PARAM, 517 | &d.oType, 518 | null, 519 | OCI_ATTR_DATA_TYPE, 520 | error)); 521 | 522 | check("OCIAttrGet", OCIAttrGet( 523 | col, 524 | OCI_DTYPE_PARAM, 525 | &d.size, 526 | null, 527 | OCI_ATTR_DATA_SIZE, 528 | error)); 529 | 530 | d.name = to!string(cast(char*)(d.ora_name)[0..d.name_len]); 531 | info("describe: name: ", d.name, ", type: ", d.oType, ", size: ", d.size); 532 | } 533 | 534 | } 535 | 536 | void build_bind() { 537 | auto allocator = Allocator(); 538 | 539 | import core.memory : GC; // needed? 540 | 541 | bind.reserve(columns); 542 | 543 | for(int i = 0; i < columns; ++i) { 544 | bind ~= Bind(); 545 | auto d = &describe[i]; 546 | auto b = &bind.back(); 547 | 548 | BindContext ctx; 549 | ctx.describe = d; 550 | ctx.bind = b; 551 | ctx.allocator = &allocator; 552 | ctx.rowArraySize = rowArraySize; 553 | 554 | BindInfo.bind(ctx); 555 | 556 | info("bind: type: ", b.oType, ", allocSize:", b.allocSize); 557 | 558 | check("OCIDefineByPos", OCIDefineByPos( 559 | stmt.stmt, 560 | &d.define, 561 | error, 562 | cast(ub4) i+1, 563 | b.data.ptr, 564 | b.allocSize, 565 | b.oType, 566 | cast(dvoid *) b.ind.ptr, // check 567 | cast(ub2*) b.length.ptr, 568 | null, 569 | OCI_DEFAULT)); 570 | 571 | if (rowArraySize > 1) { 572 | check("OCIDefineArrayOfStruct", OCIDefineArrayOfStruct( 573 | d.define, 574 | error, 575 | b.allocSize, 576 | sb2.sizeof, 577 | ub2.sizeof, 578 | 0)); 579 | } 580 | } 581 | } 582 | 583 | int fetch() { 584 | if (status == OCI_NO_DATA) return 0; 585 | 586 | int rowsFetched; 587 | 588 | //info("OCIStmtFetch2"); 589 | 590 | status = OCIStmtFetch2( 591 | stmt.stmt, 592 | error, 593 | rowArraySize, 594 | OCI_FETCH_NEXT, 595 | 0, 596 | OCI_DEFAULT); 597 | 598 | if (rowArraySize > 1) { 599 | // clean up 600 | ub4 value; 601 | check("OCIAttrGet",OCIAttrGet( 602 | stmt.stmt, 603 | OCI_HTYPE_STMT, 604 | &value, 605 | null, 606 | OCI_ATTR_ROWS_FETCHED, 607 | error)); 608 | rowsFetched = value; 609 | } else { 610 | rowsFetched = (status == OCI_NO_DATA ? 0 : 1); 611 | } 612 | 613 | if (status == OCI_SUCCESS) { 614 | return rowsFetched; 615 | } else if (status == OCI_NO_DATA) { 616 | //stmt.reset(); 617 | return rowsFetched; 618 | } 619 | 620 | throw new DatabaseException("fetch error"); // fix 621 | //check("SQLFetch", SQL_HANDLE_STMT, stmt.data_.stmt, status); 622 | //return 0; 623 | } 624 | 625 | auto get(X:string)(Cell* cell) { 626 | import core.stdc.string: strlen; 627 | checkType(cell.bind.oType, SQLT_STR); 628 | auto ptr = cast(immutable char*) data(cell); 629 | return cast(string) ptr[0..strlen(ptr)]; // fix with length 630 | } 631 | 632 | auto name(size_t idx) { 633 | return describe[idx].name; 634 | } 635 | 636 | auto get(X:int)(Cell* cell) { 637 | checkType(cell.bind.oType, SQLT_INT); 638 | return *(cast(int*) data(cell)); 639 | } 640 | 641 | auto get(X:Date)(Cell* cell) { 642 | //return Date(2016,1,1); // fix 643 | checkType(cell.bind.oType, SQLT_ODT); 644 | auto d = cast(OCIDate*) data(cell); 645 | return Date(d.OCIDateYYYY,d.OCIDateMM,d.OCIDateDD); 646 | } 647 | 648 | private void* data(Cell* cell) { 649 | return cell.bind.data.ptr + cell.bind.allocSize * cell.rowIdx; 650 | } 651 | 652 | private static void checkType(ub2 a, ub2 b) { 653 | if (a != b) throw new DatabaseException("type mismatch"); 654 | } 655 | 656 | } 657 | 658 | } 659 | 660 | 661 | -------------------------------------------------------------------------------- /src/std/database/oracle/package.d: -------------------------------------------------------------------------------- 1 | module std.database.oracle; 2 | public import std.database.oracle.database; 3 | 4 | -------------------------------------------------------------------------------- /src/std/database/oracle/stubs.d: -------------------------------------------------------------------------------- 1 | module std.database.oracle.stubs; 2 | 3 | public import std.database.oracle.bindings; 4 | 5 | import std.stdio; 6 | 7 | extern(System) { 8 | 9 | /* 10 | sword OCIEnvCreate ( 11 | OCIEnv **envhpp, 12 | ub4 mode, 13 | const dvoid *ctxp, 14 | //const dvoid *(*malocfp) (dvoid *ctxp, size_t size), 15 | //const dvoid *(*ralocfp) (dvoid *ctxp, dvoid *memptr, size_t newsize), 16 | //const void (*mfreefp) (dvoid *ctxp, dvoid *memptr)) size_t xtramemsz, 17 | const dvoid *, 18 | const dvoid *, 19 | const dvoid *, 20 | dvoid **usrmempp ) { 21 | writeln("(OCIEnvCreate)"); 22 | //*envhpp = cast(void *) 123; 23 | return 0; 24 | } 25 | 26 | sword OCIHandleFree(void *hndlp, const ub4 type) { 27 | writeln("(OCIHandleFree)"); 28 | return 0; 29 | } 30 | */ 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/std/database/package.d: -------------------------------------------------------------------------------- 1 | module std.database; 2 | public import std.database.poly; 3 | 4 | -------------------------------------------------------------------------------- /src/std/database/poly/database.d: -------------------------------------------------------------------------------- 1 | module std.database.poly.database; 2 | 3 | import std.string; 4 | import core.stdc.stdlib; 5 | import std.conv; 6 | import std.experimental.logger; 7 | 8 | public import std.database.exception; 9 | 10 | import std.stdio; 11 | import std.typecons; 12 | import std.container.array; 13 | import std.database.BasicDatabase; 14 | import std.database.allocator; 15 | import std.database.source; 16 | import std.database.common; 17 | import std.datetime; 18 | import std.database.uri; 19 | import std.variant; 20 | import std.database.variant; 21 | 22 | import std.meta; 23 | 24 | alias Database(T) = BasicDatabase!(Driver!T); 25 | 26 | struct DefaultPolicy { 27 | alias Allocator = MyMallocator; 28 | } 29 | 30 | auto registerDatabase(DB)(string name) { 31 | alias PolyDB = Database!DefaultPolicy; 32 | PolyDB.register!DB(name); 33 | } 34 | 35 | auto createDatabase()(string defaultURI="") { 36 | return Database!DefaultPolicy(defaultURI); 37 | } 38 | 39 | struct Driver(P) { 40 | alias Policy = P; 41 | alias Allocator = Policy.Allocator; 42 | alias Cell = BasicCell!(Driver); 43 | alias BindArgs = Array!Variant; 44 | 45 | // revise using allocator make 46 | 47 | static auto construct(T, A, X...)(ref A allocator, X args) { 48 | import core.memory : GC; // needed? 49 | auto s = cast(T[]) allocator.allocate(T.sizeof); 50 | GC.addRange(s.ptr, s.length); 51 | emplace!T(s, args); 52 | return cast(void[]) s; // cast apparently necessary or get array cast misallignment 53 | //return s; // cast apparently necessary or get array cast misallignment 54 | } 55 | 56 | static auto destruct(T,A)(ref A allocator, void[] data) { 57 | import core.memory : GC; // needed? 58 | .destroy(*(cast(T*) data.ptr)); 59 | allocator.deallocate(data); 60 | GC.removeRange(data.ptr); 61 | } 62 | 63 | static auto toTypedPtr(T)(void[] data) {return (cast(T[]) data).ptr;} 64 | 65 | static struct DBVtable { 66 | void[] function(string defaultURI) create; 67 | void function(void[]) destroy; 68 | const(Feature[]) function(void[] db) features; // how to make parameter ref const(FeatureArray) 69 | } 70 | 71 | static struct ConVTable { 72 | void[] function(void[], Source source) create; 73 | void function(void[]) destroy; 74 | } 75 | 76 | static struct StmtVTable { 77 | void[] function(void[], string sql) create; 78 | void function(void[]) destroy; 79 | void function(void[] stmt) prepare; 80 | void function(void[] stmt) query; 81 | void function(void[] stmt, ref BindArgs ba) variadicQuery; 82 | } 83 | 84 | static struct DriverInfo { 85 | string name; 86 | DBVtable dbVtable; 87 | ConVTable conVtable; 88 | StmtVTable stmtVtable; 89 | } 90 | 91 | static struct DBGen(Driver) { 92 | private static Allocator alloc = MyMallocator(); 93 | alias Database = Driver.Database; 94 | alias Connection = Driver.Connection; 95 | 96 | static void[] create(string uri) {return construct!Database(alloc, uri);} 97 | static void destroy(void[] db) {destruct!Database(alloc, db);} 98 | 99 | static const(Feature[]) features(void[] db) { 100 | //return DB.features; 101 | return (cast(Database*) db.ptr).features; 102 | } 103 | 104 | static DBVtable vtable = { 105 | &create, 106 | &destroy, 107 | &features, 108 | }; 109 | } 110 | 111 | static struct ConGen(Driver) { 112 | private static Allocator alloc = MyMallocator(); 113 | alias Database = Driver.Database; 114 | alias Connection = Driver.Connection; 115 | 116 | static void[] create(void[] db, Source source) { 117 | return construct!Connection(alloc, toTypedPtr!Database(db), source); 118 | } 119 | 120 | static void destroy(void[] con) {destruct!Connection(alloc, con);} 121 | 122 | static ConVTable vtable = { 123 | &create, 124 | &destroy, 125 | }; 126 | } 127 | 128 | 129 | static struct StmtGen(Driver) { 130 | private static Allocator alloc = MyMallocator(); 131 | alias Connection = Driver.Connection; 132 | alias Statement = Driver.Statement; 133 | 134 | // doesn't work outside of scope 135 | 136 | static void constructQuery(int i=0, A...)(Statement* stmt, A a) { 137 | // list of primitive types somewhere ? 138 | // dstring causes problems 139 | //alias Types = AliasSeq!(byte, ubyte, string, char, dchar, int, uint, long, ulong); 140 | alias Types = AliasSeq!(int, string, Date); 141 | 142 | static void call(int i, T, A...)(Statement* stmt, T v, A a) { 143 | constructQuery!(i+1)(stmt, a[0..i], v, a[(i+1)..$]); 144 | } 145 | 146 | static if (i == a.length) { 147 | stmt.query(a); 148 | } else { 149 | //log("type: ", a[i].type); 150 | foreach(T; Types) { 151 | //log("--TYPE: ", typeid(T)); 152 | //if (a[i].type == typeid(T)) 153 | if (a[i].convertsTo!T) { 154 | call!i(stmt,a[i].get!T,a); 155 | return; 156 | } 157 | } 158 | throw new DatabaseException("unknown type: " ~ a[i].type.toString); 159 | } 160 | } 161 | 162 | 163 | static auto create(void[] con, string sql) { 164 | return construct!Statement(alloc, toTypedPtr!Connection(con), sql); 165 | } 166 | 167 | static void destroy(void[] stmt) {destruct!Statement(alloc, stmt);} 168 | static void prepare(void[] stmt) {toTypedPtr!Statement(stmt).prepare();} 169 | static void query(void[] stmt) {toTypedPtr!Statement(stmt).query();} 170 | 171 | 172 | static void callVariadic(alias F,S,A...) (ref S s, A a) { 173 | //initial args come last in this call 174 | switch (s.length) { 175 | case 0: break; 176 | case 1: F(a,s[0]); break; 177 | case 2: F(a,s[0],s[1]); break; 178 | case 3: F(a,s[0],s[1],s[2]); break; 179 | case 4: F(a,s[0],s[1],s[2],s[3]); break; 180 | case 5: F(a,s[0],s[1],s[2],s[3],s[4]); break; 181 | default: throw new Exception("too many args"); 182 | } 183 | } 184 | 185 | static void variadicQuery(void[] stmt, ref BindArgs a) { 186 | auto s = toTypedPtr!Statement(stmt); 187 | //callVariadic!constructQuery(a, s); 188 | 189 | //void F(A...) (A a) {stmt.query(a);} 190 | 191 | switch (a.length) { 192 | case 0: break; 193 | case 1: constructQuery(s,a[0]); break; 194 | case 2: constructQuery(s,a[0],a[1]); break; 195 | case 3: constructQuery(s,a[0],a[1],a[2]); break; 196 | default: throw new DatabaseException("too many args"); 197 | } 198 | 199 | } 200 | 201 | static StmtVTable vtable = { 202 | &create, 203 | &destroy, 204 | &prepare, 205 | &query, 206 | &variadicQuery, 207 | }; 208 | } 209 | 210 | //static struct QueryGen(Driver, T...) // but you don't have driver and args at same time 211 | 212 | 213 | struct Database { 214 | private static Array!DriverInfo drivers; 215 | private string URI; 216 | 217 | private DriverInfo* driver; 218 | private void[] db; 219 | 220 | alias queryVariableType = QueryVariableType.Dollar; 221 | 222 | static void register(DB) (string name) { 223 | alias Driver = DB.Driver; 224 | DriverInfo driver; 225 | driver.name = name; 226 | driver.dbVtable = DBGen!Driver.vtable; 227 | driver.conVtable = ConGen!Driver.vtable; 228 | driver.stmtVtable = StmtGen!Driver.vtable; 229 | 230 | drivers ~= driver; 231 | 232 | info( 233 | "poly register: ", 234 | "name: ", name, ", ", 235 | "type: ", typeid(DB), 236 | "index: ", drivers.length); 237 | } 238 | 239 | void setDatabase(string name) { 240 | driver = findDatabase(name); 241 | db = driver.dbVtable.create(URI); 242 | } 243 | 244 | bool isDatabaseSet() {return driver && db;} 245 | 246 | //Allocator allocator; 247 | 248 | const(Feature[]) features() { 249 | // strange feature behavior to get it working 250 | if (driver) return driver.dbVtable.features(db); 251 | static const FeatureArray f = [ 252 | //Feature.InputBinding, 253 | //Feature.DateBinding, 254 | //Feature.ConnectionPool, 255 | //Feature.OutputArrayBinding, 256 | ]; 257 | return f; 258 | } 259 | 260 | this(string URI_) { 261 | //allocator = Allocator(); 262 | URI = URI_; 263 | } 264 | 265 | ~this() { 266 | if (isDatabaseSet) { 267 | if (!db.ptr) throw new DatabaseException("db not set"); 268 | driver.dbVtable.destroy(db); 269 | } 270 | } 271 | 272 | private static DriverInfo* findDatabase(string name) { 273 | //import std.algorithm; 274 | //import std.range.primitives: empty; 275 | foreach(ref d; drivers) { 276 | if (d.name == name) return &d; 277 | } 278 | throw new DatabaseException("can't find database with name: " ~ name); 279 | } 280 | } 281 | 282 | struct Connection { 283 | Database* database; 284 | Source source; 285 | void[] con; 286 | 287 | auto driver() {return database.driver;} 288 | auto db() {return database.db;} 289 | 290 | this(Database* database_, Source source_) { 291 | database = database_; 292 | source = source_; 293 | if (!database.isDatabaseSet) throw new DatabaseException("no database set"); 294 | con = driver.conVtable.create(db, source); 295 | } 296 | 297 | ~this() { 298 | driver.conVtable.destroy(con); 299 | } 300 | } 301 | 302 | struct Describe { 303 | } 304 | 305 | struct Bind { 306 | ValueType type; 307 | } 308 | 309 | struct Statement { 310 | Connection *connection; 311 | string sql; 312 | Allocator *allocator; 313 | void[] stmt; 314 | 315 | auto driver() {return connection.driver;} 316 | auto con() {return connection.con;} 317 | 318 | this(Connection* connection_, string sql_) { 319 | connection = connection_; 320 | sql = sql_; 321 | //allocator = &con.db.allocator; 322 | stmt = driver.stmtVtable.create(con, sql); 323 | } 324 | 325 | ~this() { 326 | driver.stmtVtable.destroy(stmt); 327 | } 328 | 329 | void exec() { 330 | //check("SQLExecDirect", SQLExecDirect(data_.stmt,cast(SQLCHAR*) toStringz(data_.sql), SQL_NTS)); 331 | } 332 | 333 | void prepare() { 334 | driver.stmtVtable.prepare(stmt); 335 | } 336 | 337 | void query() { 338 | driver.stmtVtable.query(stmt); 339 | } 340 | 341 | void query(A...) (ref A args) { 342 | import std.range; 343 | //auto a = BindArgs(only(args)); 344 | auto a = BindArgs(); 345 | a.reserve(args.length); 346 | foreach(arg; args) a ~= Variant(arg); 347 | driver.stmtVtable.variadicQuery(stmt, a); 348 | } 349 | 350 | bool hasRows() {return true;} // fix 351 | 352 | 353 | void bind(int n, int value) { 354 | } 355 | 356 | void bind(int n, const char[] value){ 357 | } 358 | 359 | void bind(int n, Date d) { 360 | } 361 | 362 | void reset() {} 363 | } 364 | 365 | struct Result { 366 | Statement *stmt; 367 | Allocator *allocator; 368 | Array!Bind bind; 369 | 370 | this(Statement* stmt_, int rowArraySize_) { 371 | stmt = stmt_; 372 | //allocator = &stmt.con.db.allocator; 373 | 374 | //build_describe(); 375 | //build_bind(); 376 | } 377 | 378 | ~this() { 379 | //foreach(ref b; bind) { 380 | //} 381 | } 382 | 383 | void build_describe() { 384 | } 385 | 386 | void build_bind() { 387 | } 388 | 389 | int columns() {return 0;} 390 | 391 | //bool hasResult() {return columns != 0;} 392 | bool hasResult() {return 0;} 393 | 394 | int fetch() { 395 | return 0; 396 | } 397 | 398 | auto name(size_t idx) { 399 | return "-name-"; 400 | } 401 | 402 | auto get(X:string)(Cell* cell) { 403 | return "abc"; 404 | } 405 | 406 | auto get(X:int)(Cell* cell) { 407 | return 0; 408 | } 409 | 410 | auto get(X:Date)(Cell* cell) { 411 | return Date(2016,1,1); // fix 412 | } 413 | 414 | } 415 | 416 | } 417 | 418 | -------------------------------------------------------------------------------- /src/std/database/poly/package.d: -------------------------------------------------------------------------------- 1 | module std.database.poly; 2 | public import std.database.poly.database; 3 | 4 | -------------------------------------------------------------------------------- /src/std/database/pool.d: -------------------------------------------------------------------------------- 1 | module std.database.pool; 2 | import std.container.array; 3 | import std.experimental.logger; 4 | import std.database.allocator; 5 | import core.thread; 6 | 7 | // not really a pool yet 8 | 9 | struct Pool(T) { 10 | alias Allocator = MyMallocator; 11 | alias Res = T; 12 | 13 | struct Factory(T) { 14 | Allocator allocator; 15 | 16 | this(Allocator allocator_ ) { 17 | allocator = allocator_; 18 | } 19 | 20 | auto acquire(A...)(auto ref A args) { 21 | import std.conv : emplace; 22 | import core.memory : GC; 23 | void[] data = allocator.allocate(T.sizeof); 24 | GC.addRange(data.ptr, T.sizeof); 25 | auto ptr = cast(T*) data.ptr; 26 | emplace(ptr, args); 27 | return ptr; 28 | } 29 | 30 | void release(T* ptr) { 31 | import core.memory : GC; 32 | .destroy(*ptr); 33 | GC.removeRange(ptr); 34 | allocator.deallocate(ptr[0..T.sizeof]); 35 | } 36 | } 37 | 38 | private Array!Resource data; 39 | private Factory!Res factory_; 40 | private bool enable_; 41 | private int nextId_; 42 | 43 | this(bool enable) { 44 | factory_ = Factory!Res(Allocator()); 45 | enable_ = enable; 46 | } 47 | 48 | ~this() { 49 | if (!data.empty()) { 50 | factory_.release(data.back.resource); 51 | } 52 | } 53 | 54 | struct Resource { 55 | int id; 56 | ThreadID tid; 57 | Res* resource; 58 | this(int id_, ThreadID tid_, Res* resource_) { 59 | id = id_; 60 | tid = tid_; 61 | resource = resource_; 62 | } 63 | } 64 | 65 | auto acquire(A...)(auto ref A args) { 66 | ThreadID tid = Thread.getThis.id; 67 | if (!enable_) return Resource(0, tid, factory_.acquire(args)); 68 | if (!data.empty()) { 69 | //log("======= pool: return back"); 70 | return Resource(0, tid, data.back.resource); 71 | } 72 | //log("========== pool: create: ",tid); 73 | data ~= Resource(0, tid, factory_.acquire(args)); 74 | return data.back(); 75 | } 76 | 77 | void release(ref Resource resource) { 78 | //log("========== pool: release"); 79 | if (!enable_) factory_.release(resource.resource); 80 | // return to pool 81 | } 82 | 83 | } 84 | 85 | /* 86 | Scoped resource 87 | Useful for RefCounted 88 | */ 89 | 90 | struct ScopedResource(T) { 91 | alias Pool = T; 92 | alias Resource = Pool.Resource; 93 | 94 | Pool* pool; 95 | Resource resource; 96 | this(ref Pool pool_, Resource resource_) { 97 | pool = &pool_; 98 | resource = resource_; 99 | } 100 | ~this() { 101 | pool.release(resource); 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/std/database/postgres/bindings.d: -------------------------------------------------------------------------------- 1 | module std.database.postgres.bindings; 2 | import core.stdc.config; 3 | 4 | extern(System) { 5 | 6 | // from server/catalog/pg_type.h 7 | enum int BOOLOID = 16; 8 | enum int BYTEAOI = 17; 9 | enum int CHAROID = 18; 10 | enum int NAMEOID = 19; 11 | enum int INT8OID = 20; 12 | enum int INT2OID = 21; 13 | enum int INT2VECTOROID = 22; 14 | enum int INT4OID = 23; 15 | enum int REGPROCOID = 24; 16 | enum int TEXTOID = 25; 17 | enum int OIDOID = 26; 18 | enum int TIDOID = 27; 19 | enum int XIDOID = 28; 20 | enum int CIDOID = 29; 21 | enum int OIDVECTOROID = 30; 22 | enum int VARCHAROID = 1043; 23 | enum int DATEOID = 1082; 24 | 25 | 26 | enum PGRES_EMPTY_QUERY = 0; 27 | 28 | enum int CONNECTION_OK = 0; 29 | enum int PGRES_COMMAND_OK = 1; 30 | enum int PGRES_TUPLES_OK = 2; 31 | enum int PGRES_COPY_OUT = 3; 32 | enum int PGRES_COPY_IN = 4; 33 | enum int PGRES_BAD_RESPONSE = 5; 34 | enum int PGRES_NONFATAL_ERROR = 6; 35 | enum int PGRES_FATAL_ERROR = 7; 36 | enum int PGRES_COPY_BOTH = 8; 37 | enum int PGRES_SINGLE_TUPLE = 9; 38 | 39 | alias ExecStatusType=int; 40 | alias Oid=uint; 41 | 42 | struct PGconn {}; 43 | 44 | PGconn* PQconnectdb(const char*); 45 | PGconn *PQconnectdbParams(const char **keywords, const char **values, int expand_dbname); 46 | void PQfinish(PGconn*); 47 | 48 | int PQstatus(PGconn*); 49 | const (char*) PQerrorMessage(PGconn*); 50 | 51 | struct PGresult {}; 52 | 53 | int PQsendQuery(PGconn *conn, const char *command); 54 | PGresult *PQgetResult(PGconn *conn); 55 | 56 | int PQsetSingleRowMode(PGconn *conn); 57 | 58 | ExecStatusType PQresultStatus(const PGresult *res); 59 | char *PQresStatus(ExecStatusType status); 60 | 61 | PGresult *PQexecParams( 62 | PGconn *conn, 63 | const char *command, 64 | int nParams, 65 | const Oid *paramTypes, 66 | const char ** paramValues, 67 | const int *paramLengths, 68 | const int *paramFormats, 69 | int resultFormat); 70 | 71 | PGresult *PQprepare( 72 | PGconn *conn, 73 | const char *stmtName, 74 | const char *query, 75 | int nParams, 76 | const Oid *paramTypes); 77 | 78 | /* 79 | PGresult *PQexecPrepared( 80 | PGconn *conn, 81 | const char *stmtName, 82 | int nParams, 83 | const char *const *paramValues, 84 | const int *paramLengths, 85 | const int *paramFormats, 86 | int resultFormat); 87 | */ 88 | 89 | PGresult* PQexecPrepared( 90 | PGconn*, 91 | const char* stmtName, 92 | int nParams, 93 | const char** paramValues, 94 | const int* paramLengths, 95 | const int* paramFormats, 96 | int resultFormat); 97 | 98 | int PQntuples(const PGresult *res); 99 | int PQnfields(PGresult*); 100 | 101 | char *PQgetvalue(const PGresult *res, int row_number, int column_number); 102 | int PQgetlength(const PGresult *res, int tup_num, int field_num); 103 | int PQgetisnull(const PGresult *res, int tup_num, int field_num); 104 | 105 | Oid PQftype(const PGresult *res, int column_number); 106 | int PQfformat(const PGresult *res, int field_num); 107 | char *PQfname(const PGresult *res, int field_num); 108 | 109 | void PQclear(PGresult *res); 110 | 111 | char *PQresultErrorMessage(const PGresult *res); 112 | 113 | // date 114 | 115 | /* see pgtypes_date.h */ 116 | 117 | alias long date; // long? 118 | void PGTYPESdate_julmdy(date, int *); 119 | void PGTYPESdate_mdyjul(int *mdy, date *jdate); 120 | 121 | // numeric 122 | 123 | enum int DECSIZE = 30; 124 | 125 | alias ubyte NumericDigit; 126 | 127 | struct numeric { 128 | int ndigits; /* number of digits in digits[] - can be 0! */ 129 | int weight; /* weight of first digit */ 130 | int rscale; /* result scale */ 131 | int dscale; /* display scale */ 132 | int sign; /* NUMERIC_POS, NUMERIC_NEG, or NUMERIC_NAN */ 133 | NumericDigit *buf; /* start of alloc'd space for digits[] */ 134 | NumericDigit *digits; /* decimal digits */ 135 | }; 136 | 137 | struct decimal { 138 | int ndigits; /* number of digits in digits[] - can be 0! */ 139 | int weight; /* weight of first digit */ 140 | int rscale; /* result scale */ 141 | int dscale; /* display scale */ 142 | int sign; /* NUMERIC_POS, NUMERIC_NEG, or NUMERIC_NAN */ 143 | NumericDigit[DECSIZE] digits; /* decimal digits */ 144 | } 145 | 146 | int PGTYPESnumeric_to_int(numeric *nv, int *ip); 147 | 148 | // non blocking calls 149 | 150 | alias PostgresPollingStatusType = int; 151 | enum { 152 | PGRES_POLLING_FAILED = 0, 153 | PGRES_POLLING_READING, /* These two indicate that one may */ 154 | PGRES_POLLING_WRITING, /* use select before polling again. */ 155 | PGRES_POLLING_OK, 156 | PGRES_POLLING_ACTIVE /* unused; keep for awhile for backwards 157 | * compatibility */ 158 | } 159 | 160 | PostgresPollingStatusType PQconnectPoll(PGconn *conn); 161 | 162 | int PQsocket(const PGconn *conn); 163 | 164 | int PQsendQuery(PGconn *conn, const char *command); 165 | 166 | int PQsendQueryParams( 167 | PGconn *conn, 168 | const char *command, 169 | int nParams, 170 | const Oid *paramTypes, 171 | const char ** paramValues, 172 | const int *paramLengths, 173 | const int *paramFormats, 174 | int resultFormat); 175 | 176 | int PQsendQueryPrepared( 177 | PGconn *conn, 178 | const char *stmtName, 179 | int nParams, 180 | const char **paramValues, 181 | const int *paramLengths, 182 | const int *paramFormats, 183 | int resultFormat); 184 | 185 | int PQsendDescribePrepared(PGconn *conn, const char *stmtName); 186 | int PQsendDescribePrepared(PGconn *conn, const char *stmt); 187 | int PQconsumeInput(PGconn *conn); 188 | int PQisBusy(PGconn *conn); 189 | int PQsetnonblocking(PGconn *conn, int arg); 190 | int PQisnonblocking(const PGconn *conn); 191 | 192 | int PQflush(PGconn *conn); 193 | 194 | struct PGnotify { 195 | char* relname; 196 | int be_pid; 197 | char* extra; 198 | private PGnotify* next; 199 | } 200 | 201 | PGnotify *PQnotifies(PGconn *conn); 202 | void PQfreemem(void *ptr); 203 | 204 | } 205 | 206 | -------------------------------------------------------------------------------- /src/std/database/postgres/database.d: -------------------------------------------------------------------------------- 1 | module std.database.postgres.database; 2 | pragma(lib, "pq"); 3 | pragma(lib, "pgtypes"); 4 | 5 | import std.string; 6 | import core.stdc.stdlib; 7 | 8 | import std.database.postgres.bindings; 9 | import std.database.common; 10 | import std.database.exception; 11 | import std.database.source; 12 | import std.database.allocator; 13 | import std.container.array; 14 | import std.experimental.logger; 15 | import std.database.BasicDatabase; 16 | 17 | import std.stdio; 18 | import std.typecons; 19 | import std.datetime; 20 | 21 | struct DefaultPolicy { 22 | alias Allocator = MyMallocator; 23 | static const bool nonblocking = false; 24 | } 25 | 26 | alias Database(T) = BasicDatabase!(Driver!T); 27 | 28 | auto createDatabase()(string defaultURI="") { 29 | return Database!DefaultPolicy(defaultURI); 30 | } 31 | 32 | auto createDatabase(T)(string defaultURI="") { 33 | return Database!T(defaultURI); 34 | } 35 | 36 | /* 37 | auto createAsyncDatabase()(string defaultURI="") { 38 | return Database!DefaultAsyncPolicy(defaultURI); 39 | } 40 | */ 41 | 42 | 43 | void error()(PGconn *con, string msg) { 44 | import std.conv; 45 | auto s = msg ~ to!string(PQerrorMessage(con)); 46 | throw new DatabaseException(msg); 47 | } 48 | 49 | void error()(PGconn *con, string msg, int result) { 50 | import std.conv; 51 | auto s = "error:" ~ msg ~ ": " ~ to!string(result) ~ ": " ~ to!string(PQerrorMessage(con)); 52 | throw new DatabaseException(msg); 53 | } 54 | 55 | int check()(PGconn *con, string msg, int result) { 56 | info(msg, ": ", result); 57 | if (result != 1) error(con, msg, result); 58 | return result; 59 | } 60 | 61 | int checkForZero()(PGconn *con, string msg, int result) { 62 | info(msg, ": ", result); 63 | if (result != 0) error(con, msg, result); 64 | return result; 65 | } 66 | 67 | struct Driver(P) { 68 | alias Policy = P; 69 | alias Allocator = Policy.Allocator; 70 | alias Cell = BasicCell!(Driver); 71 | 72 | struct Database { 73 | static const auto queryVariableType = QueryVariableType.Dollar; 74 | 75 | static const FeatureArray features = [ 76 | Feature.InputBinding, 77 | Feature.DateBinding, 78 | //Feature.ConnectionPool, 79 | ]; 80 | 81 | Allocator allocator; 82 | 83 | this(string defaultURI_) { 84 | allocator = Allocator(); 85 | } 86 | 87 | ~this() { 88 | } 89 | } 90 | 91 | struct Connection { 92 | Database* db; 93 | Source source; 94 | PGconn *con; 95 | 96 | this(Database* db_, Source source_) { 97 | db = db_; 98 | source = source_; 99 | 100 | string conninfo; 101 | conninfo ~= "dbname=" ~ source.database; 102 | con = PQconnectdb(toStringz(conninfo)); 103 | if (PQstatus(con) != CONNECTION_OK) error(con, "login error"); 104 | } 105 | 106 | 107 | ~this() { 108 | PQfinish(con); 109 | } 110 | 111 | int socket() { 112 | return PQsocket(con); 113 | } 114 | 115 | void* handle() {return con;} 116 | } 117 | 118 | 119 | struct Statement { 120 | Connection* connection; 121 | string sql; 122 | Allocator *allocator; 123 | PGconn *con; 124 | string name; 125 | PGresult *prepareRes; 126 | PGresult *res; 127 | 128 | Array!(char*) bindValue; 129 | Array!(Oid) bindType; 130 | Array!(int) bindLength; 131 | Array!(int) bindFormat; 132 | 133 | this(Connection* connection_, string sql_) { 134 | connection = connection_; 135 | sql = sql_; 136 | allocator = &connection.db.allocator; 137 | con = connection.con; 138 | //prepare(); 139 | } 140 | 141 | ~this() { 142 | for(int i = 0; i != bindValue.length; ++i) { 143 | auto ptr = bindValue[i]; 144 | auto length = bindLength[i]; 145 | allocator.deallocate(ptr[0..length]); 146 | } 147 | } 148 | 149 | void bind(int n, int value) { 150 | } 151 | 152 | void bind(int n, const char[] value) { 153 | } 154 | 155 | 156 | void query() { 157 | import std.conv; 158 | 159 | info("query sql: ", sql); 160 | 161 | if (!prepareRes) prepare(); 162 | auto n = bindValue.length; 163 | int resultFormat = 1; 164 | 165 | static if (Policy.nonblocking) { 166 | 167 | checkForZero(con,"PQsetnonblocking", PQsetnonblocking(con, 1)); 168 | 169 | check(con, "PQsendQueryPrepared", PQsendQueryPrepared( 170 | con, 171 | toStringz(name), 172 | cast(int) n, 173 | n ? cast(const char **) &bindValue[0] : null, 174 | n ? cast(int*) &bindLength[0] : null, 175 | n ? cast(int*) &bindFormat[0] : null, 176 | resultFormat)); 177 | 178 | do { 179 | Policy.Handler handler; 180 | handler.addSocket(posixSocket()); 181 | /* 182 | auto s = PQconnectPoll(con); 183 | if (s == PGRES_POLLING_OK) { 184 | log("READY"); 185 | break; 186 | } 187 | */ 188 | log("waiting: "); 189 | checkForZero(con, "PQflush", PQflush(con)); 190 | handler.wait(); 191 | check(con, "PQconsumeInput", PQconsumeInput(con)); 192 | 193 | PGnotify* notify; 194 | while ((notify = PQnotifies(con)) != null) { 195 | info("notify: ", to!string(notify.relname)); 196 | PQfreemem(notify); 197 | } 198 | 199 | } while (PQisBusy(con) == 1); 200 | 201 | res = PQgetResult(con); 202 | 203 | } else { 204 | res = PQexecPrepared( 205 | con, 206 | toStringz(name), 207 | cast(int) n, 208 | n ? cast(const char **) &bindValue[0] : null, 209 | n ? cast(int*) &bindLength[0] : null, 210 | n ? cast(int*) &bindFormat[0] : null, 211 | resultFormat); 212 | } 213 | 214 | /* 215 | not using for now 216 | if (!PQsendQuery(con, toStringz(sql))) throw error("PQsendQuery"); 217 | res = PQgetResult(con); 218 | */ 219 | 220 | // problem with PQsetSingleRowMode and prepared statements 221 | // if (!PQsetSingleRowMode(con)) throw error("PQsetSingleRowMode"); 222 | } 223 | 224 | void query(X...) (X args) { 225 | info("query sql: ", sql); 226 | 227 | // todo: stack allocation 228 | 229 | bindValue.clear(); 230 | bindType.clear(); 231 | bindLength.clear(); 232 | bindFormat.clear(); 233 | 234 | foreach (ref arg; args) bind(arg); 235 | 236 | auto n = bindValue.length; 237 | 238 | /* 239 | types must be set in prepared 240 | res = PQexecPrepared( 241 | con, 242 | toStringz(name), 243 | cast(int) n, 244 | n ? cast(const char **) &bindValue[0] : null, 245 | n ? cast(int*) &bindLength[0] : null, 246 | n ? cast(int*) &bindFormat[0] : null, 247 | 0); 248 | */ 249 | 250 | int resultForamt = 0; 251 | 252 | res = PQexecParams( 253 | con, 254 | toStringz(sql), 255 | cast(int) n, 256 | n ? cast(Oid*) &bindType[0] : null, 257 | n ? cast(const char **) &bindValue[0] : null, 258 | n ? cast(int*) &bindLength[0] : null, 259 | n ? cast(int*) &bindFormat[0] : null, 260 | resultForamt); 261 | } 262 | 263 | bool hasRows() {return true;} 264 | 265 | int binds() {return cast(int) bindValue.length;} // fix 266 | 267 | void bind(string v) { 268 | import core.stdc.string: strncpy; 269 | void[] s = allocator.allocate(v.length+1); 270 | char *p = cast(char*) s.ptr; 271 | strncpy(p, v.ptr, v.length); 272 | p[v.length] = 0; 273 | bindValue ~= p; 274 | bindType ~= 0; 275 | bindLength ~= 0; 276 | bindFormat ~= 0; 277 | } 278 | 279 | void bind(int v) { 280 | import std.bitmanip; 281 | void[] s = allocator.allocate(int.sizeof); 282 | *cast(int*) s.ptr = peek!(int, Endian.bigEndian)(cast(ubyte[]) (&v)[0..int.sizeof]); 283 | bindValue ~= cast(char*) s.ptr; 284 | bindType ~= INT4OID; 285 | bindLength ~= cast(int) s.length; 286 | bindFormat ~= 1; 287 | } 288 | 289 | void bind(Date v) { 290 | /* utility functions take 8 byte values but DATEOID is a 4 byte value */ 291 | import std.bitmanip; 292 | int[3] mdy; 293 | mdy[0] = v.month; 294 | mdy[1] = v.day; 295 | mdy[2] = v.year; 296 | long d; 297 | PGTYPESdate_mdyjul(&mdy[0], &d); 298 | void[] s = allocator.allocate(4); 299 | *cast(int*) s.ptr = peek!(int, Endian.bigEndian)(cast(ubyte[]) (&d)[0..4]); 300 | bindValue ~= cast(char*) s.ptr; 301 | bindType ~= DATEOID; 302 | bindLength ~= cast(int) s.length; 303 | bindFormat ~= 1; 304 | } 305 | 306 | void prepare() { 307 | const Oid* paramTypes; 308 | prepareRes = PQprepare( 309 | con, 310 | toStringz(name), 311 | toStringz(sql), 312 | 0, 313 | paramTypes); 314 | } 315 | 316 | auto error(string msg) { 317 | import std.conv; 318 | string s; 319 | s ~= msg ~ ", " ~ to!string(PQerrorMessage(con)); 320 | return new DatabaseException(s); 321 | } 322 | 323 | void reset() { 324 | } 325 | 326 | private auto posixSocket() { 327 | int s = PQsocket(con); 328 | if (s == -1) throw new DatabaseException("can't get socket"); 329 | return s; 330 | } 331 | 332 | } 333 | 334 | struct Describe { 335 | int dbType; 336 | int fmt; 337 | string name; 338 | } 339 | 340 | 341 | struct Bind { 342 | ValueType type; 343 | int idx; 344 | //int fmt; 345 | //int len; 346 | //int isNull; 347 | } 348 | 349 | struct Result { 350 | Statement* stmt; 351 | PGconn *con; 352 | PGresult *res; 353 | int columns; 354 | Array!Describe describe; 355 | ExecStatusType status; 356 | int row; 357 | int rows; 358 | bool hasResult_; 359 | 360 | // artifical bind array (for now) 361 | Array!Bind bind; 362 | 363 | this(Statement* stmt_, int rowArraySize_) { 364 | stmt = stmt_; 365 | con = stmt.con; 366 | res = stmt.res; 367 | 368 | setup(); 369 | 370 | build_describe(); 371 | build_bind(); 372 | } 373 | 374 | ~this() { 375 | if (res) close(); 376 | } 377 | 378 | bool setup() { 379 | if (!res) { 380 | info("no result"); 381 | return false; 382 | } 383 | status = PQresultStatus(res); 384 | rows = PQntuples(res); 385 | 386 | // not handling PGRESS_SINGLE_TUPLE yet 387 | if (status == PGRES_COMMAND_OK) { 388 | close(); 389 | return false; 390 | } else if (status == PGRES_EMPTY_QUERY) { 391 | close(); 392 | return false; 393 | } else if (status == PGRES_TUPLES_OK) { 394 | return true; 395 | } else throw error(res,status); 396 | } 397 | 398 | 399 | void build_describe() { 400 | import std.conv; 401 | // called after next() 402 | columns = PQnfields(res); 403 | for (int col = 0; col != columns; col++) { 404 | describe ~= Describe(); 405 | auto d = &describe.back(); 406 | d.dbType = cast(int) PQftype(res, col); 407 | d.fmt = PQfformat(res, col); 408 | d.name = to!string(PQfname(res, col)); 409 | } 410 | } 411 | 412 | void build_bind() { 413 | // artificial bind setup 414 | bind.reserve(columns); 415 | for(int i = 0; i < columns; ++i) { 416 | auto d = &describe[i]; 417 | bind ~= Bind(); 418 | auto b = &bind.back(); 419 | b.type = ValueType.String; 420 | b.idx = i; 421 | switch(d.dbType) { 422 | case VARCHAROID: b.type = ValueType.String; break; 423 | case INT4OID: b.type = ValueType.Int; break; 424 | case DATEOID: b.type = ValueType.Date; break; 425 | default: throw new DatabaseException("unsupported type"); 426 | } 427 | } 428 | } 429 | 430 | int fetch() { 431 | return ++row != rows ? 1 :0; 432 | } 433 | 434 | bool singleRownext() { 435 | if (res) PQclear(res); 436 | res = PQgetResult(con); 437 | if (!res) return false; 438 | status = PQresultStatus(res); 439 | 440 | if (status == PGRES_COMMAND_OK) { 441 | close(); 442 | return false; 443 | } else if (status == PGRES_SINGLE_TUPLE) return true; 444 | else if (status == PGRES_TUPLES_OK) { 445 | close(); 446 | return false; 447 | } else throw error(status); 448 | } 449 | 450 | void close() { 451 | if (!res) throw error("couldn't close result: result was not open"); 452 | res = PQgetResult(con); 453 | if (res) throw error("couldn't close result: was not finished"); 454 | res = null; 455 | } 456 | 457 | auto error(string msg) { 458 | return new DatabaseException(msg); 459 | } 460 | 461 | auto error(ExecStatusType status) { 462 | import std.conv; 463 | string s = "result error: " ~ to!string(PQresStatus(status)); 464 | return new DatabaseException(s); 465 | } 466 | 467 | auto error(PGresult *res, ExecStatusType status) { 468 | import std.conv; 469 | const char* msg = PQresultErrorMessage(res); 470 | string s = 471 | "error: " ~ to!string(PQresStatus(status)) ~ 472 | ", message:" ~ to!string(msg); 473 | return new DatabaseException(s); 474 | } 475 | 476 | /* 477 | char[] get(X:char[])(Bind *b) { 478 | auto ptr = cast(char*) b.data.ptr; 479 | return ptr[0..b.length]; 480 | } 481 | */ 482 | 483 | auto name(size_t idx) { 484 | return describe[idx].name; 485 | } 486 | 487 | auto get(X:string)(Cell* cell) { 488 | checkType(type(cell.bind.idx),VARCHAROID); 489 | immutable char *ptr = cast(immutable char*) data(cell.bind.idx); 490 | return cast(string) ptr[0..len(cell.bind.idx)]; 491 | } 492 | 493 | auto get(X:int)(Cell* cell) { 494 | import std.bitmanip; 495 | checkType(type(cell.bind.idx),INT4OID); 496 | auto p = cast(ubyte*) data(cell.bind.idx); 497 | return bigEndianToNative!int(p[0..int.sizeof]); 498 | } 499 | 500 | auto get(X:Date)(Cell* cell) { 501 | import std.bitmanip; 502 | checkType(type(cell.bind.idx),DATEOID); 503 | auto ptr = cast(ubyte*) data(cell.bind.idx); 504 | int sz = len(cell.bind.idx); 505 | date d = bigEndianToNative!uint(ptr[0..4]); // why not sz? 506 | int[3] mdy; 507 | PGTYPESdate_julmdy(d, &mdy[0]); 508 | return Date(mdy[2],mdy[0],mdy[1]); 509 | } 510 | 511 | void checkType(int a, int b) { 512 | if (a != b) throw new DatabaseException("type mismatch"); 513 | } 514 | 515 | void* data(int col) {return PQgetvalue(res, row, col);} 516 | bool isNull(int col) {return PQgetisnull(res, row, col) != 0;} 517 | int type(int col) {return describe[col].dbType;} 518 | int fmt(int col) {return describe[col].fmt;} 519 | int len(int col) {return PQgetlength(res, row, col);} 520 | 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /src/std/database/postgres/package.d: -------------------------------------------------------------------------------- 1 | module std.database.postgres; 2 | public import std.database.postgres.database; 3 | 4 | -------------------------------------------------------------------------------- /src/std/database/reference/database.d: -------------------------------------------------------------------------------- 1 | module std.database.reference.database; 2 | 3 | import std.string; 4 | 5 | import std.database.common; 6 | import std.database.exception; 7 | import std.container.array; 8 | import std.experimental.logger; 9 | 10 | public import std.database.allocator; 11 | 12 | import std.stdio; 13 | import std.typecons; 14 | 15 | struct DefaultPolicy { 16 | alias Allocator = MyMallocator; 17 | } 18 | 19 | // this function is module specific because it allows 20 | // a default template to be specified 21 | auto createDatabase()(string defaultUrl=null) { 22 | return Database!DefaultPolicy(defaultUrl); 23 | } 24 | 25 | //one for specfic type 26 | auto createDatabase(T)(string defaultUrl=null) { 27 | return Database!T(defaultUrl); 28 | } 29 | 30 | // these functions can be moved into util once a solution to forward bug is found 31 | 32 | auto connection(T)(Database!T db, string source) { 33 | return Connection!T(db,source); 34 | } 35 | 36 | auto statement(T) (Connection!T con, string sql) { 37 | return Statement!T(con, sql); 38 | } 39 | 40 | auto statement(T, X...) (Connection!T con, string sql, X args) { 41 | return Statement!T(con, sql, args); 42 | } 43 | 44 | auto result(T)(Statement!T stmt) { 45 | return Result!T(stmt); 46 | } 47 | 48 | struct Database(T) { 49 | alias Allocator = T.Allocator; 50 | 51 | static const auto queryVariableType = QueryVariableType.QuestionMark; 52 | 53 | 54 | private struct Payload { 55 | string defaultURI; 56 | Allocator allocator; 57 | 58 | this(string defaultURI_) { 59 | info("opening database resource"); 60 | defaultURI = defaultURI_; 61 | allocator = Allocator(); 62 | } 63 | 64 | ~this() { 65 | info("closing database resource"); 66 | } 67 | 68 | this(this) { assert(false); } 69 | void opAssign(Database.Payload rhs) { assert(false); } 70 | } 71 | 72 | private alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; 73 | private Data data_; 74 | 75 | this(string defaultURI) { 76 | data_ = Data(defaultURI); 77 | } 78 | } 79 | 80 | 81 | struct Connection(T) { 82 | alias Database = .Database!T; 83 | //alias Statement = .Statement!T; 84 | 85 | auto statement(string sql) {return Statement!T(this,sql);} 86 | auto statement(X...) (string sql, X args) {return Statement!T(this,sql,args);} 87 | auto query(string sql) {return statement(sql).query();} 88 | auto query(T...) (string sql, T args) {return statement(sql).query(args);} 89 | 90 | package this(Database db, string source) { 91 | data_ = Data(db,source); 92 | } 93 | 94 | private: 95 | 96 | struct Payload { 97 | Database db; 98 | string source; 99 | bool connected; 100 | 101 | this(Database db_, string source_) { 102 | db = db_; 103 | source = source_; 104 | connected = true; 105 | } 106 | 107 | ~this() { 108 | } 109 | 110 | this(this) { assert(false); } 111 | void opAssign(Connection.Payload rhs) { assert(false); } 112 | } 113 | 114 | private alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; 115 | private Data data_; 116 | } 117 | 118 | struct Statement(T) { 119 | alias Connection = .Connection!T; 120 | 121 | alias Result = .Result; 122 | //alias Range = Result.Range; // error Result.Payload no size yet for forward reference 123 | 124 | // temporary 125 | auto result() {return Result!T(this);} 126 | auto opSlice() {return result();} 127 | 128 | this(Connection con, string sql) { 129 | data_ = Data(con,sql); 130 | //prepare(); 131 | // must be able to detect binds in all DBs 132 | //if (!data_.binds) query(); 133 | } 134 | 135 | this(T...) (Connection con, string sql, T args) { 136 | data_ = Data(con,sql); 137 | //prepare(); 138 | //bindAll(args); 139 | //query(); 140 | } 141 | 142 | //string sql() {return data_.sql;} 143 | //int binds() {return data_.binds;} 144 | 145 | void bind(int n, int value) { 146 | } 147 | 148 | void bind(int n, const char[] value){ 149 | } 150 | 151 | private: 152 | 153 | struct Payload { 154 | Connection con; 155 | string sql; 156 | bool hasRows; 157 | int binds; 158 | 159 | this(Connection con_, string sql_) { 160 | con = con_; 161 | sql = sql_; 162 | } 163 | 164 | this(this) { assert(false); } 165 | void opAssign(Statement.Payload rhs) { assert(false); } 166 | } 167 | 168 | alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; 169 | Data data_; 170 | 171 | public: 172 | 173 | void exec() {} 174 | void prepare() {} 175 | 176 | auto query() { 177 | return result(); 178 | } 179 | 180 | auto query(X...) (X args) { 181 | return query(); 182 | } 183 | 184 | private: 185 | 186 | void bindAll(T...) (T args) { 187 | int col; 188 | foreach (arg; args) { 189 | bind(++col, arg); 190 | } 191 | } 192 | 193 | void reset() { 194 | } 195 | } 196 | 197 | struct Result(T) { 198 | alias Statement = .Statement!T; 199 | alias ResultRange = .ResultRange!T; 200 | alias Range = .ResultRange; 201 | alias Row = .Row; 202 | 203 | this(Statement stmt) { 204 | data_ = Data(stmt); 205 | } 206 | 207 | auto opSlice() {return ResultRange(this);} 208 | 209 | bool start() {return true;} 210 | bool next() {return data_.next();} 211 | 212 | private: 213 | 214 | struct Payload { 215 | Statement stmt; 216 | 217 | this(Statement stmt_) { 218 | stmt = stmt_; 219 | next(); 220 | } 221 | 222 | bool next() { 223 | return false; 224 | } 225 | 226 | this(this) { assert(false); } 227 | void opAssign(Statement.Payload rhs) { assert(false); } 228 | } 229 | 230 | alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; 231 | Data data_; 232 | 233 | } 234 | 235 | struct Value { 236 | auto as(T:int)() { 237 | return 0; 238 | } 239 | 240 | auto as(T:string)() { 241 | return "value"; 242 | } 243 | 244 | auto chars() {return "value";} 245 | } 246 | 247 | struct ResultRange(T) { 248 | alias Result = .Result!T; 249 | alias Row = .Row!T; 250 | private Result result_; 251 | private bool ok_; 252 | 253 | this(Result result) { 254 | result_ = result; 255 | ok_ = result_.start(); 256 | } 257 | 258 | bool empty() {return !ok_;} 259 | Row front() {return Row(&result_);} 260 | void popFront() {ok_ = result_.next();} 261 | } 262 | 263 | 264 | struct Row(T) { 265 | alias Result = .Result!T; 266 | alias Value = .Value; 267 | this(Result* result) { result_ = result;} 268 | Value opIndex(size_t idx) {return Value();} 269 | private Result* result_; 270 | 271 | int columns() {return 0;} 272 | } 273 | 274 | -------------------------------------------------------------------------------- /src/std/database/reference/package.d: -------------------------------------------------------------------------------- 1 | module std.database.reference; 2 | public import std.database.reference.database; 3 | 4 | -------------------------------------------------------------------------------- /src/std/database/resolver.d: -------------------------------------------------------------------------------- 1 | module std.database.resolver; 2 | 3 | import std.experimental.logger; 4 | 5 | import std.database.exception; 6 | import std.database.uri; 7 | import std.database.source; 8 | 9 | import std.process; 10 | import std.file; 11 | import std.path; 12 | import std.json; 13 | import std.conv; 14 | import std.stdio; 15 | import std.string; 16 | 17 | 18 | Source resolve(string name) { 19 | if (name.length == 0) throw new DatabaseException("resolver: name empty"); 20 | 21 | Source source; 22 | 23 | // first, hacky url check 24 | if (name.indexOf('/') != -1) { 25 | URI uri = toURI(name); 26 | source.type = uri.protocol; 27 | if (uri.port != 0) { 28 | source.host = uri.host; 29 | source.port = uri.port; 30 | } else { 31 | source.server = uri.host; 32 | } 33 | source.path = uri.path.startsWith('/') ? uri.path[1..$] : uri.path; 34 | source.database = source.path; 35 | source.username = uri["username"]; 36 | source.password = uri["password"]; 37 | return source; 38 | } 39 | 40 | auto home = environment["HOME"]; 41 | 42 | string file = home ~ "/db.json"; 43 | 44 | if (!exists(file)) { 45 | info("hacky for automation"); 46 | source.server = "testdb"; 47 | source.database = ""; 48 | source.username = ""; 49 | source.password = ""; 50 | return source; 51 | } 52 | 53 | auto bytes = read(file); 54 | auto str = to!string(bytes); 55 | auto doc = parseJSON(str); 56 | 57 | JSONValue[] databases = doc["databases"].array; 58 | foreach(e; databases) { 59 | if (e["name"].str != name) continue; 60 | source.type = e["type"].str; 61 | source.server = e["server"].str; 62 | //source.path = e["path"].str; // fix 63 | source.database = e["database"].str; 64 | source.username = e["username"].str; 65 | source.password = e["password"].str; 66 | return source; 67 | } 68 | 69 | throw new DatabaseException("couldn't find source name: " ~ name); 70 | } 71 | 72 | -------------------------------------------------------------------------------- /src/std/database/rowset.d: -------------------------------------------------------------------------------- 1 | module std.database.rowset; 2 | import std.database.BasicDatabase; 3 | import std.datetime; 4 | import std.container.array; 5 | 6 | // experimental detached rowset 7 | 8 | /* 9 | struct RowSet { 10 | private struct RowData { 11 | int[3] data; 12 | } 13 | private alias Data = Array!RowData; 14 | 15 | struct Row { 16 | private RowSet* rowSet; 17 | private RowData* data; 18 | this(RowSet* rs, RowData *d) {rowSet = rs; data = d;} 19 | int columns() {return rowSet.columns();} 20 | auto opIndex(size_t idx) {return Value(rowSet,Bind(ValueType.Int, &data.data[idx]));} 21 | } 22 | 23 | struct Bind { 24 | this(ValueType t, void* d) {type = t; data = d;} 25 | ValueType type; 26 | void *data; 27 | } 28 | 29 | struct Driver { 30 | alias Result = .RowSet; 31 | alias Bind = RowSet.Bind; 32 | } 33 | struct Policy {} 34 | 35 | alias Converter = .Converter!(Driver,Policy); 36 | 37 | 38 | struct TypeInfo(T:int) {static int type() {return ValueType.Int;}} 39 | struct TypeInfo(T:string) {static int type() {return ValueType.String;}} 40 | struct TypeInfo(T:Date) {static int type() {return ValueType.Date;}} 41 | 42 | static auto get(X:string)(Bind *b) {return "";} 43 | static auto get(X:int)(Bind *b) {return *cast(int*) b;} 44 | static auto get(X:Date)(Bind *b) {return Date(2016,1,1);} 45 | 46 | struct Value { 47 | RowSet* rowSet; 48 | Bind bind; 49 | private void* data; 50 | this(RowSet *r, Bind b) { 51 | rowSet = r; 52 | bind = b; 53 | } 54 | //auto as(T:int)() {return *cast(int*) bind.data;} 55 | auto as(T:int)() {return Converter.convert!T(rowSet,&bind);} 56 | } 57 | 58 | struct Range { 59 | alias Rng = Data.Range; 60 | private RowSet* rowSet; 61 | private Rng rng; 62 | this(ref RowSet rs, Rng r) {rowSet = &rs; rng = r;} 63 | bool empty() {return rng.empty();} 64 | Row front() {return Row(rowSet,&rng.front());} 65 | void popFront() {rng.popFront();} 66 | } 67 | 68 | 69 | this(R) (R result, size_t capacity = 0) { 70 | data.reserve(capacity); 71 | foreach (r; result[]) { 72 | data ~= RowData(); 73 | auto d = &data.back(); 74 | for(int c = 0; c != r.columns; ++c) d.data[c] = r[c].as!int; 75 | } 76 | } 77 | 78 | int columns() {return cast(int) data.length;} 79 | auto opSlice() {return Range(this,data[]);} 80 | 81 | private Array!RowData data; 82 | } 83 | 84 | */ 85 | 86 | -------------------------------------------------------------------------------- /src/std/database/source.d: -------------------------------------------------------------------------------- 1 | module std.database.source; 2 | 3 | struct Source { 4 | string type; 5 | string server; 6 | string host; 7 | int port; 8 | string path; // for file references (sqlite) 9 | string database; 10 | string username; 11 | string password; 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/std/database/sqlite/database.d: -------------------------------------------------------------------------------- 1 | module std.database.sqlite.database; 2 | 3 | pragma(lib, "sqlite3"); 4 | 5 | import std.string; 6 | import core.stdc.stdlib; 7 | import std.typecons; 8 | import etc.c.sqlite3; 9 | 10 | import std.database.common; 11 | import std.database.exception; 12 | import std.database.source; 13 | import std.database.allocator; 14 | import std.database.pool; 15 | import std.experimental.logger; 16 | import std.database.BasicDatabase; 17 | 18 | import std.container.array; 19 | import std.datetime; 20 | 21 | import std.stdio; 22 | 23 | struct DefaultPolicy { 24 | alias Allocator = MyMallocator; 25 | } 26 | 27 | alias Database(T) = BasicDatabase!(Driver!T); 28 | 29 | auto createDatabase()(string defaultURI="") { 30 | return Database!DefaultPolicy(defaultURI); 31 | } 32 | 33 | auto createDatabase(T)(string defaultURI="") { 34 | return Database!T(defaultURI); 35 | } 36 | 37 | struct Driver(P) { 38 | alias Policy = P; 39 | alias Allocator = Policy.Allocator; 40 | alias Cell = BasicCell!(Driver); 41 | 42 | struct Database { 43 | alias queryVariableType = QueryVariableType.QuestionMark; 44 | 45 | static const FeatureArray features = [ 46 | Feature.InputBinding, 47 | //Feature.DateBinding, 48 | //Feature.ConnectionPool, 49 | ]; 50 | 51 | this(string defaultSource_) { 52 | } 53 | } 54 | 55 | struct Connection { 56 | Database* db; 57 | Source source; 58 | string path; 59 | sqlite3* sq; 60 | 61 | this(Database* db_, Source source_) { 62 | db = db_; 63 | source = source_; 64 | 65 | // map server to path while resolution rules are refined 66 | path = source.path.length != 0 ? source.path : source.server; // fix 67 | 68 | writeln("sqlite opening file: ", path); 69 | 70 | int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; 71 | int rc = sqlite3_open_v2(toStringz(path), &sq, flags, null); 72 | if (rc) { 73 | writeln("error: rc: ", rc, sqlite3_errmsg(sq)); 74 | } 75 | } 76 | 77 | ~this() { 78 | writeln("sqlite closing ", path); 79 | if (sq) { 80 | int rc = sqlite3_close(sq); 81 | sq = null; 82 | } 83 | } 84 | } 85 | 86 | struct Statement { 87 | 88 | enum State { 89 | Init, 90 | Execute, 91 | } 92 | 93 | Connection* con; 94 | string sql; 95 | State state; 96 | sqlite3* sq; 97 | sqlite3_stmt *st; 98 | bool hasRows; 99 | int binds_; 100 | 101 | this(Connection* con_, string sql_) { 102 | con = con_; 103 | sql = sql_; 104 | state = State.Init; 105 | sq = con.sq; 106 | } 107 | 108 | ~this() { 109 | //writeln("sqlite statement closing ", filename_); 110 | if (st) { 111 | int res = sqlite3_finalize(st); 112 | st = null; 113 | } 114 | } 115 | 116 | void bind(int col, int value){ 117 | int rc = sqlite3_bind_int( 118 | st, 119 | col, 120 | value); 121 | if (rc != SQLITE_OK) { 122 | throw_error("sqlite3_bind_int"); 123 | } 124 | } 125 | 126 | void bind(int col, const char[] value){ 127 | if(value is null) { 128 | int rc = sqlite3_bind_null(st, col); 129 | if (rc != SQLITE_OK) throw_error("bind1"); 130 | } else { 131 | //cast(void*)-1); 132 | int rc = sqlite3_bind_text( 133 | st, 134 | col, 135 | value.ptr, 136 | cast(int) value.length, 137 | null); 138 | if (rc != SQLITE_OK) { 139 | writeln(rc); 140 | throw_error("bind2"); 141 | } 142 | } 143 | } 144 | 145 | void bind(int n, Date d) { 146 | throw new DatabaseException("Date input binding not yet implemented"); 147 | } 148 | 149 | int binds() {return binds_;} 150 | 151 | void prepare() { 152 | if (!st) { 153 | int res = sqlite3_prepare_v2( 154 | sq, 155 | toStringz(sql), 156 | cast(int) sql.length + 1, 157 | &st, 158 | null); 159 | if (res != SQLITE_OK) throw_error(sq, "prepare", res); 160 | binds_ = sqlite3_bind_parameter_count(st); 161 | } 162 | } 163 | 164 | void query() { 165 | //if (state == State.Execute) throw new DatabaseException("already executed"); // restore 166 | if (state == State.Execute) return; 167 | state = State.Execute; 168 | int status = sqlite3_step(st); 169 | info("sqlite3_step: status: ", status); 170 | if (status == SQLITE_ROW) { 171 | hasRows = true; 172 | } else if (status == SQLITE_DONE) { 173 | reset(); 174 | } else { 175 | throw_error(sq, "step error", status); 176 | } 177 | } 178 | 179 | void query(X...) (X args) { 180 | bindAll(args); 181 | query(); 182 | } 183 | 184 | private void bindAll(T...) (T args) { 185 | int col; 186 | foreach (arg; args) bind(++col, arg); 187 | } 188 | 189 | void reset() { 190 | int status = sqlite3_reset(st); 191 | if (status != SQLITE_OK) throw new DatabaseException("sqlite3_reset error"); 192 | } 193 | 194 | } 195 | 196 | struct Bind { 197 | ValueType type; 198 | int idx; 199 | } 200 | 201 | struct Result { 202 | private Statement* stmt_; 203 | private sqlite3_stmt *st_; 204 | bool init_; 205 | int columns; 206 | int status_; 207 | 208 | // artifical bind array (for now) 209 | Array!Bind bind; 210 | 211 | this(Statement* stmt, int rowArraySize_) { 212 | stmt_ = stmt; 213 | st_ = stmt_.st; 214 | columns = sqlite3_column_count(st_); 215 | 216 | // artificial bind setup 217 | bind.reserve(columns); 218 | for(int i = 0; i < columns; ++i) { 219 | bind ~= Bind(); 220 | auto b = &bind.back(); 221 | b.type = ValueType.String; 222 | b.idx = i; 223 | } 224 | } 225 | 226 | //~this() {} 227 | 228 | bool hasRows() {return stmt_.hasRows;} 229 | 230 | int fetch() { 231 | // needs more attention 232 | if (!init_) { 233 | init_ = 1; 234 | return 1; 235 | } 236 | status_ = sqlite3_step(st_); 237 | if (status_ == SQLITE_ROW) return 1; 238 | if (status_ == SQLITE_DONE) { 239 | stmt_.reset(); 240 | return 0; 241 | } 242 | //throw new DatabaseException("sqlite3_step error: status: " ~ to!string(status_)); 243 | throw new DatabaseException("sqlite3_step error: status: "); 244 | } 245 | 246 | auto name(size_t idx) { 247 | import core.stdc.string: strlen; 248 | auto ptr = sqlite3_column_name(st_, cast(int) idx); 249 | return cast(string) ptr[0..strlen(ptr)]; 250 | } 251 | 252 | auto get(X:string)(Cell* cell) { 253 | import core.stdc.string: strlen; 254 | auto ptr = cast(immutable char*) sqlite3_column_text(st_, cast(int) cell.bind.idx); 255 | return cast(string) ptr[0..strlen(ptr)]; // fix with length 256 | } 257 | 258 | auto get(X:int)(Cell* cell) { 259 | return sqlite3_column_int(st_, cast(int) cell.bind.idx); 260 | } 261 | 262 | auto get(X:Date)(Cell* cell) { 263 | return Date(2016,1,1); // fix 264 | } 265 | 266 | } 267 | 268 | private static void throw_error()(sqlite3 *sq, string msg, int ret) { 269 | import std.conv; 270 | import core.stdc.string: strlen; 271 | const(char*) err = sqlite3_errmsg(sq); 272 | throw new DatabaseException("sqlite error: " ~ msg ~ ": " ~ to!string(err)); // need to send err 273 | } 274 | 275 | private static void throw_error()(string label) { 276 | throw new DatabaseException(label); 277 | } 278 | 279 | private static void throw_error()(string label, char *msg) { 280 | // frees up pass char * as required by sqlite 281 | import core.stdc.string : strlen; 282 | char[] m; 283 | sizediff_t sz = strlen(msg); 284 | m.length = sz; 285 | for(int i = 0; i != sz; i++) m[i] = msg[i]; 286 | sqlite3_free(msg); 287 | throw new DatabaseException(label ~ m.idup); 288 | } 289 | 290 | /* 291 | 292 | auto as(T:string)() { 293 | import core.stdc.string: strlen; 294 | auto ptr = cast(immutable char*) sqlite3_column_text(result_.st_, cast(int) idx_); 295 | return cast(string) ptr[0..strlen(ptr)]; // fix with length 296 | } 297 | 298 | auto chars() { 299 | import core.stdc.string: strlen; 300 | auto data = sqlite3_column_text(result_.st_, cast(int) idx_); 301 | return data ? data[0 .. strlen(data)] : data[0..0]; 302 | } 303 | 304 | // char*, string_ref? 305 | 306 | const(char*) toStringz() { 307 | // this may not work either because it's not around for the whole row 308 | return sqlite3_column_text(result_.st_, cast(int) idx_); 309 | } 310 | 311 | */ 312 | 313 | extern(C) int sqlite_callback(void* cb, int howmany, char** text, char** columns) { 314 | return 0; 315 | } 316 | 317 | } 318 | -------------------------------------------------------------------------------- /src/std/database/sqlite/package.d: -------------------------------------------------------------------------------- 1 | module std.database.sqlite; 2 | public import std.database.sqlite.database; 3 | -------------------------------------------------------------------------------- /src/std/database/testsuite.d: -------------------------------------------------------------------------------- 1 | module std.database.testsuite; 2 | import std.database.common; 3 | import std.database.util; 4 | import std.stdio; 5 | import std.experimental.logger; 6 | import std.datetime; 7 | 8 | import std.database.BasicDatabase: Feature; 9 | 10 | void testAll(Database) (string source) { 11 | 12 | databaseCreation!Database(source); 13 | 14 | auto db = Database(source); 15 | 16 | simpleInsertSelect(db); 17 | //create_score_table(db, "score"); 18 | 19 | classicSelect(db); 20 | cascadeTest(db); 21 | 22 | fieldAccess(db); 23 | 24 | bindTest0(db); 25 | bindTest1(db); 26 | bindTest2(db); 27 | bindInsertTest(db); 28 | dateBindTest(db); 29 | 30 | connectionWithSourceTest(db); 31 | 32 | polyTest!Database(source); 33 | } 34 | 35 | void databaseCreation(Database) (string source) { 36 | auto db1 = Database(); 37 | auto db2 = Database(source); 38 | } 39 | 40 | void simpleInsertSelect(D) (D db) { 41 | info("simpleInsertSelect"); 42 | create_score_table(db, "score"); 43 | 44 | db.query("insert into score values('Person',123)"); 45 | 46 | //writeRows(db.connection.statement("select * from score")); // maybe should work too? 47 | db.connection.statement("select * from score").query.writeRows; 48 | } 49 | 50 | void classicSelect(Database) (Database db) { 51 | // classic non-fluent style with inline iteration 52 | string table = "t1"; 53 | create_score_table(db, table); 54 | auto con = db.connection; 55 | auto range = con.query("select * from " ~ table).rows; 56 | foreach (r; range) { 57 | for(int c = 0; c != r.width; ++c) { 58 | if (c) write(","); 59 | write("", r[c]); 60 | } 61 | writeln; 62 | } 63 | } 64 | 65 | void fieldAccess(Database)(Database db) { 66 | auto rowSet = db.connection.query("select name,score from score").rows; 67 | foreach (r; rowSet) { 68 | writeln(r[0].as!string,",",r[1].as!int); 69 | } 70 | } 71 | 72 | void bindTest0(Database) (Database db) { 73 | if (!db.hasFeature(Feature.InputBinding)) { 74 | writeln("skip bindTest"); 75 | return; 76 | } 77 | 78 | create_score_table(db, "t1"); 79 | 80 | QueryVariable!(Database.queryVariableType) v; 81 | auto rows = db.connection.query("select name,score from t1 where score >= " ~ v.next(), 50).rows; 82 | //auto rows = db.connection.query("select name,score from t1"); 83 | assert(rows.width == 2); 84 | rows.writeRows; 85 | } 86 | 87 | 88 | void bindTest1(Database) (Database db) { 89 | if (!db.hasFeature(Feature.InputBinding)) { 90 | writeln("skip bindTest"); 91 | return; 92 | } 93 | 94 | create_score_table(db, "t1"); 95 | 96 | QueryVariable!(Database.queryVariableType) v; 97 | auto stmt = db.connection.statement("select name,score from t1 where score >= " ~ v.next()); 98 | //assert(stmt.binds() == 1); // prepare problem 99 | auto rows = stmt.query(50).rows; 100 | assert(rows.width == 2); 101 | rows.writeRows; 102 | } 103 | 104 | void bindTest2(Database) (Database db) { 105 | if (!db.hasFeature(Feature.InputBinding)) { 106 | writeln("skip bindTest"); 107 | return; 108 | } 109 | 110 | create_score_table(db, "t1"); 111 | 112 | // clean up / clarify 113 | 114 | QueryVariable!(Database.queryVariableType) v; 115 | auto stmt = db.connection 116 | .statement( 117 | "select name,score from t1 where score >= " ~ v.next() ~ 118 | " and score < " ~ v.next()); 119 | // assert(stmt.binds() == 2); // fix 120 | auto rows = stmt.query(50, 80).rows; 121 | assert(rows.width == 2); 122 | rows.writeRows; 123 | } 124 | 125 | void bindInsertTest(Database) (Database db) { 126 | if (!db.hasFeature(Feature.InputBinding)) { 127 | writeln("skip bindInsertTest"); 128 | return; 129 | } 130 | 131 | // bind insert test 132 | create_score_table(db, "score", false); 133 | auto con = db.connection; 134 | QueryVariable!(Database.queryVariableType) v; 135 | auto stmt = con.statement( 136 | "insert into score values(" ~ v.next() ~ 137 | "," ~ v.next() ~ ")"); 138 | stmt.query("a",1); 139 | stmt.query("b",2); 140 | stmt.query("c",3); 141 | con.query("select * from score").writeRows; 142 | } 143 | 144 | void dateBindTest(Database) (Database db) { 145 | // test date input and output binding 146 | import std.datetime; 147 | if (!db.hasFeature(Feature.DateBinding)) { 148 | writeln("skip dateInputBindTest"); 149 | return; 150 | } 151 | 152 | auto d = Date(2016,2,3); 153 | auto con = db.connection; 154 | db.drop_table("d1"); 155 | con.query("create table d1(a date)"); 156 | 157 | QueryVariable!(Database.queryVariableType) v; 158 | con.query("insert into d1 values(" ~ v.next() ~ ")", d); 159 | 160 | auto rows = con.query("select * from d1").rows; 161 | //assert(rows.front()[0].as!Date == d); 162 | rows.writeRows; 163 | } 164 | 165 | void cascadeTest(Database) (Database db) { 166 | writeln; 167 | writeln("cascade write_result test"); 168 | db 169 | .connection 170 | .query("select * from t1") 171 | .writeRows; 172 | writeln; 173 | } 174 | 175 | void connectionWithSourceTest(Database) (Database db) { 176 | //auto con = db.connection(db.defaultSource()); 177 | } 178 | 179 | 180 | void polyTest(DB) (string source) { 181 | // careful to distinguiush DB from imported Database type 182 | import std.database.poly; 183 | auto poly = createDatabase; 184 | poly.register!DB(source); 185 | registerDatabase!DB(source); 186 | auto polyDB = createDatabase; 187 | auto db = polyDB.database(source); 188 | } 189 | 190 | // utility stuff 191 | 192 | 193 | void drop_table(D) (D db, string table) { 194 | //db.query("drop table if exists " ~ table ~ ";"); 195 | try { 196 | info("drop table: ", table); 197 | db.query("drop table " ~ table); 198 | } catch (Exception e) { 199 | info("drop table error (ignored): ", e.msg); 200 | } 201 | } 202 | 203 | void create_simple_table(DB) (DB db, string table) { 204 | import std.conv; 205 | Db.Connection con = db.connection; 206 | con.query("create table " ~ table ~ "(a integer, b integer)"); 207 | for(int i = 0; i != 10; ++i) { 208 | con.query("insert into " ~ table ~ " values(1," ~ to!string(i) ~ ")"); 209 | } 210 | } 211 | 212 | void create_score_table(DB) (DB db, string table, bool data = true) { 213 | import std.conv; 214 | auto con = db.connection; 215 | auto names = ["Knuth", "Hopper", "Dijkstra"]; 216 | auto scores = [62, 48, 84]; 217 | 218 | db.drop_table(table); 219 | con.query("create table " ~ table ~ "(name varchar(10), score integer)"); 220 | 221 | if (!data) return; 222 | for(int i = 0; i != names.length; ++i) { 223 | con.query( 224 | "insert into " ~ table ~ " values(" ~ 225 | "'" ~ names[i] ~ "'" ~ "," ~ to!string(scores[i]) ~ ")"); 226 | } 227 | } 228 | 229 | 230 | // unused stuff 231 | //auto stmt = con.statement("select * from global_status where VARIABLE_NAME = ?", "UPTIME"); 232 | 233 | //db.showDrivers(); // odbc 234 | 235 | -------------------------------------------------------------------------------- /src/std/database/uri.d: -------------------------------------------------------------------------------- 1 | module std.database.uri; 2 | 3 | import std.stdio; 4 | import std.traits; 5 | import std.string; 6 | import std.experimental.logger; 7 | 8 | struct URI { 9 | string protocol,host,path,qs; 10 | int port; 11 | string[][string] query; 12 | 13 | string opIndex(string name) const { 14 | auto x = name in query; 15 | return (x is null ? "" : (*x)[$-1]); 16 | } 17 | } 18 | 19 | URI toURI(string str) { 20 | import std.conv; 21 | 22 | // examples: 23 | // protocol://server/path?a=1&b=2 24 | // protocol://host:port/path?a=1&b=2 25 | 26 | void error(string msg) {throw new Exception(msg ~ ", URI: " ~ str);} 27 | 28 | URI uri; 29 | auto s = str[0..$]; 30 | auto i = s.indexOf(':'); 31 | uri.protocol = s[0 .. i]; 32 | ++i; 33 | if (!(i+2 <= s.length && s[i] == '/' && s[i+1] == '/')) error("missing //"); 34 | i += 2; 35 | s = s[i .. $]; 36 | 37 | auto q = s.indexOf('?'); 38 | i = s.indexOf('/'); 39 | auto host = s[0 .. (i==-1 ? s.length : i)]; 40 | if (i != -1) { 41 | uri.path = s[i .. (q==-1 ? s.length : q)]; 42 | } 43 | 44 | auto colon = host.indexOf(':'); 45 | if (colon != -1) { 46 | uri.host = host[0 .. colon]; 47 | uri.port = to!int(host[colon+1 .. host.length]); 48 | } else { 49 | uri.host = host; 50 | } 51 | 52 | if (q == -1) return uri; 53 | 54 | 55 | 56 | uri.qs = s[q+1..$]; 57 | 58 | foreach (e; split(uri.qs, "&")) { 59 | auto j = e.indexOf('='); 60 | if (j==-1) error("missing ="); 61 | auto n = e[0..j], v = e[j+1..$]; 62 | uri.query[n] ~= v; //needs url decoding 63 | } 64 | 65 | return uri; 66 | } 67 | 68 | bool toURI(string str, ref URI uri) nothrow { 69 | try { 70 | auto u = toURI(str); 71 | uri = u; 72 | return true; 73 | } catch (Exception e) {} 74 | return true; 75 | } 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/std/database/util.d: -------------------------------------------------------------------------------- 1 | module std.database.util; 2 | 3 | import std.database.common; 4 | import std.stdio; 5 | import std.traits; 6 | import std.string; 7 | 8 | /* 9 | // trying to overload write/print 10 | void writeRows(T) (T t) 11 | if (hasMember!(T, "rows") || hasMember!(T, "rowSetTag")) {write(t);} 12 | */ 13 | 14 | // should return object being written to 15 | 16 | void writeRows(T) (T t) 17 | if (hasMember!(T, "rows")) { 18 | t.rows.writeRows; 19 | } 20 | 21 | void writeRows(T) (T t) 22 | if (hasMember!(T, "rowSetTag")) { 23 | static char[100] s = '-'; 24 | int w = 80; 25 | writeln(s[0..w-1]); 26 | foreach (r; t) { 27 | for(int c = 0; c != r.width; ++c) { 28 | if (c) write(", "); 29 | write("", r[c].chars); //chars sitll a problem 30 | } 31 | writeln(); 32 | } 33 | writeln(s[0..w-1]); 34 | } 35 | 36 | 37 | struct QueryVariable(QueryVariableType t : QueryVariableType.Dollar) { 38 | import std.conv; 39 | private int n = 1; 40 | auto front() {return "$" ~ to!string(n);} 41 | auto popFront() {++n;} 42 | auto next() {auto v = front(); popFront(); return v;} 43 | } 44 | 45 | struct QueryVariable(QueryVariableType t : QueryVariableType.QuestionMark) { 46 | auto front() {return "?";} 47 | auto next() {return front();} 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/std/database/variant.d: -------------------------------------------------------------------------------- 1 | module std.database.variant; 2 | import std.variant; 3 | import std.meta; 4 | import std.exception; 5 | import std.datetime; 6 | 7 | import std.experimental.logger; 8 | 9 | class UnpackException : Exception { 10 | this(Variant v) { 11 | super("can't convert variant: (value: " ~ v.toString() ~ ", type: " ~ v.type.toString() ~")"); 12 | } 13 | } 14 | 15 | static void unpackVariants(alias F, int i=0, A...)(A a) { 16 | //alias Types = AliasSeq!(byte, ubyte, string, char, dchar, int, uint, long, ulong); 17 | alias Types = AliasSeq!(int, string, Date); 18 | 19 | static void call(int i, T, A...)(T v, A a) { 20 | unpackVariants!(F,i+1)(a[0..i], v, a[(i+1)..$]); 21 | } 22 | 23 | static if (i == a.length) { 24 | F(a); 25 | } else { 26 | //log("type: ", a[i].type); 27 | foreach(T; Types) { 28 | //log("--TYPE: ", typeid(T)); 29 | //if (a[i].type == typeid(T)) 30 | if (a[i].convertsTo!T) { 31 | call!i(a[i].get!T,a); 32 | return; 33 | } 34 | } 35 | throw new UnpackException(a[i]); 36 | } 37 | } 38 | 39 | unittest { 40 | import std.array; 41 | import std.algorithm.iteration; 42 | import std.conv : text; 43 | 44 | //joiner(["a","b"],","); 45 | //join([Variant(1), Variant(2)],Variant(",")); 46 | 47 | static void F(A...)(A a) {log("unpacked: ", a);} 48 | 49 | unpackVariants!F(Variant(1), Variant(2)); 50 | unpackVariants!F(Variant(1), Variant("abc")); 51 | unpackVariants!F(Variant(1), Variant("abc"), Variant(Date(2015,1,1))); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/std/database/vibehandler.d: -------------------------------------------------------------------------------- 1 | module std.database.vibehandler; 2 | import std.socket; 3 | 4 | 5 | struct VibeHandler(T) { 6 | import core.time: Duration, dur; 7 | import vibe.core.core; 8 | 9 | alias Event = FileDescriptorEvent; 10 | Duration timeout = dur!"seconds"(10); 11 | Event event; 12 | 13 | // both for posix sockets 14 | 15 | void addSocket(int sock) { 16 | event = createFileDescriptorEvent(sock, FileDescriptorEvent.Trigger.any); 17 | } 18 | 19 | void addSocket(Socket sock) {addSocket(sock.handle);} 20 | 21 | void wait() { 22 | //event.wait(timeout); 23 | //event.wait(FileDescriptorEvent.Trigger.read); 24 | event.wait(FileDescriptorEvent.Trigger.read); 25 | } 26 | 27 | void yield() { 28 | import vibe.core.core: yield; 29 | yield(); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /travis-ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -x -o pipefail 3 | 4 | dub test :sqlite 5 | 6 | -------------------------------------------------------------------------------- /util/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "util", 3 | "description": "Phobos std.database candidate", 4 | "homepage": "", 5 | "copyright": "Copyright © 2015-, Erik Smith", 6 | "authors": [ 7 | "Erik Smith" 8 | ], 9 | "sourcePaths": [".", "../src"], 10 | "dependencies": { 11 | }, 12 | "configurations": [ 13 | { 14 | "name": "library", 15 | "targetType": "executable", 16 | }, 17 | { 18 | "name": "unittest", 19 | "targetType": "executable", 20 | "targetName": "unittest", 21 | "dflags": ["-main"], 22 | }, 23 | ], 24 | } 25 | 26 | -------------------------------------------------------------------------------- /util/test.d: -------------------------------------------------------------------------------- 1 | module std.database.test; 2 | import std.stdio; 3 | import std.database.uri; 4 | import std.experimental.logger; 5 | 6 | unittest { 7 | URI uri = toURI("protocol://host"); 8 | assert(uri.protocol == "protocol"); 9 | assert(uri.host == "host"); 10 | log(uri.path); 11 | assert(uri.path == ""); 12 | } 13 | 14 | unittest { 15 | URI uri = toURI("protocol://host:1234/"); 16 | assert(uri.protocol == "protocol"); 17 | assert(uri.host == "host"); 18 | assert(uri.port == 1234); 19 | } 20 | 21 | unittest { 22 | URI uri = toURI("protocol://host/path"); 23 | assert(uri.protocol == "protocol"); 24 | assert(uri.host == "host"); 25 | assert(uri.path == "/path"); 26 | } 27 | 28 | unittest { 29 | URI uri = toURI("protocol://host/path?a=1&b=2"); 30 | assert(uri.protocol == "protocol"); 31 | assert(uri.host == "host"); 32 | assert(uri.path == "/path"); 33 | assert(uri.qs == "a=1&b=2"); 34 | assert(uri["a"] == "1"); 35 | assert(uri["b"] == "2"); 36 | } 37 | 38 | unittest { 39 | URI uri = toURI("file:///filename.ext"); 40 | assert(uri.host == ""); 41 | assert(uri.path == "/filename.ext"); 42 | } 43 | 44 | -------------------------------------------------------------------------------- /vibe/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vibe", 3 | "description": "Phobos std.database candidate", 4 | "homepage": "", 5 | "copyright": "Copyright © 2015-, Erik Smith", 6 | "authors": [ 7 | "Erik Smith" 8 | ], 9 | "sourcePaths": [".", "../src"], 10 | "libs" : ["pq","pgtypes"], 11 | "dependencies": { 12 | "vibe-d:core": "~>0.7.29-alpha.3" 13 | }, 14 | "configurations": [ 15 | { 16 | "name": "library", 17 | "targetType": "executable", 18 | }, 19 | { 20 | "name": "unittest", 21 | "targetType": "executable", 22 | "targetName": "unittest", 23 | "dflags": ["-main"], 24 | }, 25 | ], 26 | } 27 | 28 | -------------------------------------------------------------------------------- /vibe/test.d: -------------------------------------------------------------------------------- 1 | module std.database.vibe.test; 2 | import std.database.util; 3 | import std.database.common; 4 | import std.database.postgres; 5 | import std.database.allocator; 6 | import std.stdio; 7 | 8 | import std.experimental.logger; 9 | 10 | import std.database.postgres.bindings; 11 | import std.socket; 12 | 13 | import std.database.vibehandler; 14 | import vibe.core.core; 15 | 16 | unittest { 17 | vibeTest(); 18 | } 19 | 20 | auto posixSocket(PGconn *c) { 21 | int r = PQsocket(c); 22 | if(r == -1) throw new Exception("bad socket value"); 23 | return r; 24 | } 25 | 26 | Socket toSocket(int posixSocket) { 27 | import core.sys.posix.unistd: dup; 28 | socket_t s = cast(socket_t) dup(cast(socket_t) posixSocket); 29 | return new Socket(s, AddressFamily.UNSPEC); 30 | } 31 | 32 | bool ready(PGconn *c) { 33 | return PQconnectPoll(c) == PGRES_POLLING_OK; 34 | } 35 | 36 | struct MyAsyncPolicy { 37 | alias Allocator = MyMallocator; 38 | static const bool nonblocking = true; 39 | alias Handler = VibeHandler!int; 40 | } 41 | 42 | 43 | void vibeTest() { 44 | //import vibe.core.concurrency; // std concurrency conflict 45 | //import std.socket; 46 | //import core.sys.posix.sys.select; 47 | 48 | alias DB = Database!MyAsyncPolicy; 49 | auto db = DB("postgres://127.0.0.1/test"); 50 | 51 | auto con = db.connection(); 52 | //auto c = cast(PGconn*) c.handle(); 53 | 54 | auto rows = con.query("select name from score").rows; 55 | rows.writeRows; 56 | 57 | /* 58 | Socket sock = toSocket(posixSocket(con)); 59 | 60 | sock.blocking = false; 61 | 62 | VibeHandler!int handler; 63 | 64 | runTask({ 65 | log("a"); 66 | handler.yield(); 67 | log("b"); 68 | }); 69 | 70 | 71 | runTask({ 72 | log("1"); 73 | handler.yield(); 74 | log("2"); 75 | }); 76 | 77 | log("yield"); 78 | yield(); 79 | */ 80 | 81 | /* 82 | auto db = createAsyncDatabase("postgres://127.0.0.1/test"); 83 | //auto con = cast(PGconn*) db.connection().handle(); // why not? possible resource destruct 84 | auto c = db.connection(); 85 | auto con = cast(PGconn*) c.handle(); 86 | */ 87 | 88 | /* 89 | auto future1 = async({ 90 | log("async1"); 91 | return 1; 92 | }); 93 | 94 | auto future2 = async({ 95 | log("async2"); 96 | return 2; 97 | }); 98 | */ 99 | 100 | // can't join in different threads 101 | //log("result1: ", future1.getResult); 102 | //log("result2: ", future2.getResult); 103 | 104 | } 105 | 106 | 107 | 108 | /* 109 | 110 | //auto stmt = con.query("select name from score"); 111 | 112 | log("socket: ", socket); 113 | 114 | // loop 115 | 116 | SocketSet readset, writeset, errorset; 117 | uint setSize = FD_SETSIZE; 118 | log("setSize:", setSize); 119 | 120 | readset = new SocketSet(setSize); 121 | writeset = new SocketSet(setSize); 122 | errorset = new SocketSet(setSize); 123 | 124 | 125 | // loop until no sockets left 126 | while (true) { 127 | auto socket = PQsocket(con); 128 | 129 | readset.reset(); 130 | writeset.reset(); 131 | errorset.reset(); 132 | 133 | //socket_t s; 134 | //readset.add(socket); 135 | 136 | log("select waiting..."); 137 | int events = Socket.select(readset, writeset, errorset); 138 | } 139 | 140 | //alias fd_set_type = typeof(fd_set.init.tupleof[0][0]); 141 | //enum FD_NFDBITS = 8 * fd_set_type.sizeof; 142 | //log("FD_NFDBITS: ", FD_NFDBITS); 143 | */ 144 | 145 | 146 | --------------------------------------------------------------------------------