diff --git a/.gitignore b/.gitignore index 70d1fa2..cbbfccf 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,11 @@ dub.selections.json #unit test binaries tests/*/tests __test__*__ - +*.user +*.userprefs +.directory +.project +*.txt +*.cmake +*.kdev4 +.kdev4 diff --git a/dub.json b/dub.json index e54a687..93c9896 100644 --- a/dub.json +++ b/dub.json @@ -1,8 +1,8 @@ { - "name": "dstddb", + "name": "database", "description": "A D standard database proposal and implementation", - "copyright": "Copyright © 2015, Erik Smith", - "authors": ["Erik Smith"], + "copyright": "Copyright (C) 2015-2016 Shanghai Putao Technology Co., Ltd", + "authors": ["dlang team of putao labs."], "license": "MIT", "sourcePaths": ["src"] } diff --git a/mysql/test.d b/mysql/test.d index d644ca4..41ac1fe 100644 --- a/mysql/test.d +++ b/mysql/test.d @@ -4,8 +4,6 @@ import std.database.mysql; import std.experimental.logger; import std.stdio; -import std.database.rowset; - unittest { import std.database.testsuite; alias DB = Database!DefaultPolicy; diff --git a/src/std/database/allocator.d b/src/std/database/allocator.d index a498f4c..8e0830e 100644 --- a/src/std/database/allocator.d +++ b/src/std/database/allocator.d @@ -10,6 +10,7 @@ struct MyMallocator { import core.stdc.stdlib : malloc; if (!bytes) return null; auto p = malloc(bytes); + //log("allocate: ptr: ", p , " size :",bytes); return p ? p[0 .. bytes] : null; } @@ -17,8 +18,9 @@ struct MyMallocator { @system // removed @nogc and nothrow for logging bool deallocate(void[] b) { import core.stdc.stdlib : free; - //log("deallocate: ptr: ", b.ptr, "size: ", b.length); + //log("deallocate: ptr: ", b.ptr, " size: ", b.length); free(b.ptr); + //log("return true"); return true; } diff --git a/src/std/database/freetds/database.d b/src/std/database/freetds/database.d index 5840231..782f3e3 100644 --- a/src/std/database/freetds/database.d +++ b/src/std/database/freetds/database.d @@ -16,50 +16,47 @@ import std.experimental.logger; public import std.database.allocator; import std.database.front; import std.datetime; +import std.variant; struct DefaultPolicy { alias Allocator = MyMallocator; } -alias Database(T) = BasicDatabase!(Driver!T,T); +alias Database(T) = BasicDatabase!(Driver!T, T); -auto createDatabase()(string defaultURI="") { - return Database!DefaultPolicy(defaultURI); +auto createDatabase()(string defaultURI = "") { + return Database!DefaultPolicy(defaultURI); } - struct Driver(Policy) { alias Allocator = Policy.Allocator; - alias Cell = BasicCell!(Driver!Policy,Policy); + alias Cell = BasicCell!(Driver!Policy, Policy); private static bool isError(RETCODE ret) { - return - ret != SUCCEED && - ret != REG_ROW && - ret != NO_MORE_ROWS && - ret != NO_MORE_RESULTS; + return ret != SUCCEED && ret != REG_ROW && ret != NO_MORE_ROWS && ret != NO_MORE_RESULTS; } static T* check(T)(string msg, T* object) { info(msg); - if (object == null) throw new DatabaseException("error: " ~ msg); + if (object == null) + throw new DatabaseException("error: " ~ msg); return object; } static RETCODE check(string msg, RETCODE ret) { info(msg, " : ", ret); - if (isError(ret)) throw new DatabaseException("error: " ~ msg); + if (isError(ret)) + throw new DatabaseException("error: " ~ msg); return ret; } struct Database { alias queryVariableType = QueryVariableType.QuestionMark; - static const FeatureArray features = [ - //Feature.InputBinding, - //Feature.DateBinding, - //Feature.ConnectionPool, - ]; + static const FeatureArray features = [//Feature.InputBinding, + //Feature.DateBinding, + //Feature.ConnectionPool, + ]; Allocator allocator; @@ -72,43 +69,30 @@ struct Driver(Policy) { } ~this() { - info("~Database"); + // info("~Database"); //dbexit(); // should this be called (only on process exit?) } + static extern (C) int errorHandler(DBPROCESS* dbproc, int severity, + int dberr, int oserr, char* dberrstr, char* oserrstr) { - static extern(C) int errorHandler( - DBPROCESS* dbproc, - int severity, - int dberr, - int oserr, - char *dberrstr, - char *oserrstr) { - - auto con = cast(Connection *) dbgetuserdata(dbproc); + auto con = cast(Connection*) dbgetuserdata(dbproc); if (con) { con.error.message = to!string(dberrstr); } - info("error: ", - "severity: ", severity, - ", db error: ", to!string(dberrstr), - ", os error: ", to!string(oserrstr)); + info("error: ", "severity: ", severity, ", db error: ", + to!string(dberrstr), ", os error: ", to!string(oserrstr)); return INT_CANCEL; } - static extern(C) int msgHandler( - DBPROCESS *dbproc, - DBINT msgno, - int msgstate, - int severity, - char *msgtext, - char *srvname, - char *procname, - int line) { - - auto con = cast(Connection *) dbgetuserdata(dbproc); - if (con) {} + static extern (C) int msgHandler(DBPROCESS* dbproc, DBINT msgno, + int msgstate, int severity, char* msgtext, char* srvname, char* procname, + int line) { + + auto con = cast(Connection*) dbgetuserdata(dbproc); + if (con) { + } info("msg: ", to!string(msgtext), ", severity:", severity); return 0; } @@ -118,8 +102,8 @@ struct Driver(Policy) { struct Connection { Database* db; Source source; - LOGINREC *login; - DBPROCESS *con; + LOGINREC* login; + DBPROCESS* con; DatabaseError error; @@ -140,22 +124,26 @@ struct Driver(Policy) { string server; if (source.host.length != 0) { server = source.host ~ ":" ~ to!string(source.port); - } else { + } + else { server = source.server; } //con = dbopen(login, toStringz(source.server)); - con = check("tdsdbopen: " ~ server, tdsdbopen(login, toStringz(server), 1)); + con = check("tdsdbopen: " ~ server, tdsdbopen(login, toStringz(server), + 1)); - dbsetuserdata(con, cast(BYTE*) &this); + dbsetuserdata(con, cast(BYTE*)&this); check("dbuse", dbuse(con, toStringz(source.database))); } ~this() { //info("~Connection: ", source); - if (con) dbclose(con); - if (login) dbloginfree(login); + if (con) + dbclose(con); + if (login) + dbloginfree(login); } } @@ -163,7 +151,7 @@ struct Driver(Policy) { struct Statement { Connection* con; string sql; - Allocator *allocator; + Allocator* allocator; int binds; //Array!Bind inputbind_; @@ -186,35 +174,41 @@ struct Driver(Policy) { RETCODE status = check("dbsqlexec: ", dbsqlexec(con.con)); } - void query(X...) (X args) { + void query(X...)(X args) { bindAll(args); query(); } //bool hasRows() {return status != NO_MORE_RESULTS;} - bool hasRows() {return true;} + bool hasRows() { + return true; + } - private void bindAll(T...) (T args) { + private void bindAll(T...)(T args) { //int col; //foreach (arg; args) bind(++col, arg); } - void bind(int n, int value) {} - void bind(int n, const char[] value) {} + void bind(int n, int value) { + } + + void bind(int n, const char[] value) { + } void reset() { } private RETCODE check(string msg, RETCODE ret) { info(msg, " : ", ret); - if (isError(ret)) throw new DatabaseException(con.error, msg); + if (isError(ret)) + throw new DatabaseException(con.error, msg); return ret; } } struct Describe { - char *name; - char *buffer; + char* name; + char* buffer; int type; int size; int status; @@ -232,14 +226,19 @@ struct Driver(Policy) { //int columns() {return columns;} Statement* stmt; - Allocator *allocator; + Allocator* allocator; int columns; Array!Describe describe; Array!Bind bind; RETCODE status; - private auto con() {return stmt.con;} - private auto dbproc() {return con.con;} + private auto con() { + return stmt.con; + } + + private auto dbproc() { + return con.con; + } this(Statement* stmt_, int rowArraySize_) { stmt = stmt_; @@ -251,15 +250,16 @@ struct Driver(Policy) { } ~this() { - foreach(b; bind) allocator.deallocate(b.data); + foreach (b; bind) + allocator.deallocate(b.data); } void build_describe() { describe.reserve(columns); - for(int i = 0; i < columns; ++i) { - int c = i+1; + for (int i = 0; i < columns; ++i) { + int c = i + 1; describe ~= Describe(); auto d = &describe.back(); @@ -276,59 +276,53 @@ struct Driver(Policy) { bind.reserve(columns); - for(int i = 0; i < columns; ++i) { - int c = i+1; + for (int i = 0; i < columns; ++i) { + int c = i + 1; bind ~= Bind(); auto b = &bind.back(); auto d = &describe[i]; int allocSize; switch (d.type) { - case SYBCHAR: - b.type = ValueType.String; - b.size = 255; - allocSize = b.size+1; - b.bindType = NTBSTRINGBIND; - break; - case SYBMSDATE: - b.type = ValueType.Date; - b.size = DBDATETIME.sizeof; - allocSize = b.size; - b.bindType = DATETIMEBIND; - break; - default: - b.type = ValueType.String; - b.size = 255; - allocSize = b.size+1; - b.bindType = NTBSTRINGBIND; - break; + case SYBCHAR: + b.type = ValueType.String; + b.size = 255; + allocSize = b.size + 1; + b.bindType = NTBSTRINGBIND; + break; + case SYBMSDATE: + b.type = ValueType.Date; + b.size = DBDATETIME.sizeof; + allocSize = b.size; + b.bindType = DATETIMEBIND; + break; + default: + b.type = ValueType.String; + b.size = 255; + allocSize = b.size + 1; + b.bindType = NTBSTRINGBIND; + break; } b.data = allocator.allocate(allocSize); // make consistent acros dbs GC.addRange(b.data.ptr, b.data.length); - check("dbbind", dbbind( - dbproc, - c, - b.bindType, - cast(DBINT) b.data.length, - cast(BYTE*) b.data.ptr)); + check("dbbind", dbbind(dbproc, c, b.bindType, + cast(DBINT) b.data.length, cast(BYTE*) b.data.ptr)); check("dbnullbind", dbnullbind(dbproc, c, &b.status)); - info( - "output bind: index: ", i, - ", type: ", b.bindType, - ", size: ", b.size, - ", allocSize: ", b.data.length); + info("output bind: index: ", i, ", type: ", b.bindType, + ", size: ", b.size, ", allocSize: ", b.data.length); } } int fetch() { status = check("dbnextrow", dbnextrow(dbproc)); if (status == REG_ROW) { - return 1; - } else if (status == NO_MORE_ROWS) { + return 1; + } + else if (status == NO_MORE_ROWS) { stmt.reset(); return 0; } @@ -339,32 +333,42 @@ struct Driver(Policy) { return to!string(describe[idx].name); } - auto get(X:string)(Cell* cell) { - import core.stdc.string: strlen; - checkType(cell.bind.bindType, NTBSTRINGBIND); - auto ptr = cast(immutable char*) cell.bind.data.ptr; - return cast(string) ptr[0..strlen(ptr)]; + ubyte[] rawData(Cell* cell) { //TODO: fix + return null; } - auto get(X:int)(Cell* cell) { - //if (b.bindType == SQL_C_CHAR) return to!int(as!string()); // tmp hack - //checkType(b.bindType, SQL_C_LONG); - //return *(cast(int*) b.data); - return 0; + Variant getValue(Cell* cell) { + Variant value; + if (cell.bind.type == ValueType.Date) { + auto ptr = cast(DBDATETIME*) cell.bind.data.ptr; + DBDATEREC d; + check("dbdatecrack", dbdatecrack(dbproc, &d, ptr)); + value = Date(d.year, d.month, d.day); + + } + else { + import core.stdc.string : strlen; + + checkType(cell.bind.bindType, NTBSTRINGBIND); + auto ptr = cast(immutable char*) cell.bind.data.ptr; + value = cast(string) ptr[0 .. strlen(ptr)]; + } + return value; } - auto get(X:Date)(Cell* cell) { - auto ptr = cast(DBDATETIME*) cell.bind.data.ptr; - DBDATEREC d; - check("dbdatecrack", dbdatecrack(dbproc, &d, ptr)); - return Date(d.year, d.month, d.day); + auto type(int col){ + return describe[i].type; + } + + bool isNull(Cell* cell) { + return false; } void checkType(int a, int b) { - if (a != b) throw new DatabaseException("type mismatch"); + if (a != b) + throw new DatabaseException("type mismatch"); } } } - diff --git a/src/std/database/front.d b/src/std/database/front.d index ca72e6c..3aa4845 100644 --- a/src/std/database/front.d +++ b/src/std/database/front.d @@ -12,10 +12,9 @@ import std.database.source; import std.database.allocator; import std.traits; import std.container.array; -import std.variant; +public import std.variant; import std.range.primitives; -import std.database.option; public import std.database.array; @@ -29,20 +28,142 @@ public import std.database.array; (version_major == 2 && version_minor == 70)); */ +struct Time { + this(uint h, uint m, uint s = 0, uint ms = 0) { + hour = h; + minute = m; + second = s; + msecond = ms; + } + + uint hour; + uint minute; + uint second; + uint msecond; + + string toString(){ + import std.string; + import std.format; + return format("%d:%d:%d.%d",hour,minute,second,msecond); + } + + static Time formString(string str){ + import std.string; + import std.array; + import std.conv; + int idx = cast(int)str.indexOf("."); + string dt; + uint msec = 0; + if(idx > 0) + { + dt = str[0..idx]; + msec = to!uint(str[idx..$]); + } + else + { + dt = str; + } + string[] tm = dt.split(":"); + if(tm.length != 3) + throw new Exception("erro string To Time : ", str); + return Time(to!uint(tm[0]),to!uint(tm[1]),to!uint(tm[2]),msec); + } +} enum ValueType { + Char, + + Short, Int, + Long, + + Float, + Double, + String, + Date, - Variant, -} + Time, + DateTime, + Raw, + + UNKnown +} // improve -struct TypeInfo(T:int) {static auto type() {return ValueType.Int;}} -struct TypeInfo(T:string) {static auto type() {return ValueType.String;}} -struct TypeInfo(T:Date) {static auto type() {return ValueType.Date;}} -struct TypeInfo(T:Variant) {static auto type() {return ValueType.Variant;}} +struct TypeInfo(T : char) +{ + static auto type() { + return ValueType.Char; + } +} +struct TypeInfo(T : short) +{ + static auto type() { + return ValueType.Short; + } +} + +struct TypeInfo(T : int) { + static auto type() { + return ValueType.Int; + } +} + +struct TypeInfo(T : long) { + static auto type() { + return ValueType.Long; + } +} + +struct TypeInfo(T : float) { + static auto type() { + return ValueType.Float; + } +} + +struct TypeInfo(T : double) { + static auto type() { + return ValueType.Double; + } +} + +struct TypeInfo(T : string) { + static auto type() { + return ValueType.String; + } +} + +struct TypeInfo(T : Date) { + static auto type() { + return ValueType.Date; + } +} + +struct TypeInfo(T : Time) { + static auto type() { + return ValueType.Time; + } +} + +struct TypeInfo(T : DateTime) { + static auto type() { + return ValueType.DateTime; + } +} + +struct TypeInfo(T : ubyte[]) { + static auto type() { + return ValueType.Raw; + } +} + +/*struct TypeInfo(T : Variant) { + static auto type() { + return ValueType.Variant; + } +}*/ enum Feature { InputBinding, @@ -53,35 +174,60 @@ enum Feature { alias FeatureArray = Feature[]; -struct BasicDatabase(D,P) { +struct BasicDatabase(D, P) { alias Driver = D; alias Policy = P; alias Database = Driver.Database; alias Allocator = Policy.Allocator; - alias Connection = BasicConnection!(Driver,Policy); - alias Cell = BasicCell!(Driver,Policy); + alias Connection = BasicConnection!(Driver, Policy); + alias Cell = BasicCell!(Driver, Policy); alias Pool = .Pool!(Driver.Connection); alias ScopedResource = .ScopedResource!Pool; alias queryVariableType = Database.queryVariableType; - static auto create() {return BasicDatabase(null);} - static auto create(string url) {return BasicDatabase(url);} + static auto create() { + return BasicDatabase(null); + } + + static auto create(string url) { + return BasicDatabase(url); + } - auto connection() {return Connection(this);} - auto connection(string uri) {return Connection(this, uri);} + auto connection() { + return Connection(this); + } - auto statement(string sql) {return connection().statement(sql);} - auto statement(X...) (string sql, X args) {return connection.statement(sql,args);} + auto connection(string uri) { + return Connection(this, uri); + } - auto query(string sql) {return connection().query(sql);} - auto query(T...) (string sql, T args) {return statement(sql).query(args);} + auto statement(string sql) { + return connection().statement(sql); + } + + auto statement(X...)(string sql, X args) { + return connection.statement(sql, args); + } + + auto query(string sql) { + return connection().query(sql); + } + + auto query(T...)(string sql, T args) { + return statement(sql).query(args); + } //static bool hasFeature(Feature feature); // go with non-static hasFeature for now to accomidate poly driver - bool hasFeature(Feature feature) {return hasFeature(data_.database, feature);} -auto ref driverDatabase() {return data_.database;} + bool hasFeature(Feature feature) { + return hasFeature(data_.database, feature); + } + + auto ref driverDatabase() { + return data_.database; + } private struct Payload { string defaultURI; @@ -105,12 +251,12 @@ auto ref driverDatabase() {return data_.database;} // for poly static if (hasMember!(Database, "register")) { - static void register(DB) (string name = "") { - Database.register!DB(name); + static void register(DB)(string name = "") { + Database.register!DB(name); } auto database(string name) { - auto db = BasicDatabase(data_.defaultURI); + auto db = BasicDatabase(data_.defaultURI); db.data_.database.setDatabase(name); return db; } @@ -118,27 +264,39 @@ auto ref driverDatabase() {return data_.database;} private static bool hasFeature(ref Database db, Feature feature) { import std.algorithm; - import std.range.primitives: empty; + import std.range.primitives : empty; + //auto r = find(Database.features, feature); auto r = find(db.features, feature); return !r.empty; } } -struct BasicConnection(D,P) { +struct BasicConnection(D, P) { alias Driver = D; alias Policy = P; alias DriverConnection = Driver.Connection; - alias Statement = BasicStatement!(Driver,Policy); - alias Database = BasicDatabase!(Driver,Policy); + alias Statement = BasicStatement!(Driver, Policy); + alias Database = BasicDatabase!(Driver, Policy); alias Pool = Database.Pool; alias ScopedResource = Database.ScopedResource; alias DatabaseImpl = Driver.Database; - auto statement(string sql) {return Statement(this,sql);} - auto statement(X...) (string sql, X args) {return Statement(this,sql,args);} - auto query(string sql) {return statement(sql).query();} - auto query(T...) (string sql, T args) {return statement(sql).query(args);} + auto statement(string sql) { + return Statement(this, sql); + } + + auto statement(X...)(string sql, X args) { + return Statement(this, sql, args); + } + + auto query(string sql) { + return statement(sql).query(); + } + + auto query(T...)(string sql, T args) { + return statement(sql).query(args); + } auto rowArraySize(int rows) { rowArraySize_ = rows; @@ -153,7 +311,9 @@ struct BasicConnection(D,P) { string uri_; int rowArraySize_ = 1; - package this(Database db) {this(db,"");} + package this(Database db) { + this(db, ""); + } package this(Database db, string uri) { //db_ = db; @@ -184,7 +344,9 @@ struct BasicConnection(D,P) { } static if (hasMember!(Database, "socket")) { - auto socket() {return driverConnection.socket;} + auto socket() { + return driverConnection.socket; + } } // handle() @@ -192,7 +354,9 @@ struct BasicConnection(D,P) { // while a handle is in use (Use a connection variable to extend scope) static if (hasMember!(Database, "handle")) { - auto handle() {return driverConnection.handle;} + auto handle() { + return driverConnection.handle; + } } /* @@ -202,17 +366,16 @@ data_ = Data(&db.data_.refCountedPayload(),uri); } */ - } -struct BasicStatement(D,P) { +struct BasicStatement(D, P) { alias Driver = D; alias Policy = P; alias DriverStatement = Driver.Statement; - alias Connection = BasicConnection!(Driver,Policy); - alias Result = BasicResult!(Driver,Policy); - alias RowSet = BasicRowSet!(Driver,Policy); - alias ColumnSet = BasicColumnSet!(Driver,Policy); + alias Connection = BasicConnection!(Driver, Policy); + alias Result = BasicResult!(Driver, Policy); + alias RowSet = BasicRowSet!(Driver, Policy); + alias ColumnSet = BasicColumnSet!(Driver, Policy); //alias Allocator = Policy.Allocator; alias ScopedResource = Connection.ScopedResource; @@ -230,10 +393,9 @@ struct BasicStatement(D,P) { Executed, } - this(Connection con, string sql) { con_ = con; - data_ = Data(con_.driverConnection,sql); + data_ = Data(con_.driverConnection, sql); rowArraySize_ = con.rowArraySize_; prepare(); } @@ -248,11 +410,18 @@ struct BasicStatement(D,P) { } */ - string sql() {return data_.sql;} + string sql() { + return data_.sql; + } //int binds() {return data_.binds;} - void bind(int n, int value) {data_.bind(n, value);} - void bind(int n, const char[] value){data_.bind(n,value);} + void bind(int n, int value) { + data_.bind(n, value); + } + + void bind(int n, const char[] value) { + data_.bind(n, value); + } auto query() { data_.query(); @@ -260,19 +429,22 @@ struct BasicStatement(D,P) { return this; } - auto query(X...) (X args) { + auto query(X...)(X args) { data_.query(args); state = State.Executed; return this; } - auto into(A...) (ref A args) { - if (state != State.Executed) throw new DatabaseException("not executed"); + auto into(A...)(ref A args) { + if (state != State.Executed) + throw new DatabaseException("not executed"); rows.into(args); return this; } - bool hasRows() {return data_.hasRows;} + bool hasRows() { + return data_.hasRows; + } // rows() // accessor for single rowSet returned @@ -280,24 +452,26 @@ struct BasicStatement(D,P) { // alternate name: rowSet, table auto rows() { - if (state != State.Executed) query(); + if (state != State.Executed) + query(); return RowSet(Result(this, rowArraySize_)); } auto columns() { - if (state != State.Executed) query(); + if (state != State.Executed) + query(); return ColumnSet(Result(this, rowArraySize_)); } // results() // returns range for one or more things returned by a query auto results() { - if (state != State.Executed) throw new DatabaseException("not executed"); + if (state != State.Executed) + throw new DatabaseException("not executed"); return 0; // fill in } - - private: +private: alias RefCounted!(DriverStatement, RefCountedAutoInitialize.no) Data; Data data_; @@ -310,16 +484,17 @@ struct BasicStatement(D,P) { state = State.Prepared; } - void reset() {data_.reset();} //SQLCloseCursor + void reset() { + data_.reset(); + } //SQLCloseCursor } - -struct BasicResult(D,P) { +struct BasicResult(D, P) { alias Driver = D; alias Policy = P; alias ResultImpl = Driver.Result; - alias Statement = BasicStatement!(Driver,Policy); - alias RowSet = BasicRowSet!(Driver,Policy); + alias Statement = BasicStatement!(Driver, Policy); + alias RowSet = BasicRowSet!(Driver, Policy); //alias Allocator = Driver.Policy.Allocator; alias Bind = Driver.Bind; //alias Row = .Row; @@ -328,7 +503,8 @@ struct BasicResult(D,P) { stmt_ = stmt; rowArraySize_ = rowArraySize; data_ = Data(&stmt.data_.refCountedPayload(), rowArraySize_); - if (!stmt_.hasRows) throw new DatabaseException("not a result query"); + if (!stmt_.hasRows) + throw new DatabaseException("not a result query"); rowsFetched_ = data_.fetch(); } @@ -336,20 +512,25 @@ struct BasicResult(D,P) { //auto opSlice() {return ResultRange(this);} package: - int rowsFetched() {return rowsFetched_;} + int rowsFetched() { + return rowsFetched_; + } bool next() { if (++rowIdx_ == rowsFetched_) { rowsFetched_ = data_.fetch(); - if (!rowsFetched_) return false; + if (!rowsFetched_) + return false; rowIdx_ = 0; } return true; } - auto ref result() {return data_.refCountedPayload();} + auto ref result() { + return data_.refCountedPayload(); + } - private: +private: Statement stmt_; int rowArraySize_; //maybe move into RC int rowIdx_; @@ -359,48 +540,66 @@ package: Data data_; // these need to move - int width() {return data_.columns;} + int width() { + return data_.columns; + } private size_t index(string name) { import std.uni; + // slow name lookup, all case insensitive for now - for(int i=0; i!=width; i++) { - if (sicmp(data_.name(i), name) == 0) return i; + for (int i = 0; i != width; i++) { + if (sicmp(data_.name(i), name) == 0) + return i; } throw new DatabaseException("column name not found:" ~ name); } } -struct BasicColumnSet(D,P) { +struct BasicColumnSet(D, P) { alias Driver = D; alias Policy = P; - alias Result = BasicResult!(Driver,Policy); - alias Column = BasicColumn!(Driver,Policy); + alias Result = BasicResult!(Driver, Policy); + alias Column = BasicColumn!(Driver, Policy); private Result result_; this(Result result) { result_ = result; } - int width() {return result_.data_.columns;} + int width() { + return result_.data_.columns; + } struct Range { private Result result_; int idx; - this(Result result) {result_ = result;} - bool empty() {return idx == result_.data_.columns;} - auto front() {return Column(result_, idx);} - void popFront() {++idx;} + this(Result result) { + result_ = result; + } + + bool empty() { + return idx == result_.data_.columns; + } + + auto front() { + return Column(result_, idx); + } + + void popFront() { + ++idx; + } } - auto opSlice() {return Range(result_);} + auto opSlice() { + return Range(result_); + } } - -struct BasicColumn(D,P) { +struct BasicColumn(D, P) { alias Driver = D; alias Policy = P; - alias Result = BasicResult!(Driver,Policy); + alias Result = BasicResult!(Driver, Policy); private Result result_; private size_t idx_; @@ -409,18 +608,22 @@ struct BasicColumn(D,P) { idx_ = idx; } - auto idx() {return idx_;} - auto name() {return "abc";} + auto idx() { + return idx_; + } -} + auto name() { + return result_.data_.name(idx_); + } +} -struct BasicRowSet(D,P) { +struct BasicRowSet(D, P) { alias Driver = D; alias Policy = P; - alias Result = BasicResult!(Driver,Policy); - alias Row = BasicRow!(Driver,Policy); - alias ColumnSet = BasicColumnSet!(Driver,Policy); + alias Result = BasicResult!(Driver, Policy); + alias Row = BasicRow!(Driver, Policy); + alias ColumnSet = BasicColumnSet!(Driver, Policy); void rowSetTag(); @@ -430,21 +633,25 @@ struct BasicRowSet(D,P) { result_ = result; } - int width() {return result_.data_.columns;} + int width() { + return result_.data_.columns; + } // length will be for the number of rows (if defined) int length() { throw new Exception("not a completed/detached rowSet"); } - auto into(A...) (ref A args) { - if (!result_.rowsFetched()) throw new DatabaseException("no data"); + auto into(A...)(ref A args) { + if (!result_.rowsFetched()) + throw new DatabaseException("no data"); auto row = front(); - foreach(i, ref a; args) { + foreach (i, ref a; args) { alias T = A[i]; static if (is(T == string)) { a = row[i].as!T.dup; - } else { + } + else { a = row[i].as!T; } } @@ -456,43 +663,53 @@ struct BasicRowSet(D,P) { return this; } - // into for output range: experimental //if (isOutputRange!(R,E)) - auto into(R) (R range) - if (hasMember!(R, "put")) { - // Row should have range - // needs lots of work - auto row = Row(this); - for(int i=0; i != width; i++) { - auto f = row[i]; - switch (f.type) { - case ValueType.Int: put(range, f.as!int); break; - case ValueType.String: put(range, f.as!string); break; - //case ValueType.Date: put(range, f.as!Date); break; - default: throw new DatabaseException("switch error"); - } + auto into(R)(R range) if (hasMember!(R, "put")) { + // Row should have range + // needs lots of work + auto row = Row(this); + for (int i = 0; i != width; i++) { + auto f = row[i]; + switch (f.type) { + case ValueType.Int: + put(range, f.as!int); + break; + case ValueType.String: + put(range, f.as!string); + break; + //case ValueType.Date: put(range, f.as!Date); break; + default: + throw new DatabaseException("switch error"); } } + } auto columns() { return ColumnSet(result_); } + bool empty() { + return result_.rowsFetched_ == 0; + } + + auto front() { + return Row(this); + } - bool empty() {return result_.rowsFetched_ == 0;} - auto front() {return Row(this);} - void popFront() {result_.next();} + void popFront() { + result_.next(); + } } -struct BasicRow(D,P) { +struct BasicRow(D, P) { alias Driver = D; alias Policy = P; - alias Result = BasicResult!(Driver,Policy); - alias RowSet = BasicRowSet!(Driver,Policy); - alias Cell = BasicCell!(Driver,Policy); - alias Value = BasicValue!(Driver,Policy); - alias Column = BasicColumn!(Driver,Policy); + alias Result = BasicResult!(Driver, Policy); + alias RowSet = BasicRowSet!(Driver, Policy); + alias Cell = BasicCell!(Driver, Policy); + alias Value = BasicValue!(Driver, Policy); + alias Column = BasicColumn!(Driver, Policy); private RowSet rows_; @@ -500,14 +717,18 @@ struct BasicRow(D,P) { rows_ = rows; } - int width() {return rows_.result_.data_.columns;} + int width() { + return rows_.result_.data_.columns; + } - auto into(A...) (ref A args) { + auto into(A...)(ref A args) { rows_.into(args); return this; } - auto opIndex(Column column) {return opIndex(column.idx);} + auto opIndex(Column column) { + return opIndex(column.idx); + } // experimental auto opDispatch(string s)() { @@ -518,178 +739,451 @@ struct BasicRow(D,P) { auto result = &rows_.result_; // needs work // sending a ptr to cell instead of reference (dangerous) - auto cell = Cell(result, &result.data_.bind[idx], idx); - return Value(result, cell); + return Value(Cell(result, &result.data_.bind[idx], idx)); } } - -struct BasicValue(D,P) { +struct BasicValue(D, P) { alias Driver = D; alias Policy = P; //alias Result = Driver.Result; - alias Result = BasicResult!(Driver,Policy); - alias Cell = BasicCell!(Driver,Policy); + alias Result = BasicResult!(Driver, Policy); + alias Cell = BasicCell!(Driver, Policy); alias Bind = Driver.Bind; - private Result* result_; - private Bind* bind_; private Cell cell_; - alias Converter = .Converter!(Driver,Policy); + private Variant data_; + alias Converter = .Converter!(Driver, Policy); - this(Result* result, Cell cell) { - result_ = result; - //bind_ = bind; + this(Cell cell) { cell_ = cell; + data_ = resultPtr.getValue(&cell_); } private auto resultPtr() { - return &result_.result(); // last parens matter here (something about delegate) + auto r = cell_.result_; + return &(r.result()); // last parens matter here (something about delegate) + } + + Cell cell(){ + return cell_; } - auto type() {return cell_.bind.type;} - - auto as(T:int)() {return Converter.convert!T(resultPtr, cell_);} - auto as(T:string)() {return Converter.convert!T(resultPtr, cell_);} - auto as(T:Date)() {return Converter.convert!T(resultPtr, cell_);} - auto as(T:Nullable!T)() {return Nullable!T(as!T);} - - // should be nothrow? - auto as(T:Variant)() {return Converter.convert!T(resultPtr, cell_);} - - bool isNull() {return false;} //fix - - string name() {return resultPtr.name(cell_.idx_);} + Variant value() { + return data_; + } - auto get(T)() {return Nullable!T(as!T);} + ubyte[] rawData() { + return resultPtr.rawData(&cell_); + } - // experimental - auto option(T)() {return Option!T(as!T);} + auto type() { + return cell_.bind.type; + } - // not sure if this does anything - const(char)[] chars() {return as!string;} + auto as(T)() { + return Converter.convert!T(cell_,this); + } - string toString() {return as!string;} + auto as(T : Variant)() { + return data_; + } -} + bool isNull() { + return resultPtr.isNull(&cell_); + } + string name() { + return resultPtr.name(cell_.idx_); + } -// extra stuff + auto chars(){ + return as!string(); + } + auto get(T)() { + try{ + return Nullable!T(as!T); + }catch{ + return Nullable!T.init; + } + } -struct EfficientValue(T) { - alias Driver = T.Driver; - alias Bind = Driver.Bind; - private Bind* bind_; - alias Converter = .Converter!Driver; + auto dbType(){ + return resultPtr.type(cast(int)cell_.idx_); + } - this(Bind* bind) {bind_ = bind;} + // not sure if this does anything + //const(char)[] chars() {return as!string;} - auto as(T:int)() {return Converter.convertDirect!T(bind_);} - auto as(T:string)() {return Converter.convertDirect!T(bind_);} - auto as(T:Date)() {return Converter.convertDirect!T(bind_);} - auto as(T:Variant)() {return Converter.convertDirect!T(bind_);} + string toString() { + return data_.toString(); + } } - -struct BasicCell(D,P) { +struct BasicCell(D, P) { alias Driver = D; alias Policy = P; - alias Result = BasicResult!(Driver,Policy); + alias Result = BasicResult!(Driver, Policy); alias Bind = Driver.Bind; + alias Value = BasicValue!(Driver, Policy); private Bind* bind_; private int rowIdx_; private size_t idx_; + private Result* result_; - this(Result *r, Bind *b, size_t idx) { + this(Result* r, Bind* b, size_t idx) { bind_ = b; + result_ = r; rowIdx_ = r.rowIdx_; idx_ = idx; } - auto bind() {return bind_;} - auto rowIdx() {return rowIdx_;} -} - -struct Converter(D,P) { - alias Driver = D; - alias Policy = P; - alias Result = Driver.Result; - alias Bind = Driver.Bind; - alias Cell = BasicCell!(Driver,Policy); - - static Y convert(Y)(Result *r, ref Cell cell) { - ValueType x = cell.bind.type, y = TypeInfo!Y.type; - if (x == y) return r.get!Y(&cell); // temporary - auto e = lookup(x,y); - if (!e) conversionError(x,y); - Y value; - e.convert(r, &cell, &value); - return value; - } - - static Y convert(Y:Variant)(Result *r, ref Cell cell) { - //return Y(123); - ValueType x = cell.bind.type, y = ValueType.Variant; - auto e = lookup(x,y); - if (!e) conversionError(x,y); - Y value; - e.convert(r, &cell, &value); - return value; - } - - static Y convertDirect(Y)(Result *r, ref Cell cell) { - assert(b.type == TypeInfo!Y.type); - return r.get!Y(&cell); - } - - private: - - struct Elem { - ValueType from,to; - void function(Result*,void*,void*) convert; - } - - // only cross converters, todo: all converters - static Elem[6] converters = [ - {from: ValueType.Int, to: ValueType.String, &generate!(int,string).convert}, - {from: ValueType.String, to: ValueType.Int, &generate!(string,int).convert}, - {from: ValueType.Date, to: ValueType.String, &generate!(Date,string).convert}, - // variants - {from: ValueType.Int, to: ValueType.Variant, &generate!(int,Variant).convert}, - {from: ValueType.String, to: ValueType.Variant, &generate!(string,Variant).convert}, - {from: ValueType.Date, to: ValueType.Variant, &generate!(Date,Variant).convert}, - ]; - - static Elem* lookup(ValueType x, ValueType y) { - // rework into efficient array lookup - foreach(ref i; converters) { - if (i.from == x && i.to == y) return &i; - } - return null; + private auto resultPtr() { + return &result_.result(); // last parens matter here (something about delegate) } - struct generate(X,Y) { - static void convert(Result *r, void *x_, void *y_) { - import std.conv; - Cell* cell = cast(Cell*) x_; - *cast(Y*) y_ = to!Y(r.get!X(cell)); - } + auto value() { + return Value(this); } - struct generate(X,Y:Variant) { - static void convert(Result *r, void *x_, void *y_) { - Cell* cell = cast(Cell*) x_; - *cast(Y*) y_ = r.get!X(cell); - } + auto bind() { + return bind_; } - static void conversionError(ValueType x, ValueType y) { - import std.conv; - string msg; - msg ~= "unsupported conversion from: " ~ to!string(x) ~ " to " ~ to!string(y); - throw new DatabaseException(msg); + auto rowIdx() { + return rowIdx_; + } + + auto columnIdx(){ + return idx_; } - } +/* +struct EfficientValue(T) { + alias Driver = T.Driver; + alias Bind = Driver.Bind; + private Bind* bind_; + alias Converter = .Converter!Driver; + + private Variant data_; + + this(Bind* bind) {bind_ = bind;} + + auto as(T:int)() {return Converter.convertDirect!T(bind_);} + auto as(T:string)() {return Converter.convertDirect!T(bind_);} + auto as(T:Date)() {return Converter.convertDirect!T(bind_);} + auto as(T:Variant)() {return Converter.convertDirect!T(bind_);} + +}*/ + +import std.traits; +//Converter now is not used, but if del it ,it will build error. +struct Converter(D,P) { + alias Driver = D; + alias Policy = P; + //alias Result = Driver.Result; + alias Bind = Driver.Bind; + alias Cell = BasicCell!(Driver,Policy); + alias Value = Cell.Value; + + static Y convert(Y)(ref Cell cell,ref Value value) { + if(value.isNull()) throw new DatabaseException("The Value is Null!"); + ValueType x = cell.bind.type, y = TypeInfo!Y.type; + if(x == ValueType.UNKnown) throw new DatabaseException("The Value type is Unknown! please use rawData() get Data."); + if (x == y) return value.data_.get!Y(); + auto e = lookup(x,y); + if (!e) conversionError(x,y); + Y val; + e.convert(&(value.data_),&cell, &val); + return val; + } + + private: + + struct Elem { + ValueType from,to; + void function(Variant *,void *,void*) convert; + } + + // only cross converters, todo: all converters + static Elem[74] converters = [ + //to string + {from: ValueType.Char, to: ValueType.String, &generate!(char,string).convert}, + {from: ValueType.Short, to: ValueType.String, &generate!(short,string).convert}, + {from: ValueType.Int, to: ValueType.String, &generate!(int,string).convert}, + {from: ValueType.Long, to: ValueType.String, &generate!(int,string).convert}, + {from: ValueType.Float, to: ValueType.String, &generate!(float,string).convert}, + {from: ValueType.Double, to: ValueType.String, &generate!(double,string).convert}, + {from: ValueType.Raw, to: ValueType.String, &generate!(ubyte[],string).convert}, + {from: ValueType.Time, to: ValueType.String, &generate!(Time,string).convert}, + {from: ValueType.Date, to: ValueType.String, &generate!(Date,string).convert}, + {from: ValueType.DateTime, to: ValueType.String, &generate!(DateTime,string).convert}, + // string to other + {from: ValueType.String, to: ValueType.Char, &generate!(string,char).convert}, + {from: ValueType.String, to: ValueType.Short, &generate!(string,short).convert}, + {from: ValueType.String, to: ValueType.Int, &generate!(string,int).convert}, + {from: ValueType.String, to: ValueType.Long, &generate!(string,long).convert}, + {from: ValueType.String, to: ValueType.Float, &generate!(string,float).convert}, + {from: ValueType.String, to: ValueType.Double, &generate!(string,double).convert}, + {from: ValueType.String, to: ValueType.Raw, &generate!(string,ubyte[]).convert}, + {from: ValueType.String, to: ValueType.Time, &generate!(string,Time).convert}, + {from: ValueType.String, to: ValueType.Date, &generate!(string,Date).convert}, + {from: ValueType.String, to: ValueType.DateTime, &generate!(string,DateTime).convert}, + // char to other + {from: ValueType.Char, to: ValueType.Short, &generate!(char,short).convert}, + {from: ValueType.Char, to: ValueType.Int, &generate!(char,int).convert}, + {from: ValueType.Char, to: ValueType.Long, &generate!(char,long).convert}, + {from: ValueType.Char, to: ValueType.Float, &generate!(char,float).convert}, + {from: ValueType.Char, to: ValueType.Double, &generate!(char,double).convert}, + {from: ValueType.Char, to: ValueType.Raw, &generate!(char,ubyte[]).convert}, + //short to other + {from: ValueType.Short, to: ValueType.Char, &generate!(short,char).convert}, + {from: ValueType.Short, to: ValueType.Int, &generate!(short,int).convert}, + {from: ValueType.Short, to: ValueType.Long, &generate!(short,long).convert}, + {from: ValueType.Short, to: ValueType.Float, &generate!(short,float).convert}, + {from: ValueType.Short, to: ValueType.Double, &generate!(short,double).convert}, + {from: ValueType.Short, to: ValueType.Raw, &generate!(short,ubyte[]).convert}, + // int to other + {from: ValueType.Int, to: ValueType.Char, &generate!(int,char).convert}, + {from: ValueType.Int, to: ValueType.Short, &generate!(int,short).convert}, + {from: ValueType.Int, to: ValueType.Long, &generate!(int,long).convert}, + {from: ValueType.Int, to: ValueType.Float, &generate!(int,float).convert}, + {from: ValueType.Int, to: ValueType.Double, &generate!(int,double).convert}, + {from: ValueType.Int, to: ValueType.Raw, &generate!(int,ubyte[]).convert}, + // long to Other + {from: ValueType.Long, to: ValueType.Char, &generate!(long,char).convert}, + {from: ValueType.Long, to: ValueType.Short, &generate!(long,short).convert}, + {from: ValueType.Long, to: ValueType.Int, &generate!(long,int).convert}, + {from: ValueType.Long, to: ValueType.Float, &generate!(long,float).convert}, + {from: ValueType.Long, to: ValueType.Double, &generate!(long,double).convert}, + {from: ValueType.Long, to: ValueType.Raw, &generate!(long,ubyte[]).convert}, + {from: ValueType.Long, to: ValueType.DateTime, &generate!(int,DateTime).convert}, + //double to Other + {from: ValueType.Double, to: ValueType.Char, &generate!(double,char).convert}, + {from: ValueType.Double, to: ValueType.Short, &generate!(double,short).convert}, + {from: ValueType.Double, to: ValueType.Int, &generate!(double,int).convert}, + {from: ValueType.Double, to: ValueType.Float, &generate!(double,float).convert}, + {from: ValueType.Double, to: ValueType.Long, &generate!(double,long).convert}, + {from: ValueType.Double, to: ValueType.Raw, &generate!(double,ubyte[]).convert}, + //float to Other + {from: ValueType.Float, to: ValueType.Char, &generate!(float,char).convert}, + {from: ValueType.Float, to: ValueType.Short, &generate!(float,short).convert}, + {from: ValueType.Float, to: ValueType.Int, &generate!(float,int).convert}, + {from: ValueType.Float, to: ValueType.Double, &generate!(float,double).convert}, + {from: ValueType.Float, to: ValueType.Long, &generate!(float,long).convert}, + {from: ValueType.Float, to: ValueType.Raw, &generate!(float,ubyte[]).convert}, + //raw to other + {from: ValueType.Raw, to: ValueType.Char, &generate!(ubyte[],char).convert}, + {from: ValueType.Raw, to: ValueType.Short, &generate!(ubyte[],short).convert}, + {from: ValueType.Raw, to: ValueType.Int, &generate!(ubyte[],int).convert}, + {from: ValueType.Raw, to: ValueType.Long, &generate!(ubyte[],long).convert}, + {from: ValueType.Raw, to: ValueType.Float, &generate!(ubyte[],float).convert}, + {from: ValueType.Raw, to: ValueType.Double, &generate!(ubyte[],double).convert}, + {from: ValueType.Raw, to: ValueType.Time, &generate!(ubyte[],Time).convert}, + {from: ValueType.Raw, to: ValueType.Date, &generate!(ubyte[],Date).convert}, + {from: ValueType.Raw, to: ValueType.DateTime, &generate!(ubyte[],DateTime).convert}, + //date to datetime + {from: ValueType.Date, to: ValueType.DateTime, &generate!(Date,DateTime).convert}, + {from: ValueType.Date, to: ValueType.Raw, &generate!(Date,ubyte[]).convert}, + {from: ValueType.Time, to: ValueType.Raw, &generate!(Time,ubyte[]).convert}, + {from: ValueType.Time, to: ValueType.DateTime, &generate!(Time,DateTime).convert}, + // datetime to other + {from: ValueType.DateTime, to: ValueType.Date, &generate!(DateTime,Date).convert}, + {from: ValueType.DateTime, to: ValueType.Time, &generate!(DateTime,Time).convert}, + {from: ValueType.DateTime, to: ValueType.Long, &generate!(DateTime,long).convert}, + {from: ValueType.DateTime, to: ValueType.Raw, &generate!(DateTime,ubyte[]).convert} + ]; + + static Elem* lookup(ValueType x, ValueType y) { + // rework into efficient array lookup + foreach(ref i; converters) { + if (i.from == x && i.to == y) return &i; + } + return null; + } + + struct generate(X,Y) { + static void convert(Variant * v_,void *x_, void *y_) { + import std.conv; + Cell* cell = cast(Cell*) x_;// why this must be have? + *cast(Y*) y_ = to!Y(v_.get!X()); + } + } + + struct generate(X : DateTime,Y : Time) { + static void convert(Variant * v_,void *x_, void *y_) { + Cell* cell = cast(Cell*) x_;// why this must be have? + DateTime dt = v_.get!X(); + *cast(Y*) y_ = Time(dt.hour, dt.minute, dt.second); + } + } + + struct generate(X : DateTime,Y : long) { + static void convert(Variant * v_,void *x_, void *y_) { + Cell* cell = cast(Cell*) x_;// why this must be have? + DateTime dt = v_.get!X(); + *cast(Y*) y_ = SysTime(dt).toUnixTime() ; + } + } + + struct generate(X : DateTime,Y : Date) { + static void convert(Variant * v_,void *x_, void *y_) { + Cell* cell = cast(Cell*) x_;// why this must be have? + DateTime dt = v_.get!X(); + *cast(Y*) y_ = dt.date; + } + } + + struct generate(X : string,Y : Date) { + static void convert(Variant * v_,void *x_, void *y_) { + Cell* cell = cast(Cell*) x_;// why this must be have? + Date dt; + dt.fromISOExtString(v_.get!X()); + *cast(Y*) y_ = dt; + } + } + + struct generate(X : string,Y : Time) { + static void convert(Variant * v_,void *x_, void *y_) { + Cell* cell = cast(Cell*) x_;// why this must be have? + *cast(Y*) y_ = Time.formString(v_.get!X()); + } + } + + struct generate(X,Y : DateTime) + { + static void convert(Variant * v_,void *x_, void *y_) { + Cell* cell = cast(Cell*) x_;// why this must be have? + static if(is (X == ubyte[])) + { + ubyte[] dt = v_.get!X(); + if(dt.length < Y.sizeof) + throw new Exception("Raw Data Length is to smail!"); + auto data = dt[0..Y.sizeof]; + auto tm = cast(Y *) data.ptr; + *cast(Y*) y_ = *tm; + } + else static if(is(X == string)) + { + DateTime dt; + dt.fromISOExtString(v_.get!X()); + *cast(Y*) y_ = dt; + } + else static if (is(X == long)) + { + *cast(Y*) y_ = cast(DateTime)SysTime.fromUnixTime(v_.get!X()); + } + else + { + string msg = "unsupported conversion from: " ~ X.stringof ~ " to " ~ Y.stringof; + throw new DatabaseException(msg); + } + } + } + + struct generate(X : char, Y) + { + static void convert(Variant * v_,void *x_, void *y_) { + Cell* cell = cast(Cell*) x_;// why this must be have? + static if(is (Y == ubyte[])) + { + char a = v_.get!X(); + ubyte[] dt = new ubyte[1]; + dt[0] = cast(ubyte)a; + *cast(Y*) y_ = dt; + } + else static if(is(Y == string)) + { + string a; + a ~= v_.get!X(); + *cast(Y*) y_ = a; + } + else static if(isNumeric!Y) + { + *cast(Y*) y_ = v_.get!X(); + } + else + { + string msg = "unsupported conversion from: " ~ X.stringof ~ " to " ~ Y.stringof; + throw new DatabaseException(msg); + } + } + } + + struct generate(X , Y : char) + { + static void convert(Variant * v_,void *x_, void *y_) { + Cell* cell = cast(Cell*) x_;// why this must be have? + static if(is (X == ubyte[])) + { + ubyte[] dt = v_.get!X(); + *cast(Y*) y_ = cast(char)dt[0]; + } + else static if(is(X == string)) + { + string a = v_.get!X(); + *cast(Y*) y_ = a[0]; + } + else static if(isNumeric!Y) + { + *cast(Y*) y_ = cast(char)v_.get!X(); + } + else + { + string msg = "unsupported conversion from: " ~ X.stringof ~ " to " ~ Y.stringof; + throw new DatabaseException(msg); + } + } + } + + struct generate(X : ubyte[], Y) if(!(is(Y == char) || is(Y == DateTime))) + { + static void convert(Variant * v_,void *x_, void *y_) { + Cell* cell = cast(Cell*) x_;// why this must be have? + ubyte[] a = v_.get!X(); + static if(is(X == string)) + { + *cast(Y*) y_ = cast(string)a; + } + else + { + if(a.length < Y.sizeof) + throw new Exception("Raw Data Length is to smail!"); + auto data = a[0..Y.sizeof]; + auto tm = cast(Y *) data.ptr; + *cast(Y*) y_ = *tm; + } + } + } + + struct generate(X , Y : ubyte[]) if(!is(X == char)) + { + static void convert(Variant * v_,void *x_, void *y_) { + Cell* cell = cast(Cell*) x_;// why this must be have? + static if(is(X == string)) + { + string a = v_.get!X(); + *cast(Y*) y_ = cast(ubyte[])a; + } + else + { + ubyte[] data = new ubyte[X.sizeof]; + auto dt = v_.get!X(); + ubyte * ptr = cast(ubyte *) &dt; + data[] = ptr[0..X.sizeof]; + *cast(Y*) y_ = data; + } + } + } + + + + static void conversionError(ValueType x, ValueType y) { + import std.conv; + string msg; + msg ~= "unsupported conversion from: " ~ to!string(x) ~ " to " ~ to!string(y); + throw new DatabaseException(msg); + } +} \ No newline at end of file diff --git a/src/std/database/mysql/database.d b/src/std/database/mysql/database.d index 7bccf4a..295832c 100644 --- a/src/std/database/mysql/database.d +++ b/src/std/database/mysql/database.d @@ -7,12 +7,14 @@ import std.stdio; import std.database.common; import std.database.source; -version(Windows) { +version (Windows) { pragma(lib, "libmysql"); -} else { +} +else { version (webscalesql) { pragma(lib, "webscalesql"); - } else { + } + else { pragma(lib, "mysqlclient"); } } @@ -27,39 +29,37 @@ import std.string; // alias Database(T) = std.database.impl.Database!(T,DatabaseImpl!T); -alias Database(T) = BasicDatabase!(Driver!T.Sync,T); - -alias AsyncDatabase(T) = BasicDatabase!(Driver!T.Async,T); +alias Database(T) = BasicDatabase!(Driver!T.Sync, T); +alias AsyncDatabase(T) = BasicDatabase!(Driver!T.Async, T); struct DefaultPolicy { alias Allocator = MyMallocator; } -auto createDatabase()(string uri="") { - return Database!DefaultPolicy(uri); +auto createDatabase()(string uri = "") { + return Database!DefaultPolicy(uri); } -auto createDatabase(T)(string uri="") { - return Database!T(uri); +auto createDatabase(T)(string uri = "") { + return Database!T(uri); } private static bool isError()(int ret) { - return - !(ret == 0 || - ret == MYSQL_NO_DATA || - ret == MYSQL_DATA_TRUNCATED); + return !(ret == 0 || ret == MYSQL_NO_DATA || ret == MYSQL_DATA_TRUNCATED); } private static T* check(T)(string msg, T* ptr) { info(msg, ":", ptr); - if (!ptr) raiseError(msg); + if (!ptr) + raiseError(msg); return ptr; } private static int check()(string msg, MYSQL_STMT* stmt, int ret) { info(msg, ":", ret); - if (isError(ret)) raiseError(msg,stmt,ret); + if (isError(ret)) + raiseError(msg, stmt, ret); return ret; } @@ -72,8 +72,12 @@ private static void raiseError()(string msg, int ret) { } private static void raiseError()(string msg, MYSQL_STMT* stmt, int ret) { - import core.stdc.string: strlen; + import core.stdc.string : strlen; + import std.string; + const(char*) err = mysql_stmt_error(stmt); + msg ~= " erro: "; + msg ~= fromStringz(err); throw new DatabaseException("mysql error: " ~ msg); } @@ -82,7 +86,7 @@ private struct Driver(Policy) { struct Describe { int index; immutable(char)[] name; - MYSQL_FIELD *field; + MYSQL_FIELD* field; } struct Bind { @@ -95,10 +99,9 @@ private struct Driver(Policy) { my_bool error; } - struct Sync { alias Allocator = Policy.Allocator; - alias Cell = BasicCell!(Sync,Policy); + alias Cell = BasicCell!(Sync, Policy); alias const(ubyte)* cstring; alias Describe = Driver!Policy.Describe; @@ -107,9 +110,7 @@ private struct Driver(Policy) { struct Database { alias queryVariableType = QueryVariableType.QuestionMark; - static const FeatureArray features = [ - Feature.InputBinding, - Feature.DateBinding, + static const FeatureArray features = [Feature.InputBinding, Feature.DateBinding, //Feature.ConnectionPool, ]; @@ -120,32 +121,31 @@ private struct Driver(Policy) { info("mysql client info: ", to!string(mysql_get_client_info())); } - //~this() {log("~Database");} + ~this() { + // log("~Database"); + } } struct Connection { - Database *db; - MYSQL *mysql; + Database* db; + MYSQL* mysql; - this(Database *db_, Source source) { + this(Database* db_, Source source) { db = db_; mysql = check("mysql_init", mysql_init(null)); - check("mysql_real_connect", mysql_real_connect( - mysql, - cast(cstring) toStringz(source.server), - cast(cstring) toStringz(source.username), - cast(cstring) toStringz(source.password), - cast(cstring) toStringz(source.database), - 0, - null, - 0)); + check("mysql_real_connect", mysql_real_connect(mysql, + cast(cstring) toStringz(source.server), + cast(cstring) toStringz(source.username), + cast(cstring) toStringz(source.password), + cast(cstring) toStringz(source.database), 0, null, 0)); } ~this() { - //log("~Statement"); - if (mysql) mysql_close(mysql); + // log("~Statement"); + if (mysql) + mysql_close(mysql); mysql = null; } @@ -155,12 +155,12 @@ private struct Driver(Policy) { // make this efficient mysqlBind.clear(); mysqlBind.reserve(bind.length); - for(int i=0; i!=bind.length; ++i) { + for (int i = 0; i != bind.length; ++i) { mysqlBind ~= MYSQL_BIND(); - //import core.stdc.string: memset; - //memset(mb, 0, MYSQL_BIND.sizeof); // might not be needed: D struct auto b = &bind[i]; auto mb = &mysqlBind[i]; + //import core.stdc.string: memset; + //memset(mb, 0, MYSQL_BIND.sizeof); // might not be needed: D struct mb.buffer_type = b.mysql_type; mb.buffer = b.data.ptr; mb.buffer_length = b.allocSize; @@ -170,18 +170,17 @@ private struct Driver(Policy) { } } - struct Statement { - Connection *con; + Connection* con; string sql; - Allocator *allocator; - MYSQL_STMT *stmt; + Allocator* allocator; + MYSQL_STMT* stmt; uint binds; Array!Bind inputBind; Array!MYSQL_BIND mysqlBind; bool bindInit; - this(Connection *con_, string sql_) { + this(Connection* con_, string sql_) { con = con_; sql = sql_; allocator = &con.db.allocator; @@ -189,20 +188,26 @@ private struct Driver(Policy) { } ~this() { - foreach(b; inputBind) allocator.deallocate(b.data); - if (stmt) mysql_stmt_close(stmt); + // log("~Statement"); + foreach (b; inputBind) + allocator.deallocate(b.data); + if (stmt) + mysql_stmt_close(stmt); // stmt = null? needed } // hoist? - this(this) { assert(false); } - void opAssign(Statement rhs) { assert(false); } + this(this) { + assert(false); + } + + void opAssign(Statement rhs) { + assert(false); + } void prepare() { - check("mysql_stmt_prepare", stmt, mysql_stmt_prepare( - stmt, - cast(char*) sql.ptr, - sql.length)); + check("mysql_stmt_prepare", stmt, mysql_stmt_prepare(stmt, + cast(char*) sql.ptr, sql.length)); binds = cast(uint) mysql_stmt_param_count(stmt); } @@ -213,28 +218,30 @@ private struct Driver(Policy) { bindSetup(inputBind, mysqlBind); - check("mysql_stmt_bind_param", - stmt, - mysql_stmt_bind_param(stmt, &mysqlBind[0])); + check("mysql_stmt_bind_param", stmt, + mysql_stmt_bind_param(stmt, &mysqlBind[0])); } info("execute: ", sql); check("mysql_stmt_execute", stmt, mysql_stmt_execute(stmt)); } - void query(X...) (X args) { + void query(X...)(X args) { bindAll(args); query(); } - bool hasRows() {return true;} + bool hasRows() { + return true; + } void reset() { } - private void bindAll(T...) (T args) { + private void bindAll(T...)(T args) { int col; - foreach (arg; args) bind(++col, arg); + foreach (arg; args) + bind(++col, arg); } void bind(int n, int value) { @@ -246,11 +253,12 @@ private struct Driver(Policy) { } - void bind(int n, const char[] value){ - import core.stdc.string: strncpy; + void bind(int n, const char[] value) { + import core.stdc.string : strncpy; + info("input bind: n: ", n, ", value: ", value); - auto b = bindAlloc(n, MYSQL_TYPE_STRING, 100+1); // fix + auto b = bindAlloc(n, MYSQL_TYPE_STRING, 100 + 1); // fix b.is_null = 0; b.error = 0; @@ -272,12 +280,16 @@ private struct Driver(Policy) { } Bind* bindAlloc(int n, int mysql_type, int allocSize) { - if (n==0) throw new DatabaseException("zero index"); - auto idx = n-1; - if (idx > inputBind.length) throw new DatabaseException("bind range error"); - if (idx == inputBind.length) inputBind ~= Bind(); + if (n == 0) + throw new DatabaseException("zero index"); + auto idx = n - 1; + if (idx > inputBind.length) + throw new DatabaseException("bind range error"); + if (idx == inputBind.length) + inputBind ~= Bind(); auto b = &inputBind[idx]; - if (allocSize <= b.data.length) return b; // fix + if (allocSize <= b.data.length) + return b; // fix b.mysql_type = mysql_type; b.allocSize = allocSize; b.data = allocator.allocate(b.allocSize); @@ -287,23 +299,24 @@ private struct Driver(Policy) { } struct Result { - Statement *stmt; - Allocator *allocator; + Statement* stmt; + Allocator* allocator; uint columns; Array!Describe describe; Array!Bind bind; Array!MYSQL_BIND mysqlBind; - MYSQL_RES *result_metadata; + MYSQL_RES* result_metadata; int status; - static const maxData = 256; + static const maxData = 2048; this(Statement* stmt_, int rowArraySize_) { stmt = stmt_; allocator = stmt.allocator; result_metadata = mysql_stmt_result_metadata(stmt.stmt); - if (!result_metadata) return; + if (!result_metadata) + return; //columns = mysql_num_fields(result_metadata); build_describe(); @@ -311,19 +324,22 @@ private struct Driver(Policy) { } ~this() { - //log("~Result"); - foreach(b; bind) allocator.deallocate(b.data); - if (result_metadata) mysql_free_result(result_metadata); + // log("~Result"); + foreach (b; bind) + allocator.deallocate(b.data); + if (result_metadata) + mysql_free_result(result_metadata); + // log("~Result"); } void build_describe() { - import core.stdc.string: strlen; + import core.stdc.string : strlen; columns = cast(uint) mysql_stmt_field_count(stmt.stmt); describe.reserve(columns); - for(int i = 0; i != columns; ++i) { + for (int i = 0; i != columns; ++i) { describe ~= Describe(); auto d = &describe.back(); @@ -331,25 +347,76 @@ private struct Driver(Policy) { d.field = mysql_fetch_field(result_metadata); auto p = cast(immutable(char)*) d.field.name; - d.name = p[0 .. strlen(p)]; + d.name = p[0 .. d.field.name_length];//strlen(p)]; info("describe: name: ", d.name, ", mysql type: ", d.field.type); } } void build_bind() { - import core.stdc.string: memset; + import core.stdc.string : memset; import core.memory : GC; bind.reserve(columns); - for(int i = 0; i != columns; ++i) { + + for (int i = 0; i != columns; ++i) { auto d = &describe[i]; bind ~= Bind(); auto b = &bind.back(); + b.mysql_type = d.field.type; + b.allocSize = cast(uint)(d.field.length + 1); + + switch (d.field.type) { + case MYSQL_TYPE_TINY: + b.type = ValueType.Char; + break; + case MYSQL_TYPE_SHORT: + b.type = ValueType.Short; + break; + case MYSQL_TYPE_LONG: + b.type = ValueType.Int; + break; + case MYSQL_TYPE_LONGLONG: + b.type = ValueType.Long; + break; + case MYSQL_TYPE_FLOAT: + b.type = ValueType.Float; + break; + case MYSQL_TYPE_DOUBLE: + b.type = ValueType.Double; + break; + + case MYSQL_TYPE_DATE: + b.type = ValueType.Date; + b.allocSize += 32; + break; + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: + b.type = ValueType.Time; + b.allocSize += 32; + break; + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP2: + case MYSQL_TYPE_TIMESTAMP: + b.type = ValueType.DateTime; + b.allocSize += 32; + break; + case MYSQL_TYPE_BLOB: + b.type = ValueType.Raw; + b.allocSize += 512; + break; + default: + b.mysql_type = MYSQL_TYPE_STRING; + b.type = ValueType.String; + b.allocSize += 256; + break; + } + trace("d.field.length : ", d.field.length, " type is : ", b.type); // let in ints for now - if (d.field.type == MYSQL_TYPE_LONG) { + /* if (d.field.type == MYSQL_TYPE_LONG) { b.mysql_type = d.field.type; b.type = ValueType.Int; } else if (d.field.type == MYSQL_TYPE_DATE) { @@ -358,9 +425,7 @@ private struct Driver(Policy) { } else { b.mysql_type = MYSQL_TYPE_STRING; b.type = ValueType.String; - } - - b.allocSize = cast(uint)(d.field.length + 1); + }*/ b.data = allocator.allocate(b.allocSize); } @@ -369,16 +434,20 @@ private struct Driver(Policy) { mysql_stmt_bind_result(stmt.stmt, &mysqlBind.front()); } - bool hasResult() {return result_metadata != null;} + bool hasResult() { + return result_metadata != null; + } int fetch() { status = check("mysql_stmt_fetch", stmt.stmt, mysql_stmt_fetch(stmt.stmt)); if (!status) { return 1; - } else if (status == MYSQL_NO_DATA) { + } + else if (status == MYSQL_NO_DATA) { //rows_ = row_count_; return 0; - } else if (status == MYSQL_DATA_TRUNCATED) { + } + else if (status == MYSQL_DATA_TRUNCATED) { raiseError("mysql_stmt_fetch: truncation", status); } @@ -388,46 +457,113 @@ private struct Driver(Policy) { // value getters + Variant getValue(Cell* cell) { + Variant value; + if (isNull(cell)) + return value; + switch (cell.bind.type) { + case ValueType.Char: + value = (*cast(char*) cell.bind.data.ptr); + break; + case ValueType.Short: + value = (*cast(short*) cell.bind.data.ptr); + break; + case ValueType.Int: + value = (*cast(int*) cell.bind.data.ptr); + break; + case ValueType.Long: + value = (*cast(long*) cell.bind.data.ptr); + break; + case ValueType.Float: + value = (*cast(float*) cell.bind.data.ptr); + break; + case ValueType.Double: + value = (*cast(double*) cell.bind.data.ptr); + break; + case ValueType.String: { + auto ptr = cast(char*) cell.bind.data.ptr; + value = cast(string)(ptr[0 .. cell.bind.length]); + } + break; + case ValueType.Date: { + MYSQL_TIME* t = cast(MYSQL_TIME*) cell.bind.data.ptr; + value = Date(t.year, t.month, t.day); + } + break; + case ValueType.Time: { + MYSQL_TIME* t = cast(MYSQL_TIME*) cell.bind.data.ptr; + value = Time(t.hour, t.minute, t.second); + } + break; + case ValueType.DateTime: { + MYSQL_TIME* t = cast(MYSQL_TIME*) cell.bind.data.ptr; + value = DateTime(t.year, t.month, t.day, t.hour, t.minute, + t.second); + } + break; + case ValueType.Raw:{ + auto ptr = cast(ubyte * ) cell.bind.data.ptr; + value = ptr[0 .. cell.bind.length]; + } + break; + default: + break; + } + return value; + } + + auto type(int col){ + return describe[col].field.type; + } + /* char[] get(X:char[])(Bind *b) { auto ptr = cast(char*) b.data.ptr; return ptr[0..b.length]; } */ - - auto name(size_t idx) { - return describe[idx].name; - } - - auto get(X:string)(Cell* cell) { - //return cast(string) get!(char[])(b); - auto ptr = cast(char*) cell.bind.data.ptr; - return ptr[0..cell.bind.length]; + ubyte[] rawData(Cell* cell) { + log("-----------bind.bufferLength = ", cell.bind.allocSize, + " , dataLength = ", cell.bind.length); + auto ptr = cast(ubyte*) cell.bind.data.ptr; + return ptr[0 .. cell.bind.length]; } - auto get(X:int)(Cell* cell) { - return *cast(int*) cell.bind.data.ptr; + bool isNull(Cell* cell) { + return cell.bind.is_null > 0; } - auto get(X:Date)(Cell* cell) { - //return Date(2016,1,1); // fix - MYSQL_TIME *t = cast(MYSQL_TIME*) cell.bind.data.ptr; - //t.year,t.month,t.day,t.hour,t.minute,t.second - return Date(t.year,t.month,t.day); + auto name(size_t idx) { + return describe[idx].name; } - static void checkType(T)(Bind *b) { + static void checkType(T)(Bind* b) { int x = TypeInfo!T.type(); int y = b.mysql_type; - if (x == y) return; - warning("type pair mismatch: ",x, ":", y); + if (x == y) + return; + warning("type pair mismatch: ", x, ":", y); throw new DatabaseException("type mismatch"); } // refactor as a better 1-n bind mapping - struct TypeInfo(T:int) {static int type() {return MYSQL_TYPE_LONG;}} - struct TypeInfo(T:string) {static int type() {return MYSQL_TYPE_STRING;}} - struct TypeInfo(T:Date) {static int type() {return MYSQL_TYPE_DATE;}} + struct TypeInfo(T : int) { + static int type() { + return MYSQL_TYPE_LONG; + } + } + + struct TypeInfo(T : string) { + static int type() { + return MYSQL_TYPE_STRING; + } + } + + struct TypeInfo(T : Date) { + static int type() { + return MYSQL_TYPE_DATE; + } + } } } @@ -436,14 +572,12 @@ private struct Driver(Policy) { alias Allocator = Policy.Allocator; alias Describe = Driver!Policy.Describe; alias Bind = Driver!Policy.Bind; - alias Cell = BasicCell!(Async,Policy); + alias Cell = BasicCell!(Async, Policy); struct Database { alias queryVariableType = QueryVariableType.QuestionMark; - static const FeatureArray features = [ - Feature.InputBinding, - Feature.DateBinding, + static const FeatureArray features = [Feature.InputBinding, Feature.DateBinding, //Feature.ConnectionPool, ]; @@ -451,45 +585,49 @@ private struct Driver(Policy) { info("mysql client info: ", to!string(mysql_get_client_info())); } - bool bindable() {return true;} - bool dateBinding() {return true;} - bool poolEnable() {return false;} + bool bindable() { + return true; + } + + bool dateBinding() { + return true; + } + + bool poolEnable() { + return false; + } } struct Connection { - Database *db; - MYSQL *mysql; + Database* db; + MYSQL* mysql; - this(Database *db_, Source source) { + this(Database* db_, Source source) { db = db_; mysql = check("mysql_init", mysql_init(null)); - check("mysql_real_connect", mysql_real_connect( - mysql, - cast(cstring) toStringz(source.server), - cast(cstring) toStringz(source.username), - cast(cstring) toStringz(source.password), - cast(cstring) toStringz(source.database), - 0, - null, - 0)); + check("mysql_real_connect", mysql_real_connect(mysql, + cast(cstring) toStringz(source.server), + cast(cstring) toStringz(source.username), + cast(cstring) toStringz(source.password), + cast(cstring) toStringz(source.database), 0, null, 0)); } ~this() { - //log("~Statement"); - if (mysql) mysql_close(mysql); + // log("~Statement"); + if (mysql) + mysql_close(mysql); mysql = null; } } - static void bindSetup(ref Array!Bind bind, ref Array!MYSQL_BIND mysqlBind) { // make this efficient mysqlBind.clear(); mysqlBind.reserve(bind.length); - for(int i=0; i!=bind.length; ++i) { + for (int i = 0; i != bind.length; ++i) { mysqlBind ~= MYSQL_BIND(); //import core.stdc.string: memset; //memset(mb, 0, MYSQL_BIND.sizeof); // might not be needed: D struct @@ -504,40 +642,45 @@ private struct Driver(Policy) { } } - struct Statement { - Connection *con; + Connection* con; string sql; Allocator allocator; - MYSQL_STMT *stmt; + MYSQL_STMT* stmt; uint binds; Array!Bind inputBind; Array!MYSQL_BIND mysqlBind; bool bindInit; - this(Connection *con_, string sql_) { + this(Connection* con_, string sql_) { con = con_; sql = sql_; //allocator = &con.db.allocator; stmt = mysql_stmt_init(con.mysql); - if (!stmt) throw new DatabaseException("stmt error"); + if (!stmt) + throw new DatabaseException("stmt error"); } ~this() { - foreach(b; inputBind) allocator.deallocate(b.data); - if (stmt) mysql_stmt_close(stmt); + foreach (b; inputBind) + allocator.deallocate(b.data); + if (stmt) + mysql_stmt_close(stmt); // stmt = null? needed } // hoist? - this(this) { assert(false); } - void opAssign(Statement rhs) { assert(false); } + this(this) { + assert(false); + } + + void opAssign(Statement rhs) { + assert(false); + } void prepare() { - check("mysql_stmt_prepare", stmt, mysql_stmt_prepare( - stmt, - cast(char*) sql.ptr, - sql.length)); + check("mysql_stmt_prepare", stmt, mysql_stmt_prepare(stmt, + cast(char*) sql.ptr, sql.length)); binds = cast(uint) mysql_stmt_param_count(stmt); } @@ -548,28 +691,30 @@ private struct Driver(Policy) { bindSetup(inputBind, mysqlBind); - check("mysql_stmt_bind_param", - stmt, - mysql_stmt_bind_param(stmt, &mysqlBind[0])); + check("mysql_stmt_bind_param", stmt, + mysql_stmt_bind_param(stmt, &mysqlBind[0])); } info("execute: ", sql); check("mysql_stmt_execute", stmt, mysql_stmt_execute(stmt)); } - void query(X...) (X args) { + void query(X...)(X args) { bindAll(args); query(); } - bool hasRows() {return true;} + bool hasRows() { + return true; + } void reset() { } - private void bindAll(T...) (T args) { + private void bindAll(T...)(T args) { int col; - foreach (arg; args) bind(++col, arg); + foreach (arg; args) + bind(++col, arg); } void bind(int n, int value) { @@ -581,11 +726,12 @@ private struct Driver(Policy) { } - void bind(int n, const char[] value){ - import core.stdc.string: strncpy; + void bind(int n, const char[] value) { + import core.stdc.string : strncpy; + info("input bind: n: ", n, ", value: ", value); - auto b = bindAlloc(n, MYSQL_TYPE_STRING, 100+1); // fix + auto b = bindAlloc(n, MYSQL_TYPE_STRING, 100 + 1); // fix b.is_null = 0; b.error = 0; @@ -607,12 +753,16 @@ private struct Driver(Policy) { } Bind* bindAlloc(int n, int mysql_type, int allocSize) { - if (n==0) throw new DatabaseException("zero index"); - auto idx = n-1; - if (idx > inputBind.length) throw new DatabaseException("bind range error"); - if (idx == inputBind.length) inputBind ~= Bind(); + if (n == 0) + throw new DatabaseException("zero index"); + auto idx = n - 1; + if (idx > inputBind.length) + throw new DatabaseException("bind range error"); + if (idx == inputBind.length) + inputBind ~= Bind(); auto b = &inputBind[idx]; - if (allocSize <= b.data.length) return b; // fix + if (allocSize <= b.data.length) + return b; // fix b.mysql_type = mysql_type; b.allocSize = allocSize; b.data = allocator.allocate(b.allocSize); @@ -622,13 +772,13 @@ private struct Driver(Policy) { } struct Result { - Statement *stmt; + Statement* stmt; Allocator allocator; uint columns; Array!Describe describe; Array!Bind bind; Array!MYSQL_BIND mysqlBind; - MYSQL_RES *result_metadata; + MYSQL_RES* result_metadata; int status; static const maxData = 256; @@ -646,18 +796,20 @@ private struct Driver(Policy) { ~this() { //log("~Result"); - foreach(b; bind) allocator.deallocate(b.data); - if (result_metadata) mysql_free_result(result_metadata); + foreach (b; bind) + allocator.deallocate(b.data); + if (result_metadata) + mysql_free_result(result_metadata); } void build_describe() { - import core.stdc.string: strlen; + import core.stdc.string : strlen; columns = cast(uint) mysql_stmt_field_count(stmt.stmt); describe.reserve(columns); - for(int i = 0; i < columns; ++i) { + for (int i = 0; i < columns; ++i) { describe ~= Describe(); auto d = &describe.back(); @@ -672,29 +824,64 @@ private struct Driver(Policy) { } void build_bind() { - import core.stdc.string: memset; + import core.stdc.string : memset; import core.memory : GC; bind.reserve(columns); - for(int i = 0; i < columns; ++i) { + for (int i = 0; i != columns; ++i) { auto d = &describe[i]; bind ~= Bind(); auto b = &bind.back(); - - // let in ints for now - if (d.field.type == MYSQL_TYPE_LONG) { - b.mysql_type = d.field.type; + b.mysql_type = d.field.type; + b.allocSize = cast(uint)(d.field.length + 1); + switch (d.field.type) { + case MYSQL_TYPE_TINY: + b.type = ValueType.Char; + break; + case MYSQL_TYPE_SHORT: + b.type = ValueType.Short; + break; + case MYSQL_TYPE_LONG: b.type = ValueType.Int; - } else if (d.field.type == MYSQL_TYPE_DATE) { - b.mysql_type = d.field.type; + break; + case MYSQL_TYPE_LONGLONG: + b.type = ValueType.Long; + break; + case MYSQL_TYPE_FLOAT: + b.type = ValueType.Float; + break; + case MYSQL_TYPE_DOUBLE: + b.type = ValueType.Double; + break; + + case MYSQL_TYPE_DATE: b.type = ValueType.Date; - } else { + b.allocSize += 32; + break; + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: + b.type = ValueType.Time; + b.allocSize += 30; + break; + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP2: + case MYSQL_TYPE_TIMESTAMP: + b.type = ValueType.DateTime; + b.allocSize += 30; + break; + case MYSQL_TYPE_BLOB: + b.type = ValueType.Raw; + b.allocSize += 512; + break; + default: b.mysql_type = MYSQL_TYPE_STRING; b.type = ValueType.String; - } + b.allocSize += 256; + break; - b.allocSize = cast(uint)(d.field.length + 1); + } b.data = allocator.allocate(b.allocSize); } @@ -707,10 +894,12 @@ private struct Driver(Policy) { status = check("mysql_stmt_fetch", stmt.stmt, mysql_stmt_fetch(stmt.stmt)); if (!status) { return 1; - } else if (status == MYSQL_NO_DATA) { + } + else if (status == MYSQL_NO_DATA) { //rows_ = row_count_; return 0; - } else if (status == MYSQL_DATA_TRUNCATED) { + } + else if (status == MYSQL_DATA_TRUNCATED) { raiseError("mysql_stmt_fetch: truncation", status); } @@ -718,49 +907,109 @@ private struct Driver(Policy) { return 0; } + Variant getValue(Cell* cell) { + Variant value; + if (isNull(cell)) + return value; + switch (cell.bind.type) { + case ValueType.Char: + value = (*cast(char*) cell.bind.data.ptr); + break; + case ValueType.Short: + value = (*cast(short*) cell.bind.data.ptr); + break; + case ValueType.Int: + value = (*cast(int*) cell.bind.data.ptr); + break; + case ValueType.Long: + value = (*cast(long*) cell.bind.data.ptr); + break; + case ValueType.Float: + value = (*cast(float*) cell.bind.data.ptr); + break; + case ValueType.Double: + value = (*cast(double*) cell.bind.data.ptr); + break; + case ValueType.String: { + auto ptr = cast(char*) cell.bind.data.ptr; + value = cast(string)(ptr[0 .. cell.bind.length]); + } + break; + case ValueType.Date: { + MYSQL_TIME* t = cast(MYSQL_TIME*) cell.bind.data.ptr; + value = Date(t.year, t.month, t.day); + } + break; + case ValueType.Time: { + MYSQL_TIME* t = cast(MYSQL_TIME*) cell.bind.data.ptr; + value = Time(t.hour, t.minute, t.second); + } + break; + case ValueType.DateTime: { + MYSQL_TIME* t = cast(MYSQL_TIME*) cell.bind.data.ptr; + value = DateTime(t.year, t.month, t.day, t.hour, t.minute, + t.second); + } + break; + case ValueType.Raw:{ + auto ptr = cast(ubyte * ) cell.bind.data.ptr; + value = ptr[0 .. cell.bind.length]; + } + break; + default: + break; + } + return value; + } + // value getters + auto type(int col){ + return describe[col].field.type; + } + auto name(size_t idx) { return describe[idx].name; } - char[] get(X:char[])(Bind *b) { - auto ptr = cast(char*) b.data.ptr; - return ptr[0..b.length]; + ubyte[] rawData(Cell* cell) { + auto ptr = cast(ubyte*) cell.bind.data.ptr; + return ptr[0 .. cell.bind.length]; } - auto get(X:string)(Cell* cell) { - //return cast(string) get!(char[])(b); - auto ptr = cast(char*) cell.bind.data.ptr; - return ptr[0..cell.bind.length]; + bool isNull(Cell* cell) { + return cell.bind.is_null > 0; } - auto get(X:int)(Cell* cell) { - return *cast(int*) cell.bind.data.ptr; - } - - auto get(X:Date)(Cell* cell) { - //return Date(2016,1,1); // fix - MYSQL_TIME *t = cast(MYSQL_TIME*) cell.bind.data.ptr; - //t.year,t.month,t.day,t.hour,t.minute,t.second - return Date(t.year,t.month,t.day); - } - - static void checkType(T)(Bind *b) { + static void checkType(T)(Bind* b) { int x = TypeInfo!T.type(); int y = b.mysql_type; - if (x == y) return; - warning("type pair mismatch: ",x, ":", y); + if (x == y) + return; + warning("type pair mismatch: ", x, ":", y); throw new DatabaseException("type mismatch"); } // refactor as a better 1-n bind mapping - struct TypeInfo(T:int) {static int type() {return MYSQL_TYPE_LONG;}} - struct TypeInfo(T:string) {static int type() {return MYSQL_TYPE_STRING;}} - struct TypeInfo(T:Date) {static int type() {return MYSQL_TYPE_DATE;}} + struct TypeInfo(T : int) { + static int type() { + return MYSQL_TYPE_LONG; + } + } + + struct TypeInfo(T : string) { + static int type() { + return MYSQL_TYPE_STRING; + } + } + + struct TypeInfo(T : Date) { + static int type() { + return MYSQL_TYPE_DATE; + } + } } } - } diff --git a/src/std/database/odbc/database.d b/src/std/database/odbc/database.d index f6e6dd9..5d2164d 100644 --- a/src/std/database/odbc/database.d +++ b/src/std/database/odbc/database.d @@ -29,24 +29,22 @@ struct DefaultPolicy { alias Allocator = MyMallocator; } -alias Database(T) = BasicDatabase!(Driver!T,T); +alias Database(T) = BasicDatabase!(Driver!T, T); -auto createDatabase()(string defaultURI="") { - return Database!DefaultPolicy(defaultURI); +auto createDatabase()(string defaultURI = "") { + return Database!DefaultPolicy(defaultURI); } struct Driver(Policy) { alias Allocator = Policy.Allocator; - alias Cell = BasicCell!(Driver,Policy); + alias Cell = BasicCell!(Driver, Policy); struct Database { alias queryVariableType = QueryVariableType.QuestionMark; - static const FeatureArray features = [ - //Feature.InputBinding, - //Feature.DateBinding, - Feature.ConnectionPool, - ]; + static const FeatureArray features = [//Feature.InputBinding, + //Feature.DateBinding, + Feature.ConnectionPool,]; Allocator allocator; SQLHENV env; @@ -54,24 +52,22 @@ struct Driver(Policy) { this(string defaultURI_) { info("Database"); allocator = Allocator(); - check( - "SQLAllocHandle", - SQLAllocHandle( - SQL_HANDLE_ENV, - SQL_NULL_HANDLE, - &env)); - SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, cast(void *) SQL_OV_ODBC3, 0); + check("SQLAllocHandle", SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, + &env)); + SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, cast(void*) SQL_OV_ODBC3, 0); } ~this() { - info("~Database"); - if (!env) return; - check("SQLFreeHandle", SQL_HANDLE_ENV, env, SQLFreeHandle(SQL_HANDLE_ENV, env)); + // info("~Database"); + if (!env) + return; + check("SQLFreeHandle", SQL_HANDLE_ENV, env, SQLFreeHandle(SQL_HANDLE_ENV, + env)); env = null; } void showDrivers() { - import core.stdc.string: strlen; + import core.stdc.string : strlen; SQLUSMALLINT direction; @@ -83,17 +79,10 @@ struct Driver(Policy) { direction = SQL_FETCH_FIRST; info("DRIVERS:"); - while(SQL_SUCCEEDED(ret = SQLDrivers( - env, - direction, - driver.ptr, - driver.sizeof, - &driver_ret, - attr.ptr, - attr.sizeof, - &attr_ret))) { + while (SQL_SUCCEEDED(ret = SQLDrivers(env, direction, driver.ptr, + driver.sizeof, &driver_ret, attr.ptr, attr.sizeof, &attr_ret))) { direction = SQL_FETCH_NEXT; - info(driver.ptr[0..strlen(driver.ptr)], ": ", attr.ptr[0..strlen(attr.ptr)]); + info(driver.ptr[0 .. strlen(driver.ptr)], ": ", attr.ptr[0 .. strlen(attr.ptr)]); //if (ret == SQL_SUCCESS_WITH_INFO) printf("\tdata truncation\n"); } } @@ -115,25 +104,22 @@ struct Driver(Policy) { SQLSMALLINT outstrlen; //string DSN = "DSN=testdb"; - SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_DBC,db.env,&con); + SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_DBC, db.env, &con); if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO)) { throw new DatabaseException("SQLAllocHandle error: " ~ to!string(ret)); } - check("SQLConnect", SQL_HANDLE_DBC, con, SQLConnect( - con, - cast(SQLCHAR*) toStringz(source.server), - SQL_NTS, - cast(SQLCHAR*) toStringz(source.username), - SQL_NTS, - cast(SQLCHAR*) toStringz(source.password), - SQL_NTS)); + check("SQLConnect", SQL_HANDLE_DBC, con, SQLConnect(con, + cast(SQLCHAR*) toStringz(source.server), SQL_NTS, + cast(SQLCHAR*) toStringz(source.username), SQL_NTS, + cast(SQLCHAR*) toStringz(source.password), SQL_NTS)); connected = true; } ~this() { - info("~Connection: ", source); - if (connected) check("SQLDisconnect()", SQL_HANDLE_DBC, con, SQLDisconnect(con)); + // info("~Connection: ", source); + if (connected) + check("SQLDisconnect()", SQL_HANDLE_DBC, con, SQLDisconnect(con)); check("SQLFreeHandle", SQLFreeHandle(SQL_HANDLE_DBC, con)); } } @@ -141,7 +127,7 @@ struct Driver(Policy) { struct Statement { Connection* con; string sql; - Allocator *allocator; + Allocator* allocator; SQLHSTMT stmt; bool hasRows_; // not working int binds; @@ -156,11 +142,12 @@ struct Driver(Policy) { } ~this() { - info("~Statement"); - for(int i = 0; i < inputbind_.length; ++i) { - allocator.deallocate(inputbind_[i].data[0..inputbind_[i].allocSize]); + // info("~Statement"); + for (int i = 0; i < inputbind_.length; ++i) { + allocator.deallocate(inputbind_[i].data[0 .. inputbind_[i].allocSize]); } - if (stmt) check("SQLFreeHandle", SQLFreeHandle(SQL_HANDLE_STMT, stmt)); + if (stmt) + check("SQLFreeHandle", SQLFreeHandle(SQL_HANDLE_STMT, stmt)); // stmt = null? needed } @@ -175,11 +162,12 @@ struct Driver(Policy) { b.data = cast(void*)(allocator.allocate(b.allocSize)); inputBind(n, b); - *(cast(SQLINTEGER*) inputbind_[n-1].data) = value; + *(cast(SQLINTEGER*) inputbind_[n - 1].data) = value; } - void bind(int n, const char[] value){ - import core.stdc.string: strncpy; + void bind(int n, const char[] value) { + import core.stdc.string : strncpy; + info("input bind: n: ", n, ", value: ", value); // no null termination needed @@ -201,44 +189,31 @@ struct Driver(Policy) { inputbind_ ~= bind; auto b = &inputbind_.back(); - check("SQLBindParameter", SQLBindParameter( - stmt, - cast(SQLSMALLINT) n, - SQL_PARAM_INPUT, - b.bindType, - b.dbtype, - 0, - 0, - b.data, - b.allocSize, - null)); + check("SQLBindParameter", SQLBindParameter(stmt, + cast(SQLSMALLINT) n, SQL_PARAM_INPUT, b.bindType, b.dbtype, 0, + 0, b.data, b.allocSize, null)); } - void prepare() { //if (!data_.st) - check("SQLPrepare", SQLPrepare( - stmt, - cast(SQLCHAR*) toStringz(sql), - SQL_NTS)); + check("SQLPrepare", SQLPrepare(stmt, cast(SQLCHAR*) toStringz(sql), SQL_NTS)); SQLSMALLINT v; check("SQLNumParams", SQLNumParams(stmt, &v)); binds = v; - check("SQLNumResultCols", SQLNumResultCols (stmt, &v)); + check("SQLNumResultCols", SQLNumResultCols(stmt, &v)); info("binds: ", binds); } void query() { if (!binds) { info("sql execute direct: ", sql); - SQLRETURN ret = SQLExecDirect( - stmt, - cast(SQLCHAR*) toStringz(sql), - SQL_NTS); + SQLRETURN ret = SQLExecDirect(stmt, cast(SQLCHAR*) toStringz(sql), + SQL_NTS); check("SQLExecuteDirect()", SQL_HANDLE_STMT, stmt, ret); hasRows_ = ret != SQL_NO_DATA; - } else { + } + else { info("sql execute prepared: ", sql); SQLRETURN ret = SQLExecute(stmt); check("SQLExecute()", SQL_HANDLE_STMT, stmt, ret); @@ -246,22 +221,25 @@ struct Driver(Policy) { } } - void query(X...) (X args) { + void query(X...)(X args) { bindAll(args); query(); } - bool hasRows() {return hasRows_;} + bool hasRows() { + return hasRows_; + } void exec() { - check("SQLExecDirect", SQLExecDirect(stmt,cast(SQLCHAR*) toStringz(sql), SQL_NTS)); + check("SQLExecDirect", SQLExecDirect(stmt, cast(SQLCHAR*) toStringz(sql), + SQL_NTS)); } void reset() { //SQLCloseCursor } - private void bindAll(T...) (T args) { + private void bindAll(T...)(T args) { int col; foreach (arg; args) { bind(++col, arg); @@ -269,13 +247,12 @@ struct Driver(Policy) { } } - struct Describe { static const nameSize = 256; char[nameSize] name; SQLSMALLINT nameLen; SQLSMALLINT type; - SQLULEN size; + SQLULEN size; SQLSMALLINT digits; SQLSMALLINT nullable; SQLCHAR* data; @@ -296,8 +273,8 @@ struct Driver(Policy) { //SQLULEN allocSize; //SQLLEN len; - SQLINTEGER size; - SQLINTEGER allocSize; + SQLINTEGER size; + SQLINTEGER allocSize; SQLINTEGER len; } @@ -307,7 +284,7 @@ struct Driver(Policy) { static const maxData = 256; Statement* stmt; - Allocator *allocator; + Allocator* allocator; int columns; Array!Describe describe; Array!Bind bind; @@ -320,14 +297,14 @@ struct Driver(Policy) { // check for result set (probably a better way) //if (!stmt.hasRows) return; SQLSMALLINT v; - check("SQLNumResultCols", SQLNumResultCols (stmt.stmt, &v)); + check("SQLNumResultCols", SQLNumResultCols(stmt.stmt, &v)); columns = v; build_describe(); build_bind(); } ~this() { - for(int i = 0; i < bind.length; ++i) { + for (int i = 0; i < bind.length; ++i) { free(bind[i].data); } } @@ -336,20 +313,14 @@ struct Driver(Policy) { describe.reserve(columns); - for(int i = 0; i < columns; ++i) { + for (int i = 0; i < columns; ++i) { describe ~= Describe(); auto d = &describe.back(); - check("SQLDescribeCol", SQLDescribeCol( - stmt.stmt, - cast(SQLUSMALLINT) (i+1), - cast(SQLCHAR *) d.name, - cast(SQLSMALLINT) Describe.nameSize, - &d.nameLen, - &d.type, - &d.size, - &d.digits, - &d.nullable)); + check("SQLDescribeCol", SQLDescribeCol(stmt.stmt, + cast(SQLUSMALLINT)(i + 1), cast(SQLCHAR*) d.name, + cast(SQLSMALLINT) Describe.nameSize, &d.nameLen, &d.type, + &d.size, &d.digits, &d.nullable)); //info("NAME: ", d.name, ", type: ", d.type); } @@ -360,53 +331,50 @@ struct Driver(Policy) { bind.reserve(columns); - for(int i = 0; i < columns; ++i) { + for (int i = 0; i < columns; ++i) { bind ~= Bind(); auto b = &bind.back(); auto d = &describe[i]; b.size = d.size; - b.allocSize = cast(SQLULEN) (b.size + 1); + b.allocSize = cast(SQLULEN)(b.size + 1); b.data = cast(void*)(allocator.allocate(b.allocSize)); GC.addRange(b.data, b.allocSize); // just INT and VARCHAR for now switch (d.type) { - case SQL_INTEGER: - //b.type = SQL_C_LONG; - b.type = ValueType.String; - b.bindType = SQL_C_CHAR; - break; - case SQL_VARCHAR: - b.type = ValueType.String; - b.bindType = SQL_C_CHAR; - break; - default: - throw new DatabaseException("bind error: type: " ~ to!string(d.type)); + case SQL_INTEGER: + //b.type = SQL_C_LONG; + b.type = ValueType.String; + b.bindType = SQL_C_CHAR; + break; + case SQL_VARCHAR: + b.type = ValueType.String; + b.bindType = SQL_C_CHAR; + break; + default: + throw new DatabaseException("bind error: type: " ~ to!string(d.type)); } - check("SQLBINDCol", SQLBindCol ( - stmt.stmt, - cast(SQLUSMALLINT) (i+1), - b.bindType, - b.data, - b.size, - &b.len)); - - info( - "output bind: index: ", i, - ", type: ", b.bindType, - ", size: ", b.size, - ", allocSize: ", b.allocSize); + check("SQLBINDCol", SQLBindCol(stmt.stmt, + cast(SQLUSMALLINT)(i + 1), b.bindType, b.data, b.size, &b.len)); + + info("output bind: index: ", i, ", type: ", b.bindType, + ", size: ", b.size, ", allocSize: ", b.allocSize); } } + auto type(int col){ + return describe[i].type; + } + int fetch() { //info("SQLFetch"); status = SQLFetch(stmt.stmt); if (status == SQL_SUCCESS) { - return 1; - } else if (status == SQL_NO_DATA) { + return 1; + } + else if (status == SQL_NO_DATA) { stmt.reset(); return 0; } @@ -416,40 +384,45 @@ struct Driver(Policy) { auto name(size_t idx) { auto d = &describe[idx]; - return cast(string) d.name[0..d.nameLen]; + return cast(string) d.name[0 .. d.nameLen]; } - auto get(X:string)(Cell* cell) { - checkType(cell.bind.bindType, SQL_C_CHAR); - auto ptr = cast(immutable char*) cell.bind.data; - return cast(string) ptr[0..cell.bind.len]; + ubyte[] rawData(Cell* cell) { + auto ptr = cast(ubyte*) cell.bind.data; + return ptr[0 .. cell.bind.len]; } - auto get(X:int)(Cell* cell) { - //if (b.bindType == SQL_C_CHAR) return to!int(as!string()); // tmp hack - checkType(cell.bind.bindType, SQL_C_LONG); - return *(cast(int*) cell.bind.data); + Variant getValue(Cell* cell) { + Variant = value; + if (cell.bind.type == ValueType.String) { + auto ptr = cast(immutable char*) cell.bind.data; + value = cast(string) ptr[0 .. cell.bind.len]; + } + return value; //TODO: } - auto get(X:Date)(Cell* cell) { - return Date(2016,1,1); // fix + bool isNull(Cell* cell) { + return false; } void checkType(SQLSMALLINT a, SQLSMALLINT b) { - if (a != b) throw new DatabaseException("type mismatch"); + if (a != b) + throw new DatabaseException("type mismatch"); } } static void check()(string msg, SQLRETURN ret) { info(msg, ":", ret); - if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO || ret == SQL_NO_DATA) return; + if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO || ret == SQL_NO_DATA) + return; throw new DatabaseException("odbc error: " ~ msg); } static void check()(string msg, SQLSMALLINT handle_type, SQLHANDLE handle, SQLRETURN ret) { info(msg, ":", ret); - if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO || ret == SQL_NO_DATA) return; + if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO || ret == SQL_NO_DATA) + return; throw_detail(handle, handle_type, msg); } @@ -466,23 +439,17 @@ struct Driver(Policy) { error ~= ": "; do { - ret = SQLGetDiagRec( - type, - handle, - ++i, - cast(char*) state, - &native, - cast(char*)text, - text.length, - &len); + ret = SQLGetDiagRec(type, handle, ++i, cast(char*) state, &native, + cast(char*) text, text.length, &len); if (SQL_SUCCEEDED(ret)) { - auto s = text[0..len]; + auto s = text[0 .. len]; info("error: ", s); error ~= s; //writefln("%s:%ld:%ld:%s\n", state, i, native, text); } - } while (ret == SQL_SUCCESS); + } + while (ret == SQL_SUCCESS); throw new DatabaseException(error); } diff --git a/src/std/database/option.d b/src/std/database/option.d deleted file mode 100644 index 922e9e8..0000000 --- a/src/std/database/option.d +++ /dev/null @@ -1,68 +0,0 @@ -module std.database.option; - -struct Option(T) { - private T _value; - private bool _isNull = true; - - this(inout T value) inout { - _value = value; - _isNull = false; - } - - template toString() { - import std.format : FormatSpec, formatValue; - // Needs to be a template because of DMD @@BUG@@ 13737. - void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) { - if (isNull) - { - sink.formatValue("Nullable.null", fmt); - } - else - { - sink.formatValue(_value, fmt); - } - } - - // Issue 14940 - void toString()(scope void delegate(const(char)[]) @safe sink, FormatSpec!char fmt) { - if (isNull) - { - sink.formatValue("Nullable.null", fmt); - } - else - { - sink.formatValue(_value, fmt); - } - } - } - - @property bool isNull() const @safe pure nothrow { - return _isNull; - } - - void nullify()() { - .destroy(_value); - _isNull = true; - } - - void opAssign()(T value) { - _value = value; - _isNull = false; - } - - @property ref inout(T) get() inout @safe pure nothrow { - enum message = "Called `get' on null Nullable!" ~ T.stringof ~ "."; - assert(!isNull, message); - return _value; - } - - @property ref inout(T) front() inout @safe pure nothrow {return get;} - @property bool empty() const @safe pure nothrow {return isNull;} - @property void popFront() @safe pure nothrow {nullify;} - - //Implicitly converts to T: must not be in the null state. - alias get this; - - -} - diff --git a/src/std/database/oracle/database.d b/src/std/database/oracle/database.d index 98b90ba..ff5fbf5 100644 --- a/src/std/database/oracle/database.d +++ b/src/std/database/oracle/database.d @@ -22,19 +22,20 @@ struct DefaultPolicy { alias Allocator = MyMallocator; } -alias Database(T) = BasicDatabase!(Driver!T,T); +alias Database(T) = BasicDatabase!(Driver!T, T); -auto createDatabase()(string defaultURI="") { - return Database!DefaultPolicy(defaultURI); +auto createDatabase()(string defaultURI = "") { + return Database!DefaultPolicy(defaultURI); } -auto createDatabase(T)(string defaultURI="") { - return Database!T(defaultURI); +auto createDatabase(T)(string defaultURI = "") { + return Database!T(defaultURI); } void check()(string msg, sword status) { info(msg, ":", status); - if (status == OCI_SUCCESS) return; + if (status == OCI_SUCCESS) + return; throw new DatabaseException("OCI error: " ~ msg); } @@ -42,51 +43,35 @@ void check()(string msg, sword status) { //static auto create()(string uri="") {return Database!DefaultPolicy();} //} - -T attrGet(T)(OCIStmt *stmt, OCIError *error, ub4 attribute) { +T attrGet(T)(OCIStmt* stmt, OCIError* error, ub4 attribute) { T value; ub4 sz = T.sizeof; attrGet!T(stmt, error, attribute, value); return value; } -void attrGet(T)(OCIStmt *stmt, OCIError *error, ub4 attribute, ref T value) { +void attrGet(T)(OCIStmt* stmt, OCIError* error, ub4 attribute, ref T value) { return attrGet(stmt, OCI_HTYPE_STMT, error, attribute, value); } void attrGet(T)(void* handle, ub4 handleType, OCIError* error, ub4 attribute, ref T value) { ub4 sz = T.sizeof; - check("OCIAttrGet", OCIAttrGet( - handle, - OCI_HTYPE_STMT, - &value, - &sz, - attribute, - error)); + check("OCIAttrGet", OCIAttrGet(handle, OCI_HTYPE_STMT, &value, &sz, attribute, + error)); } - - - struct Driver(Policy) { alias Allocator = Policy.Allocator; - alias Cell = BasicCell!(Driver,Policy); - - + alias Cell = BasicCell!(Driver, Policy); - static void attrSet(T)(OCIStmt *stmt, ub4 attribute, ref T value) { + static void attrSet(T)(OCIStmt* stmt, ub4 attribute, ref T value) { return attrSet(stmt, OCI_HTYPE_STMT, attribute, value); } static void attrSet(T)(void* handle, ub4 handleType, ub4 attribute, T value) { ub4 sz = T.sizeof; - check("OCIAttrSet", OCIAttrSet( - stmt, - OCI_HTYPE_STMT, - &value, - sz, - attribute, - error)); + check("OCIAttrSet", OCIAttrSet(stmt, OCI_HTYPE_STMT, &value, sz, attribute, + error)); } struct BindContext { @@ -96,7 +81,7 @@ struct Driver(Policy) { int rowArraySize; } - static void basicCtor(T) (ref BindContext ctx) { + static void basicCtor(T)(ref BindContext ctx) { emplace(cast(T*) ctx.bind.data); } @@ -122,275 +107,236 @@ struct Driver(Policy) { struct Conversion { int colType, bindType; - private static const Conversion[4] info = [ - {SQLT_INT,SQLT_INT}, - {SQLT_NUM,SQLT_STR}, - {SQLT_CHR,SQLT_STR}, - {SQLT_DAT,SQLT_DAT} - ]; - - static int get(int colType) { - // needs improvement - foreach(ref i; info) { - if (i.colType == colType) return i.bindType; - } - throw new DatabaseException( - "unknown conversion for column: " ~ to!string(colType)); - } - } + private static const Conversion[4] info = [{SQLT_INT, SQLT_INT}, { + SQLT_NUM, SQLT_STR}, {SQLT_CHR, SQLT_STR}, {SQLT_DAT, SQLT_DAT}]; - struct BindInfo { - ub2 bindType; - void function(ref BindContext ctx) binder; - - static const BindInfo[4] info = [ - {SQLT_INT,&charBinder}, - {SQLT_STR,&charBinder}, - {SQLT_STR,&charBinder}, - {SQLT_DAT,&dateBinder!(SQLT_ODT,OCIDate)} - ]; - - static void bind(ref BindContext ctx) { - import core.memory : GC; // needed? - auto d = ctx.describe, b = ctx.bind; - auto colType = ctx.describe.oType; - auto bindType = Conversion.get(colType); - auto allocator = ctx.allocator; - - // needs improvement - foreach(ref i; info) { - if (i.bindType == bindType) { - i.binder(ctx); - b.data = allocator.allocate(b.allocSize * ctx.rowArraySize); - b.ind = allocator.allocate(sb2.sizeof * ctx.rowArraySize); - b.length = allocator.allocate(ub2.sizeof * ctx.rowArraySize); - if (b.ctor) b.ctor(ctx); - GC.addRange(b.data.ptr, b.data.length); - return; + static int get(int colType) { + // needs improvement + foreach (ref i; info) { + if (i.colType == colType) + return i.bindType; } - } - - throw new DatabaseException( - "bind not supported: " ~ to!string(colType)); - } - - } - - - struct Database { - alias queryVariableType = QueryVariableType.Dollar; - - Allocator allocator; - OCIEnv* env; - OCIError *error; - - static const FeatureArray features = [ - //Feature.InputBinding, - //Feature.DateBinding, - //Feature.ConnectionPool, - Feature.OutputArrayBinding, - ]; - - this(string defaultURI_) { - allocator = Allocator(); - info("oracle: opening database"); - ub4 mode = OCI_THREADED | OCI_OBJECT; - - check("OCIEnvCreate", OCIEnvCreate( - &env, - mode, - null, - null, - null, - null, - 0, - null)); - - check("OCIHandleAlloc", OCIHandleAlloc( - env, - cast(void**) &error, - OCI_HTYPE_ERROR, - 0, - null)); - } - - ~this() { - info("oracle: closing database"); - if (env) { - sword status = OCIHandleFree(env, OCI_HTYPE_ENV); - env = null; + throw new DatabaseException("unknown conversion for column: " ~ to!string(colType)); } } - } - - struct Connection { - Database* db; - Source source; - OCIError *error; - OCISvcCtx *svc_ctx; - bool connected; - - this(Database* db_, Source source_) {hhkk: - db = db_; - source = source_; - error = db.error; - - string dbname = getDBName(source); - - check("OCILogon: ", OCILogon( - db_.env, - error, - &svc_ctx, - cast(const OraText*) source.username.ptr, - cast(ub4) source.username.length, - cast(const OraText*) source.password.ptr, - cast(ub4) source.password.length, - cast(const OraText*) dbname.ptr, - cast(ub4) dbname.length)); - - connected = true; - } + struct BindInfo { + ub2 bindType; + void function(ref BindContext ctx) binder; + + static const BindInfo[4] info = [{SQLT_INT, &charBinder}, { + SQLT_STR, &charBinder}, {SQLT_STR, &charBinder}, { + SQLT_DAT, &dateBinder!(SQLT_ODT, OCIDate)}]; + + static void bind(ref BindContext ctx) { + import core.memory : GC; // needed? + auto d = ctx.describe, b = ctx.bind; + auto colType = ctx.describe.oType; + auto bindType = Conversion.get(colType); + auto allocator = ctx.allocator; + + // needs improvement + foreach (ref i; info) { + if (i.bindType == bindType) { + i.binder(ctx); + b.data = allocator.allocate(b.allocSize * ctx.rowArraySize); + b.ind = allocator.allocate(sb2.sizeof * ctx.rowArraySize); + b.length = allocator.allocate(ub2.sizeof * ctx.rowArraySize); + if (b.ctor) + b.ctor(ctx); + GC.addRange(b.data.ptr, b.data.length); + return; + } + } + + throw new DatabaseException("bind not supported: " ~ to!string(colType)); + } - ~this() { - if (svc_ctx) check("OCILogoff", OCILogoff(svc_ctx, error)); - } - - static string getDBName(Source src) { - string dbName = - "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)" ~ - "(HOST=" ~ src.server ~ ")" ~ - "(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)))"; // need service name - return dbName; - } - } - - static const nameSize = 256; - - struct Describe { - //char[nameSize] name; - - OraText* ora_name; - string name; - int index; - ub4 name_len; - //string name; - ub2 oType; - ub2 size; - //const nd_oracle_type_info *type_info; - //oracle_bind_type bind_type; - OCIParam *param; - OCIDefine *define; - } - - struct Bind { - ValueType type; - void[] data; - sb4 allocSize; - ub2 oType; - void[] ind; - void[] length; - void function(ref BindContext ctx) ctor; - void function(void[] data) dtor; - } + } - // non memeber needed to avoid forward error - //auto range(T)(Statement!T stmt) {return Result!T(stmt).range();} - - struct Statement { - Connection *con; - string sql; - Allocator *allocator; - OCIError *error; - OCIStmt *stmt; - ub2 stmt_type; - int binds; - Array!Bind inputBind; - - this(Connection* con_, string sql_) { - con = con_; - sql = sql_; - allocator = &con.db.allocator; - error = con.error; - - check("OCIHandleAlloc", OCIHandleAlloc( - con.db.env, - cast(void**) &stmt, - OCI_HTYPE_STMT, - 0, - null)); - - //attrSet!ub4(OCI_ATTR_PREFETCH_ROWS, 1000); - } + struct Database { + alias queryVariableType = QueryVariableType.Dollar; - ~this() { - for(int i = 0; i < inputBind.length; ++i) { - allocator.deallocate(inputBind[i].data[0..inputBind[i].allocSize]); - } - if (stmt) check("OCIHandleFree", OCIHandleFree(stmt,OCI_HTYPE_STMT)); - // stmt = null? needed - } + Allocator allocator; + OCIEnv* env; + OCIError* error; - void exec() { - //check("SQLExecDirect", SQLExecDirect(data_.stmt,cast(SQLCHAR*) toStringz(data_.sql), SQL_NTS)); - } + static const FeatureArray features = [//Feature.InputBinding, + //Feature.DateBinding, + //Feature.ConnectionPool, + Feature.OutputArrayBinding,]; - void prepare() { + this(string defaultURI_) { + allocator = Allocator(); + info("oracle: opening database"); + ub4 mode = OCI_THREADED | OCI_OBJECT; - check("OCIStmtPrepare", OCIStmtPrepare( - stmt, - error, - cast(OraText*) sql.ptr, - cast(ub4) sql.length, - OCI_NTV_SYNTAX, - OCI_DEFAULT)); + check("OCIEnvCreate", OCIEnvCreate(&env, mode, null, + null, null, null, 0, null)); + check("OCIHandleAlloc", OCIHandleAlloc(env, + cast(void**)&error, OCI_HTYPE_ERROR, 0, null)); + } - // get the type of statement - stmt_type = attrGet!ub2(OCI_ATTR_STMT_TYPE); - binds = attrGet!ub4(OCI_ATTR_BIND_COUNT); - info("binds: ", binds); - } + ~this() { + // info("oracle: closing database"); + if (env) { + sword status = OCIHandleFree(env, OCI_HTYPE_ENV); + env = null; + } + } - void query() { - ub4 iters = stmt_type == OCI_STMT_SELECT ? 0:1; - info("iters: ", iters); - info("execute sql: ", sql); - - check("OCIStmtExecute", OCIStmtExecute( - con.svc_ctx, - stmt, - error, - iters, - 0, - null, - null, - OCI_COMMIT_ON_SUCCESS)); - } - - void query(X...) (X args) { - foreach (arg; args) { - info("ARG: ", arg); - } + } - info("variadic query not implemented yet"); - //bindAll(args); - //query(); - } + struct Connection { + Database* db; + Source source; + OCIError* error; + OCISvcCtx* svc_ctx; + bool connected; + + this(Database* db_, Source source_) { + hhkk: + db = db_; + source = source_; + error = db.error; + + string dbname = getDBName(source); + + check("OCILogon: ", OCILogon(db_.env, error, &svc_ctx, + cast(const OraText*) source.username.ptr, + cast(ub4) source.username.length, + cast(const OraText*) source.password.ptr, + cast(ub4) source.password.length, + cast(const OraText*) dbname.ptr, cast(ub4) dbname.length)); + + connected = true; + } + + ~this() { + if (svc_ctx) + check("OCILogoff", OCILogoff(svc_ctx, error)); + } + + static string getDBName(Source src) { + string dbName = "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)" ~ "(HOST=" ~ src.server + ~ ")" ~ "(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)))"; // need service name + return dbName; + } + } - bool hasRows() { - // need a better way - int columns = attrGet!ub4(OCI_ATTR_PARAM_COUNT); - return columns != 0; - } + static const nameSize = 256; + + struct Describe { + //char[nameSize] name; + + OraText* ora_name; + string name; + int index; + ub4 name_len; + //string name; + ub2 oType; + ub2 size; + //const nd_oracle_type_info *type_info; + //oracle_bind_type bind_type; + OCIParam* param; + OCIDefine* define; + } - private void bindAll(T...) (T args) { - int col; - foreach (arg; args) bind(++col, arg); - } + struct Bind { + ValueType type; + void[] data; + sb4 allocSize; + ub2 oType; + void[] ind; + void[] length; + void function(ref BindContext ctx) ctor; + void function(void[] data) dtor; + } - void bind(int n, int value) { - /* + // non memeber needed to avoid forward error + //auto range(T)(Statement!T stmt) {return Result!T(stmt).range();} + + struct Statement { + Connection* con; + string sql; + Allocator* allocator; + OCIError* error; + OCIStmt* stmt; + ub2 stmt_type; + int binds; + Array!Bind inputBind; + + this(Connection* con_, string sql_) { + con = con_; + sql = sql_; + allocator = &con.db.allocator; + error = con.error; + + check("OCIHandleAlloc", OCIHandleAlloc(con.db.env, + cast(void**)&stmt, OCI_HTYPE_STMT, 0, null)); + + //attrSet!ub4(OCI_ATTR_PREFETCH_ROWS, 1000); + } + + ~this() { + for (int i = 0; i < inputBind.length; ++i) { + allocator.deallocate(inputBind[i].data[0 .. inputBind[i].allocSize]); + } + if (stmt) + check("OCIHandleFree", OCIHandleFree(stmt, OCI_HTYPE_STMT)); + // stmt = null? needed + } + + void exec() { + //check("SQLExecDirect", SQLExecDirect(data_.stmt,cast(SQLCHAR*) toStringz(data_.sql), SQL_NTS)); + } + + void prepare() { + + check("OCIStmtPrepare", OCIStmtPrepare(stmt, error, + cast(OraText*) sql.ptr, cast(ub4) sql.length, + OCI_NTV_SYNTAX, OCI_DEFAULT)); + + // get the type of statement + stmt_type = attrGet!ub2(OCI_ATTR_STMT_TYPE); + binds = attrGet!ub4(OCI_ATTR_BIND_COUNT); + info("binds: ", binds); + } + + void query() { + ub4 iters = stmt_type == OCI_STMT_SELECT ? 0 : 1; + info("iters: ", iters); + info("execute sql: ", sql); + + check("OCIStmtExecute", OCIStmtExecute(con.svc_ctx, + stmt, error, iters, 0, null, null, OCI_COMMIT_ON_SUCCESS)); + } + + void query(X...)(X args) { + foreach (arg; args) { + info("ARG: ", arg); + } + + info("variadic query not implemented yet"); + //bindAll(args); + //query(); + } + + bool hasRows() { + // need a better way + int columns = attrGet!ub4(OCI_ATTR_PARAM_COUNT); + return columns != 0; + } + + private void bindAll(T...)(T args) { + int col; + foreach (arg; args) + bind(++col, arg); + } + + void bind(int n, int value) { + /* info("input bind: n: ", n, ", value: ", value); Bind b; @@ -403,10 +349,10 @@ struct Driver(Policy) { *(cast(SQLINTEGER*) data_.inputBind[n-1].data) = value; */ - } + } - void bind(int n, const char[] value){ - /* + void bind(int n, const char[] value) { + /* import core.stdc.string: strncpy; info("input bind: n: ", n, ", value: ", value); // no null termination needed @@ -421,239 +367,233 @@ struct Driver(Policy) { strncpy(cast(char*) b.data, value.ptr, b.size); */ - } + } + + void bind(int n, Date d) { + throw new DatabaseException("Date input binding not yet implemented"); + } + + void reset() { + } + //: Error: template std.database.oracle.database.Driver!(DefaultPolicy).Driver.Statement.attrGet cannot deduce function from argument types !(ushort)(OCIStmt*, uint), candidates are: + + private T attrGet(T)(ub4 attribute) { + return attrGet!T(stmt, error, attribute); + } + + private void attrGet(T)(ub4 attribute, ref T value) { + return attrGet(stmt, error, attribute, value); + } + + private void attrSet(T)(ub4 attribute, T value) { + return attrSet(stmt, error, attribute, value); + } + + static T attrGet(T)(OCIStmt* stmt, OCIError* error, ub4 attribute) { + T value; + ub4 sz = T.sizeof; + attrGet!T(stmt, error, attribute, value); + return value; + } + + static void attrGet(T)(OCIStmt* stmt, OCIError* error, + ub4 attribute, ref T value) { + return attrGet(stmt, OCI_HTYPE_STMT, error, attribute, value); + } + + static void attrGet(T)(void* handle, ub4 handleType, + OCIError* error, ub4 attribute, ref T value) { + ub4 sz = T.sizeof; + check("OCIAttrGet", OCIAttrGet(handle, OCI_HTYPE_STMT, + &value, &sz, attribute, error)); + } - void bind(int n, Date d) { - throw new DatabaseException("Date input binding not yet implemented"); - } - - void reset() {} - //: Error: template std.database.oracle.database.Driver!(DefaultPolicy).Driver.Statement.attrGet cannot deduce function from argument types !(ushort)(OCIStmt*, uint), candidates are: - - private T attrGet(T)(ub4 attribute) {return attrGet!T(stmt, error, attribute);} - private void attrGet(T)(ub4 attribute, ref T value) { return attrGet(stmt, error, attribute, value);} - private void attrSet(T)(ub4 attribute, T value) {return attrSet(stmt, error, attribute, value);} - - static T attrGet(T)(OCIStmt *stmt, OCIError *error, ub4 attribute) { - T value; - ub4 sz = T.sizeof; - attrGet!T(stmt, error, attribute, value); - return value; - } - - static void attrGet(T)(OCIStmt *stmt, OCIError *error, ub4 attribute, ref T value) { - return attrGet(stmt, OCI_HTYPE_STMT, error, attribute, value); - } - - static void attrGet(T)(void* handle, ub4 handleType, OCIError* error, ub4 attribute, ref T value) { - ub4 sz = T.sizeof; - check("OCIAttrGet", OCIAttrGet( - handle, - OCI_HTYPE_STMT, - &value, - &sz, - attribute, - error)); - } - - } - - struct Result { - Statement *stmt; - Allocator *allocator; - OCIError *error; - int columns; - Array!Describe describe; - Array!Bind bind; - ub4 rowArraySize; - sword status; - - this(Statement* stmt_, int rowArraySize_) { - stmt = stmt_; - rowArraySize = rowArraySize_; - allocator = &stmt.con.db.allocator; - error = stmt.error; - columns = stmt_.attrGet!ub4(OCI_ATTR_PARAM_COUNT); - build_describe(); - build_bind(); - } - - ~this() { - foreach(ref b; bind) { - if (b.dtor) b.dtor(b.data); - allocator.deallocate(b.data); - allocator.deallocate(b.ind); - allocator.deallocate(b.length); - } - } + } - void build_describe() { - - describe.reserve(columns); - for(int i = 0; i < columns; ++i) { - describe ~= Describe(); - auto d = &describe.back(); - - OCIParam *col; - check("OCIParamGet",OCIParamGet( - stmt.stmt, - OCI_HTYPE_STMT, - error, - cast(void**) &col, - i+1)); - - check("OCIAttrGet", OCIAttrGet( - col, - OCI_DTYPE_PARAM, - &d.ora_name, - &d.name_len, - OCI_ATTR_NAME, - error)); - - check("OCIAttrGet", OCIAttrGet( - col, - OCI_DTYPE_PARAM, - &d.oType, - null, - OCI_ATTR_DATA_TYPE, - error)); - - check("OCIAttrGet", OCIAttrGet( - col, - OCI_DTYPE_PARAM, - &d.size, - null, - OCI_ATTR_DATA_SIZE, - error)); - - d.name = to!string(cast(char*)(d.ora_name)[0..d.name_len]); - info("describe: name: ", d.name, ", type: ", d.oType, ", size: ", d.size); - } + struct Result { + Statement* stmt; + Allocator* allocator; + OCIError* error; + int columns; + Array!Describe describe; + Array!Bind bind; + ub4 rowArraySize; + sword status; + + this(Statement* stmt_, int rowArraySize_) { + stmt = stmt_; + rowArraySize = rowArraySize_; + allocator = &stmt.con.db.allocator; + error = stmt.error; + columns = stmt_.attrGet!ub4(OCI_ATTR_PARAM_COUNT); + build_describe(); + build_bind(); + } + + ~this() { + foreach (ref b; bind) { + if (b.dtor) + b.dtor(b.data); + allocator.deallocate(b.data); + allocator.deallocate(b.ind); + allocator.deallocate(b.length); + } + } + + void build_describe() { + + describe.reserve(columns); + for (int i = 0; i < columns; ++i) { + describe ~= Describe(); + auto d = &describe.back(); + + OCIParam* col; + check("OCIParamGet", OCIParamGet(stmt.stmt, + OCI_HTYPE_STMT, error, cast(void**)&col, i + 1)); + + check("OCIAttrGet", OCIAttrGet(col, + OCI_DTYPE_PARAM, &d.ora_name, &d.name_len, OCI_ATTR_NAME, + error)); + + check("OCIAttrGet", OCIAttrGet(col, + OCI_DTYPE_PARAM, &d.oType, null, OCI_ATTR_DATA_TYPE, + error)); + + check("OCIAttrGet", OCIAttrGet(col, + OCI_DTYPE_PARAM, &d.size, null, OCI_ATTR_DATA_SIZE, + error)); + + d.name = to!string(cast(char*)(d.ora_name)[0 .. d.name_len]); + info("describe: name: ", d.name, ", type: ", + d.oType, ", size: ", d.size); + } + + } + + void build_bind() { + auto allocator = Allocator(); + + import core.memory : GC; // needed? + + bind.reserve(columns); + + for (int i = 0; i < columns; ++i) { + bind ~= Bind(); + auto d = &describe[i]; + auto b = &bind.back(); + + BindContext ctx; + ctx.describe = d; + ctx.bind = b; + ctx.allocator = &allocator; + ctx.rowArraySize = rowArraySize; + + BindInfo.bind(ctx); + + info("bind: type: ", b.oType, ", allocSize:", b.allocSize); + + check("OCIDefineByPos", OCIDefineByPos(stmt.stmt, + &d.define, error, cast(ub4) i + 1, b.data.ptr, + b.allocSize, b.oType, cast(dvoid*) b.ind.ptr, // check + cast(ub2*) b.length.ptr, null, OCI_DEFAULT)); + + if (rowArraySize > 1) { + check("OCIDefineArrayOfStruct", + OCIDefineArrayOfStruct(d.define, error, + b.allocSize, sb2.sizeof, ub2.sizeof, 0)); + } + } + } + + int fetch() { + if (status == OCI_NO_DATA) + return 0; + + int rowsFetched; + + //info("OCIStmtFetch2"); + + status = OCIStmtFetch2(stmt.stmt, error, rowArraySize, + OCI_FETCH_NEXT, 0, OCI_DEFAULT); + + if (rowArraySize > 1) { + // clean up + ub4 value; + check("OCIAttrGet", OCIAttrGet(stmt.stmt, + OCI_HTYPE_STMT, &value, null, OCI_ATTR_ROWS_FETCHED, + error)); + rowsFetched = value; + } + else { + rowsFetched = (status == OCI_NO_DATA ? 0 : 1); + } + + if (status == OCI_SUCCESS) { + return rowsFetched; + } + else if (status == OCI_NO_DATA) { + //stmt.reset(); + return rowsFetched; + } + + throw new DatabaseException("fetch error"); // fix + //check("SQLFetch", SQL_HANDLE_STMT, stmt.data_.stmt, status); + //return 0; + } + + ubyte[] rawData(Cell* cell) { //TODO: fix + return null; + //auto ptr = cast(ubyte*) data(cell); + //return ptr[0..strlen(ptr)]; + } + + Variant getValue(Cell* cell) { + Variant value; + switch (cell.bind.oType) { + case SQLT_STR: { + import core.stdc.string : strlen; + + //checkType(cell.bind.oType, SQLT_STR); + auto ptr = cast(immutable char*) data(cell); + value = cast(string) ptr[0 .. strlen(ptr)]; // fix with length + } + break; + case SQLT_INT: + value = *(cast(int*) data(cell)); + break; + case SQLT_ODT: { + auto d = cast(OCIDate*) data(cell); + value = Date(d.OCIDateYYYY, d.OCIDateMM, d.OCIDateDD); + } + break; + default: + break; + + } + return value; //TODO: + } + + auto type(int col){ + return describe[i].oType; + } + + bool isNull(Cell* cell) { + return false; + } + + auto name(size_t idx) { + return describe[idx].name; + } + + private void* data(Cell* cell) { + return cell.bind.data.ptr + cell.bind.allocSize * cell.rowIdx; + } + + private static void checkType(ub2 a, ub2 b) { + if (a != b) + throw new DatabaseException("type mismatch"); + } - } + } - void build_bind() { - auto allocator = Allocator(); - - import core.memory : GC; // needed? - - bind.reserve(columns); - - for(int i = 0; i < columns; ++i) { - bind ~= Bind(); - auto d = &describe[i]; - auto b = &bind.back(); - - BindContext ctx; - ctx.describe = d; - ctx.bind = b; - ctx.allocator = &allocator; - ctx.rowArraySize = rowArraySize; - - BindInfo.bind(ctx); - - info("bind: type: ", b.oType, ", allocSize:", b.allocSize); - - check("OCIDefineByPos", OCIDefineByPos( - stmt.stmt, - &d.define, - error, - cast(ub4) i+1, - b.data.ptr, - b.allocSize, - b.oType, - cast(dvoid *) b.ind.ptr, // check - cast(ub2*) b.length.ptr, - null, - OCI_DEFAULT)); - - if (rowArraySize > 1) { - check("OCIDefineArrayOfStruct", OCIDefineArrayOfStruct( - d.define, - error, - b.allocSize, - sb2.sizeof, - ub2.sizeof, - 0)); } - } - } - - int fetch() { - if (status == OCI_NO_DATA) return 0; - - int rowsFetched; - - //info("OCIStmtFetch2"); - - status = OCIStmtFetch2( - stmt.stmt, - error, - rowArraySize, - OCI_FETCH_NEXT, - 0, - OCI_DEFAULT); - - if (rowArraySize > 1) { - // clean up - ub4 value; - check("OCIAttrGet",OCIAttrGet( - stmt.stmt, - OCI_HTYPE_STMT, - &value, - null, - OCI_ATTR_ROWS_FETCHED, - error)); - rowsFetched = value; - } else { - rowsFetched = (status == OCI_NO_DATA ? 0 : 1); - } - - if (status == OCI_SUCCESS) { - return rowsFetched; - } else if (status == OCI_NO_DATA) { - //stmt.reset(); - return rowsFetched; - } - - throw new DatabaseException("fetch error"); // fix - //check("SQLFetch", SQL_HANDLE_STMT, stmt.data_.stmt, status); - //return 0; - } - - auto get(X:string)(Cell* cell) { - import core.stdc.string: strlen; - checkType(cell.bind.oType, SQLT_STR); - auto ptr = cast(immutable char*) data(cell); - return cast(string) ptr[0..strlen(ptr)]; // fix with length - } - - auto name(size_t idx) { - return describe[idx].name; - } - - auto get(X:int)(Cell* cell) { - checkType(cell.bind.oType, SQLT_INT); - return *(cast(int*) data(cell)); - } - - auto get(X:Date)(Cell* cell) { - //return Date(2016,1,1); // fix - checkType(cell.bind.oType, SQLT_ODT); - auto d = cast(OCIDate*) data(cell); - return Date(d.OCIDateYYYY,d.OCIDateMM,d.OCIDateDD); - } - - private void* data(Cell* cell) { - return cell.bind.data.ptr + cell.bind.allocSize * cell.rowIdx; - } - - private static void checkType(ub2 a, ub2 b) { - if (a != b) throw new DatabaseException("type mismatch"); - } - -} - -} - - diff --git a/src/std/database/poly/database.d b/src/std/database/poly/database.d index 4395d39..8a567fc 100644 --- a/src/std/database/poly/database.d +++ b/src/std/database/poly/database.d @@ -21,7 +21,7 @@ import std.database.variant; import std.meta; -alias Database(T) = BasicDatabase!(Driver!T,T); +alias Database(T) = BasicDatabase!(Driver!T, T); struct DefaultPolicy { alias Allocator = MyMallocator; @@ -32,13 +32,13 @@ auto registerDatabase(DB)(string name) { PolyDB.register!DB(name); } -auto createDatabase()(string defaultURI="") { - return Database!DefaultPolicy(defaultURI); +auto createDatabase()(string defaultURI = "") { + return Database!DefaultPolicy(defaultURI); } struct Driver(Policy) { alias Allocator = Policy.Allocator; - alias Cell = BasicCell!(Driver,Policy); + alias Cell = BasicCell!(Driver, Policy); alias BindArgs = Array!Variant; // revise using allocator make @@ -52,28 +52,30 @@ struct Driver(Policy) { //return s; // cast apparently necessary or get array cast misallignment } - static auto destruct(T,A)(ref A allocator, void[] data) { + static auto destruct(T, A)(ref A allocator, void[] data) { import core.memory : GC; // needed? .destroy(*(cast(T*) data.ptr)); allocator.deallocate(data); GC.removeRange(data.ptr); } - static auto toTypedPtr(T)(void[] data) {return (cast(T[]) data).ptr;} + static auto toTypedPtr(T)(void[] data) { + return (cast(T[]) data).ptr; + } static struct DBVtable { - void[] function(string defaultURI) create; + void[]function(string defaultURI) create; void function(void[]) destroy; const(Feature[]) function(void[] db) features; // how to make parameter ref const(FeatureArray) } static struct ConVTable { - void[] function(void[], Source source) create; + void[]function(void[], Source source) create; void function(void[]) destroy; } static struct StmtVTable { - void[] function(void[], string sql) create; + void[]function(void[], string sql) create; void function(void[]) destroy; void function(void[] stmt) prepare; void function(void[] stmt) query; @@ -92,19 +94,20 @@ struct Driver(Policy) { alias Database = Driver.Database; alias Connection = Driver.Connection; - static void[] create(string uri) {return construct!Database(alloc, uri);} - static void destroy(void[] db) {destruct!Database(alloc, db);} + static void[] create(string uri) { + return construct!Database(alloc, uri); + } + + static void destroy(void[] db) { + destruct!Database(alloc, db); + } static const(Feature[]) features(void[] db) { //return DB.features; return (cast(Database*) db.ptr).features; } - static DBVtable vtable = { - &create, - &destroy, - &features, - }; + static DBVtable vtable = {&create, &destroy, &features,}; } static struct ConGen(Driver) { @@ -116,15 +119,13 @@ struct Driver(Policy) { return construct!Connection(alloc, toTypedPtr!Database(db), source); } - static void destroy(void[] con) {destruct!Connection(alloc, con);} + static void destroy(void[] con) { + destruct!Connection(alloc, con); + } - static ConVTable vtable = { - &create, - &destroy, - }; + static ConVTable vtable = {&create, &destroy,}; } - static struct StmtGen(Driver) { private static Allocator alloc = MyMallocator(); alias Connection = Driver.Connection; @@ -132,25 +133,26 @@ struct Driver(Policy) { // doesn't work outside of scope - static void constructQuery(int i=0, A...)(Statement* stmt, A a) { + static void constructQuery(int i = 0, A...)(Statement* stmt, A a) { // list of primitive types somewhere ? // dstring causes problems //alias Types = AliasSeq!(byte, ubyte, string, char, dchar, int, uint, long, ulong); alias Types = AliasSeq!(int, string, Date); static void call(int i, T, A...)(Statement* stmt, T v, A a) { - constructQuery!(i+1)(stmt, a[0..i], v, a[(i+1)..$]); + constructQuery!(i + 1)(stmt, a[0 .. i], v, a[(i + 1) .. $]); } static if (i == a.length) { stmt.query(a); - } else { + } + else { //log("type: ", a[i].type); - foreach(T; Types) { + foreach (T; Types) { //log("--TYPE: ", typeid(T)); //if (a[i].type == typeid(T)) if (a[i].convertsTo!T) { - call!i(stmt,a[i].get!T,a); + call!i(stmt, a[i].get!T, a); return; } } @@ -158,26 +160,44 @@ struct Driver(Policy) { } } - static auto create(void[] con, string sql) { return construct!Statement(alloc, toTypedPtr!Connection(con), sql); } - static void destroy(void[] stmt) {destruct!Statement(alloc, stmt);} - static void prepare(void[] stmt) {toTypedPtr!Statement(stmt).prepare();} - static void query(void[] stmt) {toTypedPtr!Statement(stmt).query();} + static void destroy(void[] stmt) { + destruct!Statement(alloc, stmt); + } + static void prepare(void[] stmt) { + toTypedPtr!Statement(stmt).prepare(); + } - static void callVariadic(alias F,S,A...) (ref S s, A a) { + static void query(void[] stmt) { + toTypedPtr!Statement(stmt).query(); + } + + static void callVariadic(alias F, S, A...)(ref S s, A a) { //initial args come last in this call switch (s.length) { - case 0: break; - case 1: F(a,s[0]); break; - case 2: F(a,s[0],s[1]); break; - case 3: F(a,s[0],s[1],s[2]); break; - case 4: F(a,s[0],s[1],s[2],s[3]); break; - case 5: F(a,s[0],s[1],s[2],s[3],s[4]); break; - default: throw new Exception("too many args"); + case 0: + break; + case 1: + F(a, s[0]); + break; + case 2: + F(a, s[0], s[1]); + break; + case 3: + F(a, s[0], s[1], s[2]); + break; + case 4: + F(a, s[0], s[1], s[2], s[3]); + break; + case 5: + F(a, s[0], s[1], s[2], s[3], s[4]); + break; + default: + throw new Exception("too many args"); } } @@ -188,229 +208,251 @@ struct Driver(Policy) { //void F(A...) (A a) {stmt.query(a);} switch (a.length) { - case 0: break; - case 1: constructQuery(s,a[0]); break; - case 2: constructQuery(s,a[0],a[1]); break; - case 3: constructQuery(s,a[0],a[1],a[2]); break; - default: throw new DatabaseException("too many args"); + case 0: + break; + case 1: + constructQuery(s, a[0]); + break; + case 2: + constructQuery(s, a[0], a[1]); + break; + case 3: + constructQuery(s, a[0], a[1], a[2]); + break; + default: + throw new DatabaseException("too many args"); } } static StmtVTable vtable = { - &create, - &destroy, - &prepare, - &query, - &variadicQuery, - }; - } - - //static struct QueryGen(Driver, T...) // but you don't have driver and args at same time + &create, &destroy, &prepare, &query, &variadicQuery,}; + } + //static struct QueryGen(Driver, T...) // but you don't have driver and args at same time - struct Database { - private static Array!DriverInfo drivers; - private string URI; + struct Database { + private static Array!DriverInfo drivers; + private string URI; - private DriverInfo* driver; - private void[] db; + private DriverInfo* driver; + private void[] db; - alias queryVariableType = QueryVariableType.Dollar; + alias queryVariableType = QueryVariableType.Dollar; - static void register(DB) (string name) { - alias Driver = DB.Driver; - DriverInfo driver; - driver.name = name; - driver.dbVtable = DBGen!Driver.vtable; - driver.conVtable = ConGen!Driver.vtable; - driver.stmtVtable = StmtGen!Driver.vtable; + static void register(DB)(string name) { + alias Driver = DB.Driver; + DriverInfo driver; + driver.name = name; + driver.dbVtable = DBGen!Driver.vtable; + driver.conVtable = ConGen!Driver.vtable; + driver.stmtVtable = StmtGen!Driver.vtable; - drivers ~= driver; + drivers ~= driver; - info( - "poly register: ", - "name: ", name, ", " - "type: ", typeid(DB), - "index: ", drivers.length); - } + info("poly register: ", "name: ", name, ", " "type: ", + typeid(DB), "index: ", drivers.length); + } - void setDatabase(string name) { - driver = findDatabase(name); - db = driver.dbVtable.create(URI); - } + void setDatabase(string name) { + driver = findDatabase(name); + db = driver.dbVtable.create(URI); + } - bool isDatabaseSet() {return driver && db;} + bool isDatabaseSet() { + return driver && db; + } - //Allocator allocator; + //Allocator allocator; - const(Feature[]) features() { - // strange feature behavior to get it working - if (driver) return driver.dbVtable.features(db); - static const FeatureArray f = [ - //Feature.InputBinding, + const(Feature[]) features() { + // strange feature behavior to get it working + if (driver) + return driver.dbVtable.features(db); + static const FeatureArray f = [//Feature.InputBinding, //Feature.DateBinding, //Feature.ConnectionPool, //Feature.OutputArrayBinding, ]; - return f; - } + return f; + } - this(string URI_) { - //allocator = Allocator(); - URI = URI_; - } + this(string URI_) { + //allocator = Allocator(); + URI = URI_; + } - ~this() { - if (isDatabaseSet) { - if (!db.ptr) throw new DatabaseException("db not set"); - driver.dbVtable.destroy(db); + ~this() { + if (isDatabaseSet) { + if (!db.ptr) + throw new DatabaseException("db not set"); + driver.dbVtable.destroy(db); + } } - } - private static DriverInfo* findDatabase(string name) { - //import std.algorithm; - //import std.range.primitives: empty; - foreach(ref d; drivers) { - if (d.name == name) return &d; + private static DriverInfo* findDatabase(string name) { + //import std.algorithm; + //import std.range.primitives: empty; + foreach (ref d; drivers) { + if (d.name == name) + return &d; + } + throw new DatabaseException("can't find database with name: " ~ name); } - throw new DatabaseException("can't find database with name: " ~ name); } - } - struct Connection { - Database* database; - Source source; - void[] con; + struct Connection { + Database* database; + Source source; + void[] con; + + auto driver() { + return database.driver; + } + + auto db() { + return database.db; + } - auto driver() {return database.driver;} - auto db() {return database.db;} + this(Database* database_, Source source_) { + database = database_; + source = source_; + if (!database.isDatabaseSet) + throw new DatabaseException("no database set"); + con = driver.conVtable.create(db, source); + } - this(Database* database_, Source source_) { - database = database_; - source = source_; - if (!database.isDatabaseSet) throw new DatabaseException("no database set"); - con = driver.conVtable.create(db, source); + ~this() { + driver.conVtable.destroy(con); + } } - ~this() { - driver.conVtable.destroy(con); + struct Describe { } - } - struct Describe { - } + struct Bind { + ValueType type; + } - struct Bind { - ValueType type; - } + struct Statement { + Connection* connection; + string sql; + Allocator* allocator; + void[] stmt; - struct Statement { - Connection *connection; - string sql; - Allocator *allocator; - void[] stmt; + auto driver() { + return connection.driver; + } - auto driver() {return connection.driver;} - auto con() {return connection.con;} + auto con() { + return connection.con; + } - this(Connection* connection_, string sql_) { - connection = connection_; - sql = sql_; - //allocator = &con.db.allocator; - stmt = driver.stmtVtable.create(con, sql); - } + this(Connection* connection_, string sql_) { + connection = connection_; + sql = sql_; + //allocator = &con.db.allocator; + stmt = driver.stmtVtable.create(con, sql); + } - ~this() { - driver.stmtVtable.destroy(stmt); - } + ~this() { + driver.stmtVtable.destroy(stmt); + } - void exec() { - //check("SQLExecDirect", SQLExecDirect(data_.stmt,cast(SQLCHAR*) toStringz(data_.sql), SQL_NTS)); - } + void exec() { + //check("SQLExecDirect", SQLExecDirect(data_.stmt,cast(SQLCHAR*) toStringz(data_.sql), SQL_NTS)); + } - void prepare() { - driver.stmtVtable.prepare(stmt); - } + void prepare() { + driver.stmtVtable.prepare(stmt); + } - void query() { - driver.stmtVtable.query(stmt); - } + void query() { + driver.stmtVtable.query(stmt); + } - void query(A...) (ref A args) { - import std.range; - //auto a = BindArgs(only(args)); - auto a = BindArgs(); - a.reserve(args.length); - foreach(arg; args) a ~= Variant(arg); - driver.stmtVtable.variadicQuery(stmt, a); - } + void query(A...)(ref A args) { + import std.range; - bool hasRows() {return true;} // fix + //auto a = BindArgs(only(args)); + auto a = BindArgs(); + a.reserve(args.length); + foreach (arg; args) + a ~= Variant(arg); + driver.stmtVtable.variadicQuery(stmt, a); + } + bool hasRows() { + return true; + } // fix - void bind(int n, int value) { - } + void bind(int n, int value) { + } - void bind(int n, const char[] value){ - } + void bind(int n, const char[] value) { + } + + void bind(int n, Date d) { + } - void bind(int n, Date d) { + void reset() { + } } - void reset() {} - } + struct Result { + Statement* stmt; + Allocator* allocator; + Array!Bind bind; - struct Result { - Statement *stmt; - Allocator *allocator; - Array!Bind bind; + this(Statement* stmt_, int rowArraySize_) { + stmt = stmt_; + //allocator = &stmt.con.db.allocator; - this(Statement* stmt_, int rowArraySize_) { - stmt = stmt_; - //allocator = &stmt.con.db.allocator; + //build_describe(); + //build_bind(); + } - //build_describe(); - //build_bind(); - } + ~this() { + //foreach(ref b; bind) { + //} + } - ~this() { - //foreach(ref b; bind) { - //} - } + void build_describe() { + } - void build_describe() { - } + void build_bind() { + } - void build_bind() { - } + int columns() { + return 0; + } - int columns() {return 0;} + //bool hasResult() {return columns != 0;} + bool hasResult() { + return 0; + } - //bool hasResult() {return columns != 0;} - bool hasResult() {return 0;} + int fetch() { + return 0; + } - int fetch() { - return 0; - } + auto type(int col){return 0;} - auto name(size_t idx) { - return "-name-"; - } + ubyte[] rawData(Cell* cell) { + return null; + } - auto get(X:string)(Cell* cell) { - return "abc"; - } + Variant getValue(Cell* cell) { + return Variant(); //TODO: + } - auto get(X:int)(Cell* cell) { - return 0; - } + bool isNull(Cell* cell) { + return false; + } - auto get(X:Date)(Cell* cell) { - return Date(2016,1,1); // fix + auto name(size_t idx) { + return "-name-"; + } } } - -} - diff --git a/src/std/database/postgres/bindings.d b/src/std/database/postgres/bindings.d index f6a11b5..2c84c72 100644 --- a/src/std/database/postgres/bindings.d +++ b/src/std/database/postgres/bindings.d @@ -4,37 +4,104 @@ import core.stdc.config; extern(System) { // from server/catalog/pg_type.h - enum int BOOLOID = 16; - enum int BYTEAOI = 17; - enum int CHAROID = 18; - enum int NAMEOID = 19; - enum int INT8OID = 20; - enum int INT2OID = 21; - enum int INT2VECTOROID = 22; - enum int INT4OID = 23; - enum int REGPROCOID = 24; - enum int TEXTOID = 25; - enum int OIDOID = 26; - enum int TIDOID = 27; - enum int XIDOID = 28; - enum int CIDOID = 29; - enum int OIDVECTOROID = 30; - enum int VARCHAROID = 1043; - enum int DATEOID = 1082; - + enum BOOLOID = 16; + enum BYTEAOID = 17; + enum CHAROID = 18; + enum NAMEOID = 19; + enum INT8OID = 20; + enum INT2OID = 21; + enum INT2VECTOROID = 22; + enum INT4OID = 23; + enum REGPROCOID = 24; + enum TEXTOID = 25; + enum OIDOID = 26; + enum TIDOID = 27; + enum XIDOID = 28; + enum CIDOID = 29; + enum OIDVECTOROID = 30; + enum JSONOID = 114; + enum XMLOID = 142; + enum PGNODETREEOID = 194; + enum POINTOID = 600; + enum LSEGOID = 601; + enum PATHOID = 602; + enum BOXOID = 603; + enum POLYGONOID = 604; + enum LINEOID = 628; + enum FLOAT4OID = 700; + enum FLOAT8OID = 701; + enum ABSTIMEOID = 702; + enum RELTIMEOID = 703; + enum TINTERVALOID = 704; + enum UNKNOWNOID = 705; + enum CIRCLEOID = 718; + enum CASHOID = 790; + enum MACADDROID = 829; + enum INETOID = 869; + enum CIDROID = 650; + enum INT2ARRAYOID = 1005; + enum INT4ARRAYOID = 1007; + enum TEXTARRAYOID = 1009; + enum OIDARRAYOID = 1028; + enum FLOAT4ARRAYOID = 1021; + enum ACLITEMOID = 1033; + enum CSTRINGARRAYOID = 1263; + enum BPCHAROID = 1042; + enum VARCHAROID = 1043; + enum DATEOID = 1082; + enum TIMEOID = 1083; + enum TIMESTAMPOID = 1114; + enum TIMESTAMPTZOID = 1184; + enum INTERVALOID = 1186; + enum TIMETZOID = 1266; + enum BITOID = 1560; + enum VARBITOID = 1562; + enum NUMERICOID = 1700; + enum REFCURSOROID = 1790; + enum REGPROCEDUREOID = 2202; + enum REGOPEROID = 2203; + enum REGOPERATOROID = 2204; + enum REGCLASSOID = 2205; + enum REGTYPEOID = 2206; + enum REGTYPEARRAYOID = 2211; + enum UUIDOID = 2951; + enum LSNOID = 3220; + enum TSVECTOROID = 3614; + enum GTSVECTOROID = 3642; + enum TSQUERYOID = 3615; + enum REGCONFIGOID = 3734; + enum REGDICTIONARYOID = 3769; + enum JSONBOID = 3802; + enum INT4RANGEOID = 3904; + enum RECORDOID = 2249; + enum RECORDARRAYOID = 2287; + enum CSTRINGOID = 2275; + enum ANYOID = 2276; + enum ANYARRAYOID = 2277; + enum VOIDOID = 2278; + enum TRIGGEROID = 2279; + enum EVTTRIGGEROID = 3838; + enum LANGUAGE_HANDLEROID = 2280; + enum INTERNALOID = 2281; + enum OPAQUEOID = 2282; + enum ANYELEMENTOID = 2283; + enum ANYNONARRAYOID = 2776; + enum ANYENUMOID = 3500; + enum FDW_HANDLEROID = 3115; + enum ANYRANGEOID = 3831; enum PGRES_EMPTY_QUERY = 0; - enum int CONNECTION_OK = 0; - enum int PGRES_COMMAND_OK = 1; - enum int PGRES_TUPLES_OK = 2; - enum int PGRES_COPY_OUT = 3; - enum int PGRES_COPY_IN = 4; - enum int PGRES_BAD_RESPONSE = 5; - enum int PGRES_NONFATAL_ERROR = 6; - enum int PGRES_FATAL_ERROR = 7; - enum int PGRES_COPY_BOTH = 8; - enum int PGRES_SINGLE_TUPLE = 9; + enum CONNECTION_OK = 0; + enum PGRES_COMMAND_OK = 1; + enum PGRES_TUPLES_OK = 2; + enum PGRES_COPY_OUT = 3; + enum PGRES_COPY_IN = 4; + enum PGRES_BAD_RESPONSE = 5; + enum PGRES_NONFATAL_ERROR = 6; + enum PGRES_FATAL_ERROR = 7; + enum PGRES_COPY_BOTH = 8; + enum PGRES_SINGLE_TUPLE = 9; alias ExecStatusType=int; alias Oid=uint; @@ -51,6 +118,7 @@ extern(System) { struct PGresult {}; int PQsendQuery(PGconn *conn, const char *command); + PGresult *PQgetResult(PGconn *conn); int PQsetSingleRowMode(PGconn *conn); @@ -58,6 +126,8 @@ extern(System) { ExecStatusType PQresultStatus(const PGresult *res); char *PQresStatus(ExecStatusType status); + PGresult * PQexec(PGconn *conn, const char *command); + PGresult *PQexecParams( PGconn *conn, const char *command, @@ -120,7 +190,7 @@ extern(System) { // numeric - enum int DECSIZE = 30; + enum DECSIZE = 30; alias ubyte NumericDigit; @@ -201,5 +271,39 @@ extern(System) { PGnotify *PQnotifies(PGconn *conn); void PQfreemem(void *ptr); + + + enum TYPTYPE_BASE = 'b'; /* base type (ordinary scalar type) */ + enum TYPTYPE_COMPOSITE = 'c' ;/* composite (e.g., table's rowtype) */ + enum TYPTYPE_DOMAIN = 'd' ;/* domain over another type */ + enum TYPTYPE_ENUM ='e' ;/* enumerated type */ + enum TYPTYPE_PSEUDO = 'p'; /* pseudo-type */ + enum TYPTYPE_RANGE ='r'; /* range type */ + + enum TYPCATEGORY_INVALID ='\0'; /* not an allowed category */ + enum TYPCATEGORY_ARRAY ='A'; + enum TYPCATEGORY_BOOLEAN ='B'; + enum TYPCATEGORY_COMPOSITE= 'C'; + enum TYPCATEGORY_DATETIME ='D'; + enum TYPCATEGORY_ENUM ='E'; + enum TYPCATEGORY_GEOMETRIC= 'G'; + enum TYPCATEGORY_NETWORK ='I' ;/* think INET */ + enum TYPCATEGORY_NUMERIC ='N'; + enum TYPCATEGORY_PSEUDOTYPE ='P'; + enum TYPCATEGORY_RANGE ='R'; + enum TYPCATEGORY_STRING = 'S'; + enum TYPCATEGORY_TIMESPAN= 'T'; + enum TYPCATEGORY_USER ='U'; + enum TYPCATEGORY_BITSTRING= 'V' ; /* er ... "varbit"? */ + enum TYPCATEGORY_UNKNOWN ='X'; + + bool IsPolymorphicType(int typid) + { + return ((typid) == ANYELEMENTOID || + (typid) == ANYARRAYOID || + (typid) == ANYNONARRAYOID || + (typid) == ANYENUMOID || + (typid) == ANYRANGEOID); + } } diff --git a/src/std/database/postgres/database.d b/src/std/database/postgres/database.d index fb038f3..3650413 100644 --- a/src/std/database/postgres/database.d +++ b/src/std/database/postgres/database.d @@ -17,20 +17,21 @@ import std.database.front; import std.stdio; import std.typecons; import std.datetime; +import std.conv; struct DefaultPolicy { alias Allocator = MyMallocator; static const bool nonblocking = false; } -alias Database(T) = BasicDatabase!(Driver!T,T); +alias Database(T) = BasicDatabase!(Driver!T, T); -auto createDatabase()(string defaultURI="") { - return Database!DefaultPolicy(defaultURI); +auto createDatabase()(string defaultURI = "") { + return Database!DefaultPolicy(defaultURI); } -auto createDatabase(T)(string defaultURI="") { - return Database!T(defaultURI); +auto createDatabase(T)(string defaultURI = "") { + return Database!T(defaultURI); } /* @@ -39,41 +40,42 @@ auto createDatabase(T)(string defaultURI="") { } */ - -void error()(PGconn *con, string msg) { +void error()(PGconn* con, string msg) { import std.conv; + auto s = msg ~ to!string(PQerrorMessage(con)); - throw new DatabaseException(msg); + throw new DatabaseException(s); } -void error()(PGconn *con, string msg, int result) { +void error()(PGconn* con, string msg, int result) { import std.conv; + auto s = "error:" ~ msg ~ ": " ~ to!string(result) ~ ": " ~ to!string(PQerrorMessage(con)); - throw new DatabaseException(msg); + throw new DatabaseException(s); } -int check()(PGconn *con, string msg, int result) { +int check()(PGconn* con, string msg, int result) { info(msg, ": ", result); - if (result != 1) error(con, msg, result); + if (result != 1) + error(con, msg, result); return result; } -int checkForZero()(PGconn *con, string msg, int result) { +int checkForZero()(PGconn* con, string msg, int result) { info(msg, ": ", result); - if (result != 0) error(con, msg, result); + if (result != 0) + error(con, msg, result); return result; } struct Driver(Policy) { alias Allocator = Policy.Allocator; - alias Cell = BasicCell!(Driver,Policy); + alias Cell = BasicCell!(Driver, Policy); struct Database { static const auto queryVariableType = QueryVariableType.Dollar; - static const FeatureArray features = [ - Feature.InputBinding, - Feature.DateBinding, + static const FeatureArray features = [Feature.InputBinding, Feature.DateBinding, //Feature.ConnectionPool, ]; @@ -90,19 +92,31 @@ struct Driver(Policy) { struct Connection { Database* db; Source source; - PGconn *con; + PGconn* con; this(Database* db_, Source source_) { db = db_; source = source_; string conninfo; - conninfo ~= "dbname=" ~ source.database; + if(source.host.length > 0) + { + conninfo ~= "host=" ~ source.host; + if(source.port > 0) conninfo ~= " port=" ~ to!string(source.port); + } + else + { + conninfo ~= "host=" ~ source.server; + } + conninfo ~= " dbname=" ~ source.database; + if(source.username.length > 0) conninfo ~= " user=" ~ source.username; + if(source.password.length > 0) conninfo ~= " password=" ~ source.password; + trace("link string is : ", conninfo); con = PQconnectdb(toStringz(conninfo)); - if (PQstatus(con) != CONNECTION_OK) error(con, "login error"); + if (PQstatus(con) != CONNECTION_OK) + error(con, "login error"); } - ~this() { PQfinish(con); } @@ -111,23 +125,24 @@ struct Driver(Policy) { return PQsocket(con); } - void* handle() {return con;} + void* handle() { + return con; + } } - struct Statement { Connection* connection; string sql; - Allocator *allocator; - PGconn *con; + Allocator* allocator; + PGconn* con; string name; - PGresult *prepareRes; - PGresult *res; + PGresult* prepareRes; + PGresult* res; - Array!(char*) bindValue; + /* Array!(char*) bindValue; Array!(Oid) bindType; Array!(int) bindLength; - Array!(int) bindFormat; + Array!(int) bindFormat;*/ this(Connection* connection_, string sql_) { connection = connection_; @@ -138,11 +153,11 @@ struct Driver(Policy) { } ~this() { - for(int i = 0; i != bindValue.length; ++i) { +/* for (int i = 0; i != bindValue.length; ++i) { auto ptr = bindValue[i]; auto length = bindLength[i]; - allocator.deallocate(ptr[0..length]); - } + allocator.deallocate(ptr[0 .. length]); + }*/ } void bind(int n, int value) { @@ -151,32 +166,20 @@ struct Driver(Policy) { void bind(int n, const char[] value) { } - void query() { import std.conv; info("query sql: ", sql); - if (!prepareRes) prepare(); - auto n = bindValue.length; - int resultFormat = 1; - static if (Policy.nonblocking) { - checkForZero(con,"PQsetnonblocking", PQsetnonblocking(con, 1)); + checkForZero(con, "PQsetnonblocking", PQsetnonblocking(con, 1)); - check(con, "PQsendQueryPrepared", PQsendQueryPrepared( - con, - toStringz(name), - cast(int) n, - n ? cast(const char **) &bindValue[0] : null, - n ? cast(int*) &bindLength[0] : null, - n ? cast(int*) &bindFormat[0] : null, - resultFormat)); + check(con, "PQsendQueryPrepared", PQsendQuery(con,toStringz(sql))); do { Policy.Handler handler; - handler.addSocket(posixSocket()); + handler.addSocket(posixSocket()); /* auto s = PQconnectPoll(con); if (s == PGRES_POLLING_OK) { @@ -189,49 +192,36 @@ struct Driver(Policy) { handler.wait(); check(con, "PQconsumeInput", PQconsumeInput(con)); - PGnotify* notify; + PGnotify* notify; while ((notify = PQnotifies(con)) != null) { info("notify: ", to!string(notify.relname)); PQfreemem(notify); } - } while (PQisBusy(con) == 1); + } + while (PQisBusy(con) == 1); res = PQgetResult(con); - } else { - res = PQexecPrepared( - con, - toStringz(name), - cast(int) n, - n ? cast(const char **) &bindValue[0] : null, - n ? cast(int*) &bindLength[0] : null, - n ? cast(int*) &bindFormat[0] : null, - resultFormat); } - - /* - not using for now - if (!PQsendQuery(con, toStringz(sql))) throw error("PQsendQuery"); - res = PQgetResult(con); - */ - - // problem with PQsetSingleRowMode and prepared statements - // if (!PQsetSingleRowMode(con)) throw error("PQsetSingleRowMode"); + else { + res = PQexec(con,toStringz(sql)); + } } - void query(X...) (X args) { + void query(X...)(X args) { info("query sql: ", sql); // todo: stack allocation - bindValue.clear(); + /* bindValue.clear(); bindType.clear(); bindLength.clear(); bindFormat.clear(); - foreach (ref arg; args) bind(arg); - + foreach (ref arg; args) + bind(arg); + auto n = bindValue.length; /* @@ -246,27 +236,36 @@ struct Driver(Policy) { 0); */ - int resultForamt = 0; + /* int resultForamt = 0; - res = PQexecParams( - con, - toStringz(sql), - cast(int) n, - n ? cast(Oid*) &bindType[0] : null, - n ? cast(const char **) &bindValue[0] : null, - n ? cast(int*) &bindLength[0] : null, - n ? cast(int*) &bindFormat[0] : null, - resultForamt); + res = PQexecParams(con, toStringz(sql), cast(int) n, + n ? cast(Oid*)&bindType[0] : null, + n ? cast(const char**)&bindValue[0] : null, + n ? cast(int*)&bindLength[0] : null, + n ? cast(int*)&bindFormat[0] : null, resultForamt); + */ } - bool hasRows() {return true;} - - int binds() {return cast(int) bindValue.length;} // fix + bool hasRows() { + return rowCout() > 0; + } + + int rowCout() + { + int r = 0; + if(res !is null) r = PQntuples(res); + return r; + } +/* + int binds() { + return cast(int) bindValue.length; + } // fix void bind(string v) { - import core.stdc.string: strncpy; - void[] s = allocator.allocate(v.length+1); - char *p = cast(char*) s.ptr; + import core.stdc.string : strncpy; + + void[] s = allocator.allocate(v.length + 1); + char* p = cast(char*) s.ptr; strncpy(p, v.ptr, v.length); p[v.length] = 0; bindValue ~= p; @@ -277,8 +276,9 @@ struct Driver(Policy) { void bind(int v) { import std.bitmanip; + void[] s = allocator.allocate(int.sizeof); - *cast(int*) s.ptr = peek!(int, Endian.bigEndian)(cast(ubyte[]) (&v)[0..int.sizeof]); + *cast(int*) s.ptr = peek!(int, Endian.bigEndian)(cast(ubyte[])(&v)[0 .. int.sizeof]); bindValue ~= cast(char*) s.ptr; bindType ~= INT4OID; bindLength ~= cast(int) s.length; @@ -287,7 +287,8 @@ struct Driver(Policy) { void bind(Date v) { /* utility functions take 8 byte values but DATEOID is a 4 byte value */ - import std.bitmanip; + /* import std.bitmanip; + int[3] mdy; mdy[0] = v.month; mdy[1] = v.day; @@ -295,25 +296,21 @@ struct Driver(Policy) { long d; PGTYPESdate_mdyjul(&mdy[0], &d); void[] s = allocator.allocate(4); - *cast(int*) s.ptr = peek!(int, Endian.bigEndian)(cast(ubyte[]) (&d)[0..4]); + *cast(int*) s.ptr = peek!(int, Endian.bigEndian)(cast(ubyte[])(&d)[0 .. 4]); bindValue ~= cast(char*) s.ptr; bindType ~= DATEOID; bindLength ~= cast(int) s.length; bindFormat ~= 1; } - - void prepare() { +*/ + void prepare() { const Oid* paramTypes; - prepareRes = PQprepare( - con, - toStringz(name), - toStringz(sql), - 0, - paramTypes); + prepareRes = PQprepare(con, toStringz(name), toStringz(sql), 0, paramTypes); } auto error(string msg) { import std.conv; + string s; s ~= msg ~ ", " ~ to!string(PQerrorMessage(con)); return new DatabaseException(s); @@ -324,7 +321,8 @@ struct Driver(Policy) { private auto posixSocket() { int s = PQsocket(con); - if (s == -1) throw new DatabaseException("can't get socket"); + if (s == -1) + throw new DatabaseException("can't get socket"); return s; } @@ -335,8 +333,8 @@ struct Driver(Policy) { int fmt; string name; } - - + // use std.database.front.ValueType not std.traits.ValueType + alias ValueType = std.database.front.ValueType; struct Bind { ValueType type; int idx; @@ -347,14 +345,15 @@ struct Driver(Policy) { struct Result { Statement* stmt; - PGconn *con; - PGresult *res; + PGconn* con; + PGresult* res; int columns; Array!Describe describe; ExecStatusType status; int row; int rows; bool hasResult_; + bool fristFecth = true; // artifical bind array (for now) Array!Bind bind; @@ -371,7 +370,8 @@ struct Driver(Policy) { } ~this() { - if (res) close(); + if (res) + close(); } bool setup() { @@ -386,17 +386,21 @@ struct Driver(Policy) { if (status == PGRES_COMMAND_OK) { close(); return false; - } else if (status == PGRES_EMPTY_QUERY) { + } + else if (status == PGRES_EMPTY_QUERY) { close(); return false; - } else if (status == PGRES_TUPLES_OK) { + } + else if (status == PGRES_TUPLES_OK) { return true; - } else throw error(res,status); + } + else + throw error(res, status); } - void build_describe() { import std.conv; + // called after next() columns = PQnfields(res); for (int col = 0; col != columns; col++) { @@ -411,45 +415,98 @@ struct Driver(Policy) { void build_bind() { // artificial bind setup bind.reserve(columns); - for(int i = 0; i < columns; ++i) { + for (int i = 0; i < columns; ++i) { auto d = &describe[i]; bind ~= Bind(); auto b = &bind.back(); b.type = ValueType.String; b.idx = i; - switch(d.dbType) { - case VARCHAROID: b.type = ValueType.String; break; - case INT4OID: b.type = ValueType.Int; break; - case DATEOID: b.type = ValueType.Date; break; - default: throw new DatabaseException("unsupported type"); + switch (d.dbType) { + case CHAROID: + b.type = ValueType.Char; + break; + case TEXTOID: + case NAMEOID: + case VARCHAROID: + b.type = ValueType.String; + break; + case BOOLOID: + case INT4OID: + b.type = ValueType.Int; + break; + case INT2OID: + b.type = ValueType.Short; + break; + case INT8OID: + b.type = ValueType.Long; + break; + case FLOAT4OID: + b.type = ValueType.Float; + break; + case FLOAT8OID: + b.type = ValueType.Double; + break; + case DATEOID: + b.type = ValueType.Date; + break; + case TIMEOID: + b.type = ValueType.Time; + break; + case TIMESTAMPOID: + b.type = ValueType.DateTime; + break; + case BYTEAOID: + b.type = ValueType.Raw; + break; + default: + b.type = ValueType.UNKnown; + break; + // throw new DatabaseException("unsupported type"); } } } int fetch() { - return ++row != rows ? 1 :0; + int r = 0; + if(fristFecth) + r = rows > 0 ? 1 : 0; + else + { + ++row; + r = row != rows ? 1 : 0; + } + fristFecth = false; + return r; } bool singleRownext() { - if (res) PQclear(res); + if (res) + PQclear(res); res = PQgetResult(con); - if (!res) return false; + if (!res) + return false; status = PQresultStatus(res); if (status == PGRES_COMMAND_OK) { close(); return false; - } else if (status == PGRES_SINGLE_TUPLE) return true; + } + else if (status == PGRES_SINGLE_TUPLE) + return true; else if (status == PGRES_TUPLES_OK) { close(); return false; - } else throw error(status); + } + else + throw error(status); } void close() { - if (!res) throw error("couldn't close result: result was not open"); + if (!res) + throw error("couldn't close result: result was not open"); res = PQgetResult(con); - if (res) throw error("couldn't close result: was not finished"); + if (res) + throw error("couldn't close result: was not finished"); res = null; } @@ -459,16 +516,16 @@ struct Driver(Policy) { auto error(ExecStatusType status) { import std.conv; + string s = "result error: " ~ to!string(PQresStatus(status)); return new DatabaseException(s); } - auto error(PGresult *res, ExecStatusType status) { + auto error(PGresult* res, ExecStatusType status) { import std.conv; + const char* msg = PQresultErrorMessage(res); - string s = - "error: " ~ to!string(PQresStatus(status)) ~ - ", message:" ~ to!string(msg); + string s = "error: " ~ to!string(PQresStatus(status)) ~ ", message:" ~ to!string(msg); return new DatabaseException(s); } @@ -483,39 +540,178 @@ struct Driver(Policy) { return describe[idx].name; } - auto get(X:string)(Cell* cell) { - checkType(type(cell.bind.idx),VARCHAROID); - immutable char *ptr = cast(immutable char*) data(cell.bind.idx); - return cast(string) ptr[0..len(cell.bind.idx)]; + ubyte[] rawData(Cell* cell) { + auto ptr = cast(ubyte*) data(cell.bind.idx); + return ptr[0 .. len(cell.bind.idx)]; } - auto get(X:int)(Cell* cell) { - import std.bitmanip; - checkType(type(cell.bind.idx),INT4OID); - auto p = cast(ubyte*) data(cell.bind.idx); - return bigEndianToNative!int(p[0..int.sizeof]); + Variant getValue(Cell* cell) { + import std.conv; + + Variant value; + if (isNull(cell)) + return value; + + void* dt = data(cell.bind.idx); + int leng = len(cell.bind.idx); + immutable char* ptr = cast(immutable char*) dt; + string str = cast(string) ptr[0 .. leng]; + switch (type(cell.bind.idx)) { + case VARCHAROID: + case TEXTOID: + case NAMEOID:{ + value = str; + } + break; + case INT2OID: { + value = parse!short(str); + } + break; + case INT4OID: { + value = parse!int(str); + } + break; + case INT8OID: { + value = parse!long(str); + } + break; + case FLOAT4OID: { + value = parse!float(str); + } + break; + case FLOAT8OID: { + value = parse!double(str); + } + break; + case DATEOID: { + import std.format: formattedRead; + string input = str; + int year, month, day; + formattedRead(input, "%s-%s-%s", &year, &month, &day); + value = Date(year, month, day); + } + break; + case TIMESTAMPOID: { + import std.string; + + value = DateTime.fromISOExtString(str.translate([' ' : 'T']).split('.').front()); + } + break; + case TIMEOID: { + value = parseTimeoid(str); + } + break; + case BYTEAOID: { + value = byteaToUbytes(str); + } + break; + case CHAROID: { + value = cast(char)(leng > 0 ? str[0] : 0x00); + } + break; + case BOOLOID: { + + if (str == "true" || str == "t" || str == "1") + value = 1; + else if (str == "false" || str == "f" || str == "0") + value = 0; + else + value = parse!int(str); + } + break; + default: + break; + } + trace("value is : ",value ); + return value; } - auto get(X:Date)(Cell* cell) { - import std.bitmanip; - checkType(type(cell.bind.idx),DATEOID); - auto ptr = cast(ubyte*) data(cell.bind.idx); - int sz = len(cell.bind.idx); - date d = bigEndianToNative!uint(ptr[0..4]); // why not sz? - int[3] mdy; - PGTYPESdate_julmdy(d, &mdy[0]); - return Date(mdy[2],mdy[0],mdy[1]); + bool isNull(Cell* cell) { + return PQgetisnull(res, row, cell.bind.idx) != 0; } void checkType(int a, int b) { - if (a != b) throw new DatabaseException("type mismatch"); + if (a != b) + throw new DatabaseException("type mismatch"); } - void* data(int col) {return PQgetvalue(res, row, col);} - bool isNull(int col) {return PQgetisnull(res, row, col) != 0;} - int type(int col) {return describe[col].dbType;} - int fmt(int col) {return describe[col].fmt;} - int len(int col) {return PQgetlength(res, row, col);} + void* data(int col) { + return PQgetvalue(res, row, col); + } + + bool isNull(int col) { + return PQgetisnull(res, row, col) != 0; + } + auto type(int col) { + return describe[col].dbType; + } + + int fmt(int col) { + return describe[col].fmt; + } + + int len(int col) { + return PQgetlength(res, row, col); + } + + } +} + +// the under is from ddbc: +import core.vararg; +import std.exception; +import std.meta; +import std.range.primitives; +import std.traits; +import std.format; + +Time parseTimeoid(const string timeoid) { + import std.format; + + string input = timeoid.dup; + int hour, min, sec; + formattedRead(input, "%s:%s:%s", &hour, &min, &sec); + return Time(hour, min, sec); +} + +ubyte[] byteaToUbytes(string s) { + if (s is null) + return null; + ubyte[] res; + bool lastBackSlash = 0; + foreach (ch; s) { + if (ch == '\\') { + if (lastBackSlash) { + res ~= '\\'; + lastBackSlash = false; + } + else { + lastBackSlash = true; + } + } + else { + if (lastBackSlash) { + if (ch == '0') { + res ~= 0; + } + else if (ch == 'r') { + res ~= '\r'; + } + else if (ch == 'n') { + res ~= '\n'; + } + else if (ch == 't') { + res ~= '\t'; + } + else { + } + } + else { + res ~= cast(byte) ch; + } + lastBackSlash = false; + } } + return res; } diff --git a/src/std/database/reference/database.d b/src/std/database/reference/database.d index 751b54c..6d8ed19 100644 --- a/src/std/database/reference/database.d +++ b/src/std/database/reference/database.d @@ -18,31 +18,31 @@ struct DefaultPolicy { // this function is module specific because it allows // a default template to be specified -auto createDatabase()(string defaultUrl=null) { - return Database!DefaultPolicy(defaultUrl); +auto createDatabase()(string defaultUrl = null) { + return Database!DefaultPolicy(defaultUrl); } //one for specfic type -auto createDatabase(T)(string defaultUrl=null) { - return Database!T(defaultUrl); +auto createDatabase(T)(string defaultUrl = null) { + return Database!T(defaultUrl); } // these functions can be moved into util once a solution to forward bug is found auto connection(T)(Database!T db, string source) { - return Connection!T(db,source); + return Connection!T(db, source); } -auto statement(T) (Connection!T con, string sql) { +auto statement(T)(Connection!T con, string sql) { return Statement!T(con, sql); } -auto statement(T, X...) (Connection!T con, string sql, X args) { +auto statement(T, X...)(Connection!T con, string sql, X args) { return Statement!T(con, sql, args); } auto result(T)(Statement!T stmt) { - return Result!T(stmt); + return Result!T(stmt); } struct Database(T) { @@ -50,7 +50,6 @@ struct Database(T) { static const auto queryVariableType = QueryVariableType.QuestionMark; - private struct Payload { string defaultURI; Allocator allocator; @@ -62,11 +61,16 @@ struct Database(T) { } ~this() { - info("closing database resource"); + // info("closing database resource"); + } + + this(this) { + assert(false); } - this(this) { assert(false); } - void opAssign(Database.Payload rhs) { assert(false); } + void opAssign(Database.Payload rhs) { + assert(false); + } } private alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; @@ -77,21 +81,31 @@ struct Database(T) { } } - struct Connection(T) { alias Database = .Database!T; //alias Statement = .Statement!T; - auto statement(string sql) {return Statement!T(this,sql);} - auto statement(X...) (string sql, X args) {return Statement!T(this,sql,args);} - auto query(string sql) {return statement(sql).query();} - auto query(T...) (string sql, T args) {return statement(sql).query(args);} + auto statement(string sql) { + return Statement!T(this, sql); + } + + auto statement(X...)(string sql, X args) { + return Statement!T(this, sql, args); + } + + auto query(string sql) { + return statement(sql).query(); + } + + auto query(T...)(string sql, T args) { + return statement(sql).query(args); + } package this(Database db, string source) { - data_ = Data(db,source); + data_ = Data(db, source); } - private: +private: struct Payload { Database db; @@ -107,8 +121,13 @@ struct Connection(T) { ~this() { } - this(this) { assert(false); } - void opAssign(Connection.Payload rhs) { assert(false); } + this(this) { + assert(false); + } + + void opAssign(Connection.Payload rhs) { + assert(false); + } } private alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; @@ -122,18 +141,23 @@ struct Statement(T) { //alias Range = Result.Range; // error Result.Payload no size yet for forward reference // temporary - auto result() {return Result!T(this);} - auto opSlice() {return result();} + auto result() { + return Result!T(this); + } + + auto opSlice() { + return result(); + } this(Connection con, string sql) { - data_ = Data(con,sql); + data_ = Data(con, sql); //prepare(); // must be able to detect binds in all DBs //if (!data_.binds) query(); } - this(T...) (Connection con, string sql, T args) { - data_ = Data(con,sql); + this(T...)(Connection con, string sql, T args) { + data_ = Data(con, sql); //prepare(); //bindAll(args); //query(); @@ -145,10 +169,10 @@ struct Statement(T) { void bind(int n, int value) { } - void bind(int n, const char[] value){ + void bind(int n, const char[] value) { } - private: +private: struct Payload { Connection con; @@ -161,29 +185,37 @@ struct Statement(T) { sql = sql_; } - this(this) { assert(false); } - void opAssign(Statement.Payload rhs) { assert(false); } + this(this) { + assert(false); + } + + void opAssign(Statement.Payload rhs) { + assert(false); + } } alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; Data data_; - public: +public: + + void exec() { + } - void exec() {} - void prepare() {} + void prepare() { + } auto query() { return result(); } - auto query(X...) (X args) { + auto query(X...)(X args) { return query(); } - private: +private: - void bindAll(T...) (T args) { + void bindAll(T...)(T args) { int col; foreach (arg; args) { bind(++col, arg); @@ -204,12 +236,19 @@ struct Result(T) { data_ = Data(stmt); } - auto opSlice() {return ResultRange(this);} + auto opSlice() { + return ResultRange(this); + } + + bool start() { + return true; + } - bool start() {return true;} - bool next() {return data_.next();} + bool next() { + return data_.next(); + } - private: +private: struct Payload { Statement stmt; @@ -223,8 +262,13 @@ struct Result(T) { return false; } - this(this) { assert(false); } - void opAssign(Statement.Payload rhs) { assert(false); } + this(this) { + assert(false); + } + + void opAssign(Statement.Payload rhs) { + assert(false); + } } alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; @@ -233,15 +277,17 @@ struct Result(T) { } struct Value { - auto as(T:int)() { + auto as(T : int)() { return 0; } - auto as(T:string)() { + auto as(T : string)() { return "value"; } - auto chars() {return "value";} + auto chars() { + return "value"; + } } struct ResultRange(T) { @@ -255,19 +301,33 @@ struct ResultRange(T) { ok_ = result_.start(); } - bool empty() {return !ok_;} - Row front() {return Row(&result_);} - void popFront() {ok_ = result_.next();} -} + bool empty() { + return !ok_; + } + Row front() { + return Row(&result_); + } + + void popFront() { + ok_ = result_.next(); + } +} struct Row(T) { alias Result = .Result!T; alias Value = .Value; - this(Result* result) { result_ = result;} - Value opIndex(size_t idx) {return Value();} + this(Result* result) { + result_ = result; + } + + Value opIndex(size_t idx) { + return Value(); + } + private Result* result_; - int columns() {return 0;} + int columns() { + return 0; + } } - diff --git a/src/std/database/rowset.d b/src/std/database/rowset.d deleted file mode 100644 index b376302..0000000 --- a/src/std/database/rowset.d +++ /dev/null @@ -1,85 +0,0 @@ -module std.database.rowset; -import std.database.front; -import std.datetime; -import std.container.array; - -// experimental detached rowset - -/* -struct RowSet { - private struct RowData { - int[3] data; - } - private alias Data = Array!RowData; - - struct Row { - private RowSet* rowSet; - private RowData* data; - this(RowSet* rs, RowData *d) {rowSet = rs; data = d;} - int columns() {return rowSet.columns();} - auto opIndex(size_t idx) {return Value(rowSet,Bind(ValueType.Int, &data.data[idx]));} - } - - struct Bind { - this(ValueType t, void* d) {type = t; data = d;} - ValueType type; - void *data; - } - - struct Driver { - alias Result = .RowSet; - alias Bind = RowSet.Bind; - } - struct Policy {} - - alias Converter = .Converter!(Driver,Policy); - - - struct TypeInfo(T:int) {static int type() {return ValueType.Int;}} - struct TypeInfo(T:string) {static int type() {return ValueType.String;}} - struct TypeInfo(T:Date) {static int type() {return ValueType.Date;}} - - static auto get(X:string)(Bind *b) {return "";} - static auto get(X:int)(Bind *b) {return *cast(int*) b;} - static auto get(X:Date)(Bind *b) {return Date(2016,1,1);} - - struct Value { - RowSet* rowSet; - Bind bind; - private void* data; - this(RowSet *r, Bind b) { - rowSet = r; - bind = b; - } - //auto as(T:int)() {return *cast(int*) bind.data;} - auto as(T:int)() {return Converter.convert!T(rowSet,&bind);} - } - - struct Range { - alias Rng = Data.Range; - private RowSet* rowSet; - private Rng rng; - this(ref RowSet rs, Rng r) {rowSet = &rs; rng = r;} - bool empty() {return rng.empty();} - Row front() {return Row(rowSet,&rng.front());} - void popFront() {rng.popFront();} - } - - - this(R) (R result, size_t capacity = 0) { - data.reserve(capacity); - foreach (r; result[]) { - data ~= RowData(); - auto d = &data.back(); - for(int c = 0; c != r.columns; ++c) d.data[c] = r[c].as!int; - } - } - - int columns() {return cast(int) data.length;} - auto opSlice() {return Range(this,data[]);} - - private Array!RowData data; -} - -*/ - diff --git a/src/std/database/sqlite/database.d b/src/std/database/sqlite/database.d index 63106a7..5e4974e 100644 --- a/src/std/database/sqlite/database.d +++ b/src/std/database/sqlite/database.d @@ -24,28 +24,26 @@ struct DefaultPolicy { alias Allocator = MyMallocator; } -alias Database(T) = BasicDatabase!(Driver!T,T); +alias Database(T) = BasicDatabase!(Driver!T, T); -auto createDatabase()(string defaultURI="") { - return Database!DefaultPolicy(defaultURI); +auto createDatabase()(string defaultURI = "") { + return Database!DefaultPolicy(defaultURI); } -auto createDatabase(T)(string defaultURI="") { - return Database!T(defaultURI); +auto createDatabase(T)(string defaultURI = "") { + return Database!T(defaultURI); } struct Driver(Policy) { alias Allocator = Policy.Allocator; - alias Cell = BasicCell!(Driver,Policy); + alias Cell = BasicCell!(Driver, Policy); struct Database { alias queryVariableType = QueryVariableType.QuestionMark; - static const FeatureArray features = [ - Feature.InputBinding, - //Feature.DateBinding, - //Feature.ConnectionPool, - ]; + static const FeatureArray features = [Feature.InputBinding,//Feature.DateBinding, + //Feature.ConnectionPool, + ]; this(string defaultSource_) { } @@ -74,7 +72,7 @@ struct Driver(Policy) { } ~this() { - writeln("sqlite closing ", path); + // writeln("sqlite closing ", path); if (sq) { int rc = sqlite3_close(sq); sq = null; @@ -93,7 +91,7 @@ struct Driver(Policy) { string sql; State state; sqlite3* sq; - sqlite3_stmt *st; + sqlite3_stmt* st; bool hasRows; int binds_; @@ -112,28 +110,23 @@ struct Driver(Policy) { } } - void bind(int col, int value){ - int rc = sqlite3_bind_int( - st, - col, - value); + void bind(int col, int value) { + int rc = sqlite3_bind_int(st, col, value); if (rc != SQLITE_OK) { throw_error("sqlite3_bind_int"); } } - void bind(int col, const char[] value){ - if(value is null) { + void bind(int col, const char[] value) { + if (value is null) { int rc = sqlite3_bind_null(st, col); - if (rc != SQLITE_OK) throw_error("bind1"); - } else { + if (rc != SQLITE_OK) + throw_error("bind1"); + } + else { //cast(void*)-1); - int rc = sqlite3_bind_text( - st, - col, - value.ptr, - cast(int) value.length, - null); + int rc = sqlite3_bind_text(st, col, value.ptr, cast(int) value.length, + null); if (rc != SQLITE_OK) { writeln(rc); throw_error("bind2"); @@ -145,49 +138,53 @@ struct Driver(Policy) { throw new DatabaseException("Date input binding not yet implemented"); } - int binds() {return binds_;} + int binds() { + return binds_; + } void prepare() { - if (!st) { - int res = sqlite3_prepare_v2( - sq, - toStringz(sql), - cast(int) sql.length + 1, - &st, - null); - if (res != SQLITE_OK) throw_error(sq, "prepare", res); + if (!st) { + int res = sqlite3_prepare_v2(sq, toStringz(sql), + cast(int) sql.length + 1, &st, null); + if (res != SQLITE_OK) + throw_error(sq, "prepare", res); binds_ = sqlite3_bind_parameter_count(st); } } void query() { //if (state == State.Execute) throw new DatabaseException("already executed"); // restore - if (state == State.Execute) return; + if (state == State.Execute) + return; state = State.Execute; int status = sqlite3_step(st); info("sqlite3_step: status: ", status); if (status == SQLITE_ROW) { hasRows = true; - } else if (status == SQLITE_DONE) { + } + else if (status == SQLITE_DONE) { reset(); - } else { + } + else { throw_error(sq, "step error", status); } } - void query(X...) (X args) { + void query(X...)(X args) { bindAll(args); query(); } - private void bindAll(T...) (T args) { + private void bindAll(T...)(T args) { int col; - foreach (arg; args) bind(++col, arg); + foreach (arg; args) + bind(++col, arg); } void reset() { int status = sqlite3_reset(st); - if (status != SQLITE_OK) throw new DatabaseException("sqlite3_reset error"); + if (status != SQLITE_OK) + throw new DatabaseException("sqlite3_reset error"); } } @@ -199,7 +196,7 @@ struct Driver(Policy) { struct Result { private Statement* stmt_; - private sqlite3_stmt *st_; + private sqlite3_stmt* st_; int columns; int status_; @@ -213,21 +210,42 @@ struct Driver(Policy) { // artificial bind setup bind.reserve(columns); - for(int i = 0; i < columns; ++i) { + for (int i = 0; i < columns; ++i) { bind ~= Bind(); auto b = &bind.back(); - b.type = ValueType.String; + /* switch (sqlite3_column_type(st_, i)) + { + case SQLITE_INTEGER: + b.type = ValueType.Int; + break; + case SQLITE_FLOAT: + b.type = ValueType.Float; + break; + case SQLITE3_TEXT: { + b.type = ValueType.String; + break; + case SQLITE_BLOB: + b.type = ValueType.Raw; + break; + default: + b.type = ValueType.String; + break; + }*/ + b.type = ValueType.String; b.idx = i; } } //~this() {} - bool hasRows() {return stmt_.hasRows;} + bool hasRows() { + return stmt_.hasRows; + } int fetch() { status_ = sqlite3_step(st_); - if (status_ == SQLITE_ROW) return 1; + if (status_ == SQLITE_ROW) + return 1; if (status_ == SQLITE_DONE) { stmt_.reset(); return 0; @@ -237,30 +255,68 @@ struct Driver(Policy) { } auto name(size_t idx) { - import core.stdc.string: strlen; + import core.stdc.string : strlen; + auto ptr = sqlite3_column_name(st_, cast(int) idx); - return cast(string) ptr[0..strlen(ptr)]; + return cast(string) ptr[0 .. strlen(ptr)]; } - auto get(X:string)(Cell* cell) { - import core.stdc.string: strlen; - auto ptr = cast(immutable char*) sqlite3_column_text(st_, cast(int) cell.bind.idx); - return cast(string) ptr[0..strlen(ptr)]; // fix with length + ubyte[] rawData(Cell* cell) { + int idx = cast(int) cell.bind.idx; + ubyte * bytes = cast(ubyte *)sqlite3_column_blob(st_, idx); + int len = sqlite3_column_bytes(st_, idx); + return bytes[0..len]; } - auto get(X:int)(Cell* cell) { - return sqlite3_column_int(st_, cast(int) cell.bind.idx); + auto type(int col) + { + return sqlite3_column_type(st_, col); + } + + Variant getValue(Cell* cell) { + Variant value; + int idx = cast(int) cell.bind.idx; + switch (sqlite3_column_type(st_, idx)) { + case SQLITE_INTEGER: + value = sqlite3_column_int64(st_, idx); + cell.bind.type = ValueType.Long; + break; + case SQLITE_FLOAT: + value = sqlite3_column_double(st_, idx); + cell.bind.type = ValueType.Double; + break; + case SQLITE3_TEXT: { + import core.stdc.string : strlen; + + auto ptr = cast(immutable char*) sqlite3_column_text(st_,idx); + int len = sqlite3_column_bytes(st_, idx); + value = cast(string) ptr[0 .. len]; // fix with length + cell.bind.type = ValueType.String; + } + break; + case SQLITE_BLOB: { + ubyte * bytes = cast(ubyte *)sqlite3_column_blob(st_, idx); + int len = sqlite3_column_bytes(st_, idx); + value = bytes[0..len]; + cell.bind.type = ValueType.Raw; + } + break; + default: + cell.bind.type = ValueType.UNKnown; + break; + } + return value; } - auto get(X:Date)(Cell* cell) { - return Date(2016,1,1); // fix + bool isNull(Cell* cell) { + return sqlite3_column_type(st_, cast(int) cell.bind.idx) == SQLITE_NULL; } - } - private static void throw_error()(sqlite3 *sq, string msg, int ret) { + private static void throw_error()(sqlite3* sq, string msg, int ret) { import std.conv; - import core.stdc.string: strlen; + import core.stdc.string : strlen; + const(char*) err = sqlite3_errmsg(sq); throw new DatabaseException("sqlite error: " ~ msg ~ ": " ~ to!string(err)); // need to send err } @@ -269,13 +325,15 @@ struct Driver(Policy) { throw new DatabaseException(label); } - private static void throw_error()(string label, char *msg) { + private static void throw_error()(string label, char* msg) { // frees up pass char * as required by sqlite import core.stdc.string : strlen; + char[] m; sizediff_t sz = strlen(msg); m.length = sz; - for(int i = 0; i != sz; i++) m[i] = msg[i]; + for (int i = 0; i != sz; i++) + m[i] = msg[i]; sqlite3_free(msg); throw new DatabaseException(label ~ m.idup); } @@ -303,8 +361,8 @@ return sqlite3_column_text(result_.st_, cast(int) idx_); */ -extern(C) int sqlite_callback(void* cb, int howmany, char** text, char** columns) { - return 0; -} + extern (C) int sqlite_callback(void* cb, int howmany, char** text, char** columns) { + return 0; + } } diff --git a/src/std/database/uri.d b/src/std/database/uri.d index aa72089..aa375f6 100644 --- a/src/std/database/uri.d +++ b/src/std/database/uri.d @@ -61,7 +61,8 @@ URI toURI(string str) { auto n = e[0..j], v = e[j+1..$]; uri.query[n] ~= v; //needs url decoding } - + trace("host is :", uri.host, " port is :", uri.port, " protool is : ", uri.protocol); + trace("path is : ", uri.path); return uri; }