| 1 | module mssql |
| 2 | |
| 3 | pub struct Connection { |
| 4 | mut: |
| 5 | henv C.SQLHENV = C.SQLHENV(C.SQL_NULL_HENV) // Environment |
| 6 | hdbc C.SQLHDBC = C.SQLHDBC(C.SQL_NULL_HDBC) // Connection handle |
| 7 | pub mut: |
| 8 | conn_str string |
| 9 | } |
| 10 | |
| 11 | // connect to db |
| 12 | pub fn (mut conn Connection) connect(conn_str string) !bool { |
| 13 | conn_str_c := unsafe { &C.SQLCHAR(conn_str.str) } |
| 14 | mut retcode := C.SQLRETURN(C.SQL_SUCCESS) |
| 15 | // Allocate environment handle |
| 16 | retcode = C.SQLAllocHandle(C.SQLSMALLINT(C.SQL_HANDLE_ENV), C.SQLHANDLE(C.SQL_NULL_HANDLE), |
| 17 | unsafe { &C.SQLHANDLE(&conn.henv) }) |
| 18 | check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_ENV)', C.SQLHANDLE(conn.henv), |
| 19 | C.SQLSMALLINT(C.SQL_HANDLE_ENV))! |
| 20 | |
| 21 | // Set the ODBC version environment attribute |
| 22 | retcode = C.SQLSetEnvAttr(conn.henv, C.SQLINTEGER(C.SQL_ATTR_ODBC_VERSION), |
| 23 | &C.SQLPOINTER(C.SQL_OV_ODBC3), C.SQLINTEGER(0)) |
| 24 | check_error(retcode, 'SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION)', C.SQLHANDLE(conn.henv), |
| 25 | C.SQLSMALLINT(C.SQL_HANDLE_ENV))! |
| 26 | |
| 27 | // Allocate connection handle |
| 28 | retcode = C.SQLAllocHandle(C.SQLSMALLINT(C.SQL_HANDLE_DBC), C.SQLHANDLE(conn.henv), |
| 29 | unsafe { &C.SQLHANDLE(&conn.hdbc) }) |
| 30 | check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_DBC)', C.SQLHANDLE(conn.hdbc), |
| 31 | C.SQLSMALLINT(C.SQL_HANDLE_DBC))! |
| 32 | |
| 33 | // Set login timeout to 5 seconds |
| 34 | retcode = C.SQLSetConnectAttr(conn.hdbc, C.SQLINTEGER(C.SQL_LOGIN_TIMEOUT), C.SQLPOINTER(5), |
| 35 | C.SQLINTEGER(0)) |
| 36 | check_error(retcode, 'SQLSetConnectAttr(SQL_LOGIN_TIMEOUT)', C.SQLHANDLE(conn.hdbc), |
| 37 | C.SQLSMALLINT(C.SQL_HANDLE_DBC))! |
| 38 | |
| 39 | // Connect to data source |
| 40 | mut outstr := [1024]char{} |
| 41 | mut outstrlen := C.SQLSMALLINT(0) |
| 42 | retcode = C.SQLDriverConnect(conn.hdbc, C.SQLHWND(0), conn_str_c, C.SQLSMALLINT(C.SQL_NTS), |
| 43 | &C.SQLCHAR(&outstr[0]), C.SQLSMALLINT(sizeof(outstr)), &outstrlen, |
| 44 | C.SQLUSMALLINT(C.SQL_DRIVER_NOPROMPT)) |
| 45 | check_error(retcode, 'SQLDriverConnect()', C.SQLHANDLE(conn.hdbc), |
| 46 | C.SQLSMALLINT(C.SQL_HANDLE_DBC))! |
| 47 | conn.conn_str = conn_str |
| 48 | return true |
| 49 | } |
| 50 | |
| 51 | // close - closes the connection. |
| 52 | pub fn (mut conn Connection) close() { |
| 53 | // Connection |
| 54 | if conn.hdbc != C.SQLHDBC(C.SQL_NULL_HDBC) { |
| 55 | C.SQLDisconnect(conn.hdbc) |
| 56 | C.SQLFreeHandle(C.SQLSMALLINT(C.SQL_HANDLE_DBC), C.SQLHANDLE(conn.hdbc)) |
| 57 | conn.hdbc = C.SQLHDBC(C.SQL_NULL_HDBC) |
| 58 | } |
| 59 | // Environment |
| 60 | if conn.henv != C.SQLHENV(C.SQL_NULL_HENV) { |
| 61 | C.SQLFreeHandle(C.SQLSMALLINT(C.SQL_HANDLE_ENV), C.SQLHANDLE(conn.henv)) |
| 62 | conn.henv = C.SQLHENV(C.SQL_NULL_HENV) |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | // query executes a sql query |
| 67 | pub fn (mut conn Connection) query(q string) !Result { |
| 68 | mut hstmt := new_hstmt(conn.hdbc)! |
| 69 | defer { |
| 70 | hstmt.close() |
| 71 | } |
| 72 | |
| 73 | hstmt.exec(q)! |
| 74 | |
| 75 | affected := hstmt.retrieve_affected_rows()! |
| 76 | |
| 77 | hstmt.prepare_read()! |
| 78 | raw_rows := hstmt.read_rows()! |
| 79 | |
| 80 | mut res := Result{ |
| 81 | rows: []Row{} |
| 82 | num_rows_affected: affected |
| 83 | } |
| 84 | |
| 85 | for rr in raw_rows { |
| 86 | res.rows << Row{ |
| 87 | vals: rr |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | return res |
| 92 | } |
| 93 | |
| 94 | // check_error checks odbc return code and extract error string if available |
| 95 | fn check_error(e C.SQLRETURN, s string, h C.SQLHANDLE, t C.SQLSMALLINT) ! { |
| 96 | if e != C.SQLRETURN(C.SQL_SUCCESS) && e != C.SQLRETURN(C.SQL_SUCCESS_WITH_INFO) { |
| 97 | err_str := extract_error(s, h, t) |
| 98 | return error(err_str) |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | // extract_error extracts error string from odbc |
| 103 | fn extract_error(fnName string, handle C.SQLHANDLE, tp C.SQLSMALLINT) string { |
| 104 | mut err_str := fnName |
| 105 | mut i := 0 |
| 106 | mut native_error := C.SQLINTEGER(0) |
| 107 | mut sql_state := [7]char{} |
| 108 | mut message_text := [256]char{} |
| 109 | mut text_length := C.SQLSMALLINT(0) |
| 110 | mut ret := C.SQLRETURN(C.SQL_SUCCESS) |
| 111 | |
| 112 | for ret == C.SQLRETURN(C.SQL_SUCCESS) { |
| 113 | i++ |
| 114 | ret = C.SQLGetDiagRec(tp, handle, C.SQLSMALLINT(i), &C.SQLCHAR(&sql_state[0]), |
| 115 | &native_error, &C.SQLCHAR(&message_text[0]), C.SQLSMALLINT(sizeof(message_text)), |
| 116 | &text_length) |
| 117 | |
| 118 | // add driver error string |
| 119 | if ret == C.SQLRETURN(C.SQL_SUCCESS) || ret == C.SQLRETURN(C.SQL_SUCCESS_WITH_INFO) { |
| 120 | unsafe { |
| 121 | state_str := (&sql_state[0]).vstring() |
| 122 | native_error_code := int(native_error) |
| 123 | txt_str := (&message_text[0]).vstring() |
| 124 | err_str += '\n\todbc=${state_str}:${i}:${native_error_code}:${txt_str}' |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | return err_str |
| 129 | } |
| 130 | |