aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/mssql/mssql.v
blob: a885e4b8dacd2a6565c9d6ce3f9ca8acd49779c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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
}