diff --git a/README b/README index ae19164..5461924 100644 --- a/README +++ b/README @@ -30,6 +30,7 @@ database.d - main interface to databases. Includes DataObject mysql.d - a mysql engine for database.d (most mature of the three) postgres.d - a postgres engne for database.d sqlite.d - a sqlite engine for database.d +mssql.d - a (super crappy) mssql engine for database.d (uses ODBC) Desktop app stuff ================ diff --git a/mssql.d b/mssql.d new file mode 100644 index 0000000..ec0f3fd --- /dev/null +++ b/mssql.d @@ -0,0 +1,199 @@ +module arsd.mssql; +pragma(lib, "odbc32"); + +public import arsd.database; + +import std.string; +import std.exception; + +import win32.sql; +import win32.sqlext; + +class MsSql : Database { + // dbname = name is probably the most common connection string + this(string connectionString) { + SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env); + enforce(env !is null); + scope(failure) + SQLFreeHandle(SQL_HANDLE_ENV, env); + SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); + SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + scope(failure) + SQLFreeHandle(SQL_HANDLE_DBC, conn); + enforce(conn !is null); + + auto ret = SqlDriverConnect( + conn, null, connectionString, SQL_NTS, + outstr, sizeof(outstr), &outstrlen, + SQL_DRIVER_COMPLETE); + + if(!SQL_SUCCEEDED(ret)) + throw new DatabaseException("Unable to connect to ODBC object"); // FIXME: print error + query("SET NAMES 'utf8'"); // D does everything with utf8 + } + + ~this() { + SQLDisconnect(conn); + SQLFreeHandle(SQL_HANDLE_DBC, conn); + SQLFreeHandle(SQL_HANDLE_ENV, env); + } + + override void startTransaction() { + query("START TRANSACTION"); + } + + ResultSet queryImpl(string sql, Variant[] args...) { + sql = escapedVariants(this, sql, args); + + // this is passed to MsSqlResult to control + SQLHSTMT statement; + auto returned = SQLAllocHandle(SQL_HANDLE_STMT, conn, + &statement) + + enforce(returned == SQL_SUCCESS); + + returned = SQLExecDirect(statement, sql.ptr, SQL_NTS); + if(returned != SQL_SUCCESS) + throw new DatabaseException(error()); + + return new MsSqlResult(statement); + } + + string escape(string sqlData) { // FIXME + return ret.replace("'", "''"); + } + + + string error() { + return null; // FIXME + } + + private: + SQLHENV env; + SQLHDBC conn; +} + +class MsSqlResult : ResultSet { + // name for associative array to result index + int getFieldIndex(string field) { + if(mapping is null) + makeFieldMapping(); + return mapping[field]; + } + + + string[] fieldNames() { + if(mapping is null) + makeFieldMapping(); + return columnNames; + } + + // this is a range that can offer other ranges to access it + bool empty() { + return isEmpty; + } + + Row front() { + return row; + } + + void popFront() { + if(!isEmpty) + fetchNext; + } + + this(SQLHSTMT statement) { + this.statement = statement; + + SQLSMALLINT info; + SQLNumResultCols(statement, &info); + numFields = info; + + fetchNext(); + } + + ~this() { + SQLFreeHandle(SQL_HANDLE_STMT, statement); + } + + private: + SQLHSTMT statement; + int[string] mapping; + string[] columnNames; + int numFields; + + bool isEmpty; + + Row row; + + void fetchNext() { + if(isEmpty) + return; + + if(SQLFetch(statement) == SQL_SUCCESS) { + Row r; + r.resultSet = this; + string[] row; + + SQLLEN ptr; + + for(int i = 0; i < numFields; i++) { + string a; + + more: + SQLCHAR buf[255]; + if(SQLGetData(statement, i, SQL_CHAR, buf.ptr, 255, &ptr) != SQL_SUCCESS) + throw new DatabaseException("get data"); + + assert(ptr != SQL_NO_TOTAL); + if(ptr == SQL_NULL_DATA) + a = null; + else { + a ~= cast(string) buf[0 .. ptr > 255 ? 255 : ptr].idup; + ptr -= ptr > 255 ? 255 : ptr; + if(ptr) + goto more; + } +} + row ~= a; + } + + r.row = row; + this.row = r; + } else { + isEmpty = true; + } + } + + void makeFieldMapping() { + for(int i = 0; i < numFields; i++) { + SQLSMALLINT len; + SQLCHAR[255] buf; + SQLDescribeCol(statement, + i, + &buf, + 255, + &len, + null, null, null, null); + + string a = cast(string) buf[0 .. len].idup; + + columnNames ~= a; + mapping[a] = i; + } + + } +} + +/* +import std.stdio; +void main() { + auto db = new PostgreSql("dbname = test"); + + db.query("INSERT INTO users (id, name) values (?, ?)", 30, "hello mang"); + + foreach(line; db.query("SELECT * FROM users")) { + writeln(line[0], line["name"]); + } +} +*/