mirror of https://github.com/adamdruppe/arsd.git
merge trass3r's fixes
This commit is contained in:
parent
5b12187850
commit
6a17a2ab75
1
README
1
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
|
||||
================
|
||||
|
|
|
@ -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"]);
|
||||
}
|
||||
}
|
||||
*/
|
Loading…
Reference in New Issue