2021-05-31 14:08:31 +03:00
|
|
|
module mssql
|
|
|
|
|
|
|
|
pub struct Connection {
|
|
|
|
mut:
|
|
|
|
henv C.SQLHENV = C.SQLHENV(C.SQL_NULL_HENV) // Environment
|
|
|
|
hdbc C.SQLHDBC = C.SQLHDBC(C.SQL_NULL_HDBC) // Connection handle
|
|
|
|
pub mut:
|
|
|
|
conn_str string
|
|
|
|
}
|
|
|
|
|
|
|
|
// connect to db
|
2022-10-26 11:26:28 +03:00
|
|
|
pub fn (mut conn Connection) connect(conn_str string) !bool {
|
2021-05-31 14:08:31 +03:00
|
|
|
conn_str_c := unsafe { &C.SQLCHAR(conn_str.str) }
|
|
|
|
mut retcode := C.SQLRETURN(C.SQL_SUCCESS)
|
|
|
|
// Allocate environment handle
|
|
|
|
retcode = C.SQLAllocHandle(C.SQLSMALLINT(C.SQL_HANDLE_ENV), C.SQLHANDLE(C.SQL_NULL_HANDLE),
|
|
|
|
unsafe { &C.SQLHANDLE(&conn.henv) })
|
2022-10-26 11:26:28 +03:00
|
|
|
check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_ENV)', C.SQLHANDLE(conn.henv), C.SQLSMALLINT(C.SQL_HANDLE_ENV))!
|
2021-05-31 14:08:31 +03:00
|
|
|
|
|
|
|
// Set the ODBC version environment attribute
|
|
|
|
retcode = C.SQLSetEnvAttr(conn.henv, C.SQLINTEGER(C.SQL_ATTR_ODBC_VERSION), &C.SQLPOINTER(C.SQL_OV_ODBC3),
|
|
|
|
C.SQLINTEGER(0))
|
|
|
|
check_error(retcode, 'SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION)', C.SQLHANDLE(conn.henv),
|
2022-10-26 11:26:28 +03:00
|
|
|
C.SQLSMALLINT(C.SQL_HANDLE_ENV))!
|
2021-05-31 14:08:31 +03:00
|
|
|
|
|
|
|
// Allocate connection handle
|
|
|
|
retcode = C.SQLAllocHandle(C.SQLSMALLINT(C.SQL_HANDLE_DBC), C.SQLHANDLE(conn.henv),
|
|
|
|
unsafe { &C.SQLHANDLE(&conn.hdbc) })
|
2022-10-26 11:26:28 +03:00
|
|
|
check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_DBC)', C.SQLHANDLE(conn.hdbc), C.SQLSMALLINT(C.SQL_HANDLE_DBC))!
|
2021-05-31 14:08:31 +03:00
|
|
|
|
|
|
|
// Set login timeout to 5 seconds
|
|
|
|
retcode = C.SQLSetConnectAttr(conn.hdbc, C.SQLINTEGER(C.SQL_LOGIN_TIMEOUT), C.SQLPOINTER(5),
|
|
|
|
C.SQLINTEGER(0))
|
|
|
|
check_error(retcode, 'SQLSetConnectAttr(SQL_LOGIN_TIMEOUT)', C.SQLHANDLE(conn.hdbc),
|
2022-10-26 11:26:28 +03:00
|
|
|
C.SQLSMALLINT(C.SQL_HANDLE_DBC))!
|
2021-05-31 14:08:31 +03:00
|
|
|
|
|
|
|
// Connect to data source
|
|
|
|
mut outstr := [1024]char{}
|
|
|
|
mut outstrlen := C.SQLSMALLINT(0)
|
|
|
|
retcode = C.SQLDriverConnect(conn.hdbc, C.SQLHWND(0), conn_str_c, C.SQLSMALLINT(C.SQL_NTS),
|
|
|
|
&C.SQLCHAR(&outstr[0]), C.SQLSMALLINT(sizeof(outstr)), &outstrlen, C.SQLUSMALLINT(C.SQL_DRIVER_NOPROMPT))
|
2022-10-26 11:26:28 +03:00
|
|
|
check_error(retcode, 'SQLDriverConnect()', C.SQLHANDLE(conn.hdbc), C.SQLSMALLINT(C.SQL_HANDLE_DBC))!
|
2021-05-31 14:08:31 +03:00
|
|
|
conn.conn_str = conn_str
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// close - closes the connection.
|
|
|
|
pub fn (mut conn Connection) close() {
|
|
|
|
// Connection
|
|
|
|
if conn.hdbc != C.SQLHDBC(C.SQL_NULL_HDBC) {
|
|
|
|
C.SQLDisconnect(conn.hdbc)
|
|
|
|
C.SQLFreeHandle(C.SQLSMALLINT(C.SQL_HANDLE_DBC), C.SQLHANDLE(conn.hdbc))
|
|
|
|
conn.hdbc = C.SQLHDBC(C.SQL_NULL_HDBC)
|
|
|
|
}
|
|
|
|
// Environment
|
|
|
|
if conn.henv != C.SQLHENV(C.SQL_NULL_HENV) {
|
|
|
|
C.SQLFreeHandle(C.SQLSMALLINT(C.SQL_HANDLE_ENV), C.SQLHANDLE(conn.henv))
|
|
|
|
conn.henv = C.SQLHENV(C.SQL_NULL_HENV)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// query executes a sql query
|
2022-10-26 11:26:28 +03:00
|
|
|
pub fn (mut conn Connection) query(q string) !Result {
|
|
|
|
mut hstmt := new_hstmt(conn.hdbc)!
|
2021-05-31 14:08:31 +03:00
|
|
|
defer {
|
|
|
|
hstmt.close()
|
|
|
|
}
|
|
|
|
|
2022-10-26 11:26:28 +03:00
|
|
|
hstmt.exec(q)!
|
2021-05-31 14:08:31 +03:00
|
|
|
|
2022-10-26 11:26:28 +03:00
|
|
|
affected := hstmt.retrieve_affected_rows()!
|
2021-05-31 14:08:31 +03:00
|
|
|
|
2022-10-26 11:26:28 +03:00
|
|
|
hstmt.prepare_read()!
|
|
|
|
raw_rows := hstmt.read_rows()!
|
2021-05-31 14:08:31 +03:00
|
|
|
|
|
|
|
mut res := Result{
|
|
|
|
rows: []Row{}
|
|
|
|
num_rows_affected: affected
|
|
|
|
}
|
|
|
|
|
|
|
|
for rr in raw_rows {
|
|
|
|
res.rows << Row{
|
|
|
|
vals: rr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
// check_error checks odbc return code and extract error string if available
|
2022-10-26 11:26:28 +03:00
|
|
|
fn check_error(e C.SQLRETURN, s string, h C.SQLHANDLE, t C.SQLSMALLINT) ! {
|
2021-05-31 14:08:31 +03:00
|
|
|
if e != C.SQLRETURN(C.SQL_SUCCESS) && e != C.SQLRETURN(C.SQL_SUCCESS_WITH_INFO) {
|
|
|
|
err_str := extract_error(s, h, t)
|
|
|
|
return error(err_str)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// extract_error extracts error string from odbc
|
|
|
|
fn extract_error(fnName string, handle C.SQLHANDLE, tp C.SQLSMALLINT) string {
|
|
|
|
mut err_str := fnName
|
|
|
|
mut i := 0
|
|
|
|
mut native_error := C.SQLINTEGER(0)
|
|
|
|
mut sql_state := [7]char{}
|
|
|
|
mut message_text := [256]char{}
|
|
|
|
mut text_length := C.SQLSMALLINT(0)
|
|
|
|
mut ret := C.SQLRETURN(C.SQL_SUCCESS)
|
|
|
|
|
|
|
|
for ret == C.SQLRETURN(C.SQL_SUCCESS) {
|
|
|
|
i++
|
|
|
|
ret = C.SQLGetDiagRec(tp, handle, C.SQLSMALLINT(i), &C.SQLCHAR(&sql_state[0]),
|
|
|
|
&native_error, &C.SQLCHAR(&message_text[0]), C.SQLSMALLINT(sizeof(message_text)),
|
|
|
|
&text_length)
|
|
|
|
|
|
|
|
// add driver error string
|
|
|
|
if ret == C.SQLRETURN(C.SQL_SUCCESS) || ret == C.SQLRETURN(C.SQL_SUCCESS_WITH_INFO) {
|
|
|
|
unsafe {
|
2021-06-01 11:10:27 +03:00
|
|
|
state_str := (&sql_state[0]).vstring()
|
|
|
|
native_error_code := int(native_error)
|
|
|
|
txt_str := (&message_text[0]).vstring()
|
2022-11-15 16:53:13 +03:00
|
|
|
err_str += '\n\todbc=${state_str}:${i}:${native_error_code}:${txt_str}'
|
2021-05-31 14:08:31 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err_str
|
|
|
|
}
|