aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/mssql/stmt_handle.v
blob: 8e0b7923e89c869c5cffdb78206e6abb38c9267d (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
126
127
module mssql

// HStmt is handle for sql statement
struct HStmt {
mut:
	// db connection reference. Owner is Connection struct.
	hdbc C.SQLHDBC = C.SQLHDBC(C.SQL_NULL_HDBC)
	// statement handle
	hstmt C.SQLHSTMT = C.SQLHSTMT(C.SQL_NULL_HSTMT)
	// fields used for computation
	column_count int = -1
	// columns
	buffers [][]char
	// indicators for each column
	indicators []C.SQLLEN
}

// new_hstmt constructs a new statement handle
fn new_hstmt(hdbc C.SQLHDBC) ?HStmt {
	mut retcode := C.SQLRETURN(C.SQL_SUCCESS)
	mut hstmt := C.SQLHSTMT(C.SQL_NULL_HSTMT)
	// Allocate statement handle
	retcode = C.SQLAllocHandle(C.SQLSMALLINT(C.SQL_HANDLE_STMT), C.SQLHANDLE(hdbc), unsafe { &C.SQLHANDLE(&hstmt) })
	check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_STMT)', C.SQLHANDLE(hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ?

	return HStmt{
		hdbc: hdbc
		hstmt: hstmt
	}
}

// close the statement handle
fn (mut h HStmt) close() {
	// Deallocate handle
	if h.hstmt != C.SQLHSTMT(C.SQL_NULL_HSTMT) {
		// check error code?
		C.SQLFreeHandle(C.SQLSMALLINT(C.SQL_HANDLE_STMT), C.SQLHANDLE(h.hstmt))
		h.hstmt = C.SQLHSTMT(C.SQL_NULL_HSTMT)
	}
}

// exec executes a Sql statement. Result is stored in odbc driver, and not yet read.
fn (h HStmt) exec(sql string) ? {
	retcode := C.SQLExecDirect(h.hstmt, sql.str, C.SQLINTEGER(C.SQL_NTS))
	check_error(retcode, 'SQLExecDirect()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ?
}

// retrieve_affected_rows returns number of rows affected/modified by the last operation. -1 if not applicable.
fn (h HStmt) retrieve_affected_rows() ?int {
	count_ret := C.SQLLEN(0)
	retcode := C.SQLRowCount(h.hstmt, &count_ret)
	check_error(retcode, 'SQLRowCount()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ?
	return int(count_ret)
}

fn (h HStmt) retrieve_column_count() ?int {
	mut retcode := C.SQLRETURN(C.SQL_SUCCESS)
	col_count_buff := C.SQLSMALLINT(0)
	retcode = C.SQLNumResultCols(h.hstmt, &col_count_buff)
	check_error(retcode, 'SQLNumResultCols()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ?
	return int(col_count_buff)
}

// allocate buffers and bind them to drivers
fn (mut h HStmt) prepare_read() ? {
	mut retcode := C.SQLRETURN(C.SQL_SUCCESS)

	column_count := h.retrieve_column_count() ?
	h.column_count = column_count // remember the count because read will need it

	h.buffers = [][]char{len: h.column_count, cap: h.column_count}
	h.indicators = []C.SQLLEN{len: h.column_count, cap: h.column_count}

	for i := 0; i < h.column_count; i++ {
		i_col := C.SQLUSMALLINT(i + 1) // col number starts with 1
		size_ret := C.SQLLEN(0)
		// find out buffer size needed to read data in this column
		retcode = C.SQLColAttribute(h.hstmt, i_col, C.SQLUSMALLINT(C.SQL_DESC_LENGTH),
			C.SQLPOINTER(0), C.SQLSMALLINT(0), C.SQLSMALLINT(0), &size_ret)
		check_error(retcode, 'SQLColAttribute()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ?

		// buffer allocation is the size + 1 to include termination char, since SQL_DESC_LENGTH does not include it.
		allocate_size := size_ret + C.SQLLEN(1)
		allocate_size_int := int(allocate_size)
		buff := []char{len: allocate_size_int, cap: allocate_size_int}

		// bind the buffer
		retcode = C.SQLBindCol(h.hstmt, C.SQLUSMALLINT(i_col), C.SQLSMALLINT(C.SQL_C_CHAR),
			C.SQLPOINTER(&buff[0]), allocate_size, &h.indicators[i])
		check_error(retcode, 'SQLBindCol()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ?

		// record the buffer in HStmt
		h.buffers[i] = buff
	}
}

// fetch all rows
fn (h HStmt) read_rows() ?[][]string {
	mut retcode := C.SQLRETURN(C.SQL_SUCCESS)

	mut res := [][]string{}

	if h.column_count <= 0 {
		// there is nothing in the driver to read from
		return res
	}

	// Fetch and print each row of data until SQL_NO_DATA returned.
	for {
		mut row := []string{}
		retcode = C.SQLFetch(h.hstmt)
		if retcode == C.SQLRETURN(C.SQL_SUCCESS) || retcode == C.SQLRETURN(C.SQL_SUCCESS_WITH_INFO) {
			// copy buffered result to res
			for content in h.buffers {
				row << string(content)
			}
		} else {
			if retcode != C.SQLRETURN(C.SQL_NO_DATA) {
				check_error(retcode, 'SQLFetch()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ?
			} else {
				break
			}
		}
		res << row
	}
	return res
}