126 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			126 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			V
		
	
	
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
 | 
						|
pub fn (mut conn Connection) connect(conn_str string) ?bool {
 | 
						|
	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) })
 | 
						|
	check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_ENV)', C.SQLHANDLE(conn.henv), C.SQLSMALLINT(C.SQL_HANDLE_ENV))?
 | 
						|
 | 
						|
	// 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),
 | 
						|
		C.SQLSMALLINT(C.SQL_HANDLE_ENV))?
 | 
						|
 | 
						|
	// Allocate connection handle
 | 
						|
	retcode = C.SQLAllocHandle(C.SQLSMALLINT(C.SQL_HANDLE_DBC), C.SQLHANDLE(conn.henv),
 | 
						|
		unsafe { &C.SQLHANDLE(&conn.hdbc) })
 | 
						|
	check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_DBC)', C.SQLHANDLE(conn.hdbc), C.SQLSMALLINT(C.SQL_HANDLE_DBC))?
 | 
						|
 | 
						|
	// 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),
 | 
						|
		C.SQLSMALLINT(C.SQL_HANDLE_DBC))?
 | 
						|
 | 
						|
	// 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))
 | 
						|
	check_error(retcode, 'SQLDriverConnect()', C.SQLHANDLE(conn.hdbc), C.SQLSMALLINT(C.SQL_HANDLE_DBC))?
 | 
						|
	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
 | 
						|
pub fn (mut conn Connection) query(q string) ?Result {
 | 
						|
	mut hstmt := new_hstmt(conn.hdbc)?
 | 
						|
	defer {
 | 
						|
		hstmt.close()
 | 
						|
	}
 | 
						|
 | 
						|
	hstmt.exec(q)?
 | 
						|
 | 
						|
	affected := hstmt.retrieve_affected_rows()?
 | 
						|
 | 
						|
	hstmt.prepare_read()?
 | 
						|
	raw_rows := hstmt.read_rows()?
 | 
						|
 | 
						|
	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
 | 
						|
fn check_error(e C.SQLRETURN, s string, h C.SQLHANDLE, t C.SQLSMALLINT) ? {
 | 
						|
	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 {
 | 
						|
				state_str := (&sql_state[0]).vstring()
 | 
						|
				native_error_code := int(native_error)
 | 
						|
				txt_str := (&message_text[0]).vstring()
 | 
						|
				err_str += '\n\todbc=$state_str:$i:$native_error_code:$txt_str'
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return err_str
 | 
						|
}
 |