| 1 | module mssql |
| 2 | |
| 3 | // HStmt is handle for sql statement |
| 4 | struct HStmt { |
| 5 | mut: |
| 6 | // db connection reference. Owner is Connection struct. |
| 7 | hdbc C.SQLHDBC = C.SQLHDBC(C.SQL_NULL_HDBC) |
| 8 | // statement handle |
| 9 | hstmt C.SQLHSTMT = C.SQLHSTMT(C.SQL_NULL_HSTMT) |
| 10 | // fields used for computation |
| 11 | column_count int = -1 |
| 12 | // columns |
| 13 | buffers [][]char |
| 14 | // indicators for each column. Use a V pointer-sized integer to avoid arrays |
| 15 | // of C typedefs in generated code while still matching SQLLEN width. |
| 16 | indicators []isize |
| 17 | } |
| 18 | |
| 19 | // new_hstmt constructs a new statement handle |
| 20 | fn new_hstmt(hdbc C.SQLHDBC) !HStmt { |
| 21 | mut retcode := C.SQLRETURN(C.SQL_SUCCESS) |
| 22 | mut hstmt := C.SQLHSTMT(C.SQL_NULL_HSTMT) |
| 23 | // Allocate statement handle |
| 24 | retcode = C.SQLAllocHandle(C.SQLSMALLINT(C.SQL_HANDLE_STMT), C.SQLHANDLE(hdbc), |
| 25 | unsafe { &C.SQLHANDLE(&hstmt) }) |
| 26 | check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_STMT)', C.SQLHANDLE(hstmt), |
| 27 | C.SQLSMALLINT(C.SQL_HANDLE_STMT))! |
| 28 | |
| 29 | return HStmt{ |
| 30 | hdbc: hdbc |
| 31 | hstmt: hstmt |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | // close the statement handle |
| 36 | fn (mut h HStmt) close() { |
| 37 | // Deallocate handle |
| 38 | if h.hstmt != C.SQLHSTMT(C.SQL_NULL_HSTMT) { |
| 39 | // check error code? |
| 40 | C.SQLFreeHandle(C.SQLSMALLINT(C.SQL_HANDLE_STMT), C.SQLHANDLE(h.hstmt)) |
| 41 | h.hstmt = C.SQLHSTMT(C.SQL_NULL_HSTMT) |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | // exec executes a Sql statement. Result is stored in odbc driver, and not yet read. |
| 46 | fn (h HStmt) exec(sql_ string) ! { |
| 47 | retcode := C.SQLExecDirect(h.hstmt, sql_.str, C.SQLINTEGER(C.SQL_NTS)) |
| 48 | check_error(retcode, 'SQLExecDirect()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT))! |
| 49 | } |
| 50 | |
| 51 | // retrieve_affected_rows returns number of rows affected/modified by the last operation. -1 if not applicable. |
| 52 | fn (h HStmt) retrieve_affected_rows() !int { |
| 53 | count_ret := C.SQLLEN(0) |
| 54 | retcode := C.SQLRowCount(h.hstmt, &count_ret) |
| 55 | check_error(retcode, 'SQLRowCount()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT))! |
| 56 | return int(count_ret) |
| 57 | } |
| 58 | |
| 59 | fn (h HStmt) retrieve_column_count() !int { |
| 60 | mut retcode := C.SQLRETURN(C.SQL_SUCCESS) |
| 61 | col_count_buff := C.SQLSMALLINT(0) |
| 62 | retcode = C.SQLNumResultCols(h.hstmt, &col_count_buff) |
| 63 | check_error(retcode, 'SQLNumResultCols()', C.SQLHANDLE(h.hstmt), |
| 64 | C.SQLSMALLINT(C.SQL_HANDLE_STMT))! |
| 65 | return int(col_count_buff) |
| 66 | } |
| 67 | |
| 68 | // allocate buffers and bind them to drivers |
| 69 | fn (mut h HStmt) prepare_read() ! { |
| 70 | mut retcode := C.SQLRETURN(C.SQL_SUCCESS) |
| 71 | |
| 72 | column_count := h.retrieve_column_count()! |
| 73 | h.column_count = column_count // remember the count because read will need it |
| 74 | |
| 75 | h.buffers = [][]char{len: h.column_count} |
| 76 | h.indicators = []isize{len: h.column_count} |
| 77 | |
| 78 | for i := 0; i < h.column_count; i++ { |
| 79 | i_col := C.SQLUSMALLINT(i + 1) // col number starts with 1 |
| 80 | size_ret := C.SQLLEN(0) |
| 81 | // find out buffer size needed to read data in this column |
| 82 | retcode = C.SQLColAttribute(h.hstmt, i_col, C.SQLUSMALLINT(C.SQL_DESC_LENGTH), |
| 83 | C.SQLPOINTER(0), C.SQLSMALLINT(0), C.SQLSMALLINT(0), &size_ret) |
| 84 | check_error(retcode, 'SQLColAttribute()', C.SQLHANDLE(h.hstmt), |
| 85 | C.SQLSMALLINT(C.SQL_HANDLE_STMT))! |
| 86 | |
| 87 | // buffer allocation is the size + 1 to include termination char, since SQL_DESC_LENGTH does not include it. |
| 88 | allocate_size := size_ret + C.SQLLEN(1) |
| 89 | allocate_size_int := int(allocate_size) |
| 90 | buff := []char{len: allocate_size_int} |
| 91 | |
| 92 | // bind the buffer |
| 93 | retcode = C.SQLBindCol(h.hstmt, C.SQLUSMALLINT(i_col), C.SQLSMALLINT(C.SQL_C_CHAR), |
| 94 | C.SQLPOINTER(&buff[0]), allocate_size, unsafe { &C.SQLLEN(&h.indicators[i]) }) |
| 95 | check_error(retcode, 'SQLBindCol()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT))! |
| 96 | |
| 97 | // record the buffer in HStmt |
| 98 | h.buffers[i] = buff |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | // fetch all rows |
| 103 | fn (h HStmt) read_rows() ![][]string { |
| 104 | mut retcode := C.SQLRETURN(C.SQL_SUCCESS) |
| 105 | |
| 106 | mut res := [][]string{} |
| 107 | |
| 108 | if h.column_count <= 0 { |
| 109 | // there is nothing in the driver to read from |
| 110 | return res |
| 111 | } |
| 112 | |
| 113 | // Fetch and print each row of data until SQL_NO_DATA returned. |
| 114 | for { |
| 115 | mut row := []string{} |
| 116 | retcode = C.SQLFetch(h.hstmt) |
| 117 | if retcode == C.SQLRETURN(C.SQL_SUCCESS) || retcode == C.SQLRETURN(C.SQL_SUCCESS_WITH_INFO) { |
| 118 | // copy buffered result to res |
| 119 | for content in h.buffers { |
| 120 | row << unsafe { cstring_to_vstring(content.data) } |
| 121 | } |
| 122 | } else { |
| 123 | if retcode != C.SQLRETURN(C.SQL_NO_DATA) { |
| 124 | check_error(retcode, 'SQLFetch()', C.SQLHANDLE(h.hstmt), |
| 125 | C.SQLSMALLINT(C.SQL_HANDLE_STMT))! |
| 126 | } else { |
| 127 | break |
| 128 | } |
| 129 | } |
| 130 | res << row |
| 131 | } |
| 132 | return res |
| 133 | } |
| 134 | |