aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/mssql
diff options
context:
space:
mode:
authorIndrajith K L2022-12-03 17:00:20 +0530
committerIndrajith K L2022-12-03 17:00:20 +0530
commitf5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch)
tree2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/vlib/mssql
downloadcli-tools-windows-master.tar.gz
cli-tools-windows-master.tar.bz2
cli-tools-windows-master.zip
Adds most of the toolsHEADmaster
Diffstat (limited to 'v_windows/v/vlib/mssql')
-rw-r--r--v_windows/v/vlib/mssql/README.md69
-rw-r--r--v_windows/v/vlib/mssql/_cdef_nix.c.v6
-rw-r--r--v_windows/v/vlib/mssql/_cdef_windows.c.v12
-rw-r--r--v_windows/v/vlib/mssql/_cdefs.c.v27
-rw-r--r--v_windows/v/vlib/mssql/config.v20
-rw-r--r--v_windows/v/vlib/mssql/mssql.v125
-rw-r--r--v_windows/v/vlib/mssql/result.v13
-rw-r--r--v_windows/v/vlib/mssql/stmt_handle.v127
8 files changed, 399 insertions, 0 deletions
diff --git a/v_windows/v/vlib/mssql/README.md b/v_windows/v/vlib/mssql/README.md
new file mode 100644
index 0000000..ff4fefc
--- /dev/null
+++ b/v_windows/v/vlib/mssql/README.md
@@ -0,0 +1,69 @@
+# SQL Server ODBC
+
+* This is a V wrapper of SQL Server ODBC C/C++ library
+
+## Dependencies
+* ODBC C/C++ library
+ * Linux Install:
+ * Details: https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server
+ * `msodbcsql17` and `unixodbc-dev` packages are needed
+ * Windows Install:
+ * `odbc` lib is included in windows sdk for most of distributions,
+ so there is no need to install it separately
+ * Details: https://docs.microsoft.com/en-us/sql/connect/odbc/microsoft-odbc-driver-for-sql-server
+
+## Windows Notes
+### Using `msvc`
+* Make sure `cl.exe` of `msvc` is accessible from command line.
+You can run `v` commands in `Visual Studio 2019 Developer Command Prompt` to be safe.
+* C Headers and dlls can be automatically resolved by `msvc`.
+### Using `tcc`
+* Copy those headers to `@VEXEROOT\thirdparty\mssql\include`.
+The version number `10.0.18362.0` might differ on your system.
+Command Prompt commands:
+```cmd
+copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\sql.h" thirdparty\mssql\include
+copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\sqlext.h" thirdparty\mssql\include
+copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\sqltypes.h" thirdparty\mssql\include
+copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\sqlucode.h" thirdparty\mssql\include
+copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared\sal.h" thirdparty\mssql\include
+copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared\concurrencysal.h" thirdparty\mssql\include
+```
+* dlls can be automatically resolved by `tcc`
+
+## TODO
+* Support Mac
+* Support ORM
+
+## Usage
+```v ignore
+import mssql
+
+fn test_example() ? {
+ // connect to server
+ config := mssql.Config{
+ driver: 'ODBC Driver 17 for SQL Server'
+ server: 'tcp:localhost'
+ uid: '<your username>'
+ pwd: '<your password>'
+ }
+
+ mut conn := mssql.Connection{}
+
+ conn.connect(config.get_conn_str()) ?
+
+ defer {
+ conn.close()
+ }
+
+ // get current db name
+ mut query := 'SELECT DB_NAME()'
+ mut res := conn.query(query) ?
+ assert res == mssql.Result{
+ rows: [mssql.Row{
+ vals: ['master']
+ }]
+ num_rows_affected: -1
+ }
+}
+```
diff --git a/v_windows/v/vlib/mssql/_cdef_nix.c.v b/v_windows/v/vlib/mssql/_cdef_nix.c.v
new file mode 100644
index 0000000..0a9ec00
--- /dev/null
+++ b/v_windows/v/vlib/mssql/_cdef_nix.c.v
@@ -0,0 +1,6 @@
+module mssql
+
+#flag -lodbc
+
+#include <sql.h>
+#include <sqlext.h>
diff --git a/v_windows/v/vlib/mssql/_cdef_windows.c.v b/v_windows/v/vlib/mssql/_cdef_windows.c.v
new file mode 100644
index 0000000..61724ee
--- /dev/null
+++ b/v_windows/v/vlib/mssql/_cdef_windows.c.v
@@ -0,0 +1,12 @@
+module mssql
+
+// mssql module does not support tcc on windows
+
+// odbc32 lib comes with windows sdk and does not need to be installed separately.
+// v builder for msvc can resolve the sdk includes search path, so no need to repeat here.
+#flag windows -lodbc32
+
+// Special handling of sql headers on windows.
+// Source is in v third party folder.
+#flag windows -I@VEXEROOT/thirdparty/mssql/include
+#include <mssql.h>
diff --git a/v_windows/v/vlib/mssql/_cdefs.c.v b/v_windows/v/vlib/mssql/_cdefs.c.v
new file mode 100644
index 0000000..7ce0d08
--- /dev/null
+++ b/v_windows/v/vlib/mssql/_cdefs.c.v
@@ -0,0 +1,27 @@
+module mssql
+
+fn C.SQLAllocHandle(handle_type C.SQLSMALLINT, input_handle C.SQLHANDLE, output_handle &C.SQLHANDLE) C.SQLRETURN
+
+fn C.SQLSetEnvAttr(environment_handle C.SQLHENV, attribute C.SQLINTEGER, value C.SQLPOINTER, string_length C.SQLINTEGER) C.SQLRETURN
+
+fn C.SQLGetDiagRec(handle_type C.SQLSMALLINT, handle C.SQLHANDLE, rec_number C.SQLSMALLINT, sql_state &C.SQLCHAR, native_error &C.SQLINTEGER, message_text &C.SQLCHAR, buffer_length C.SQLSMALLINT, text_length &C.SQLSMALLINT) C.SQLRETURN
+
+fn C.SQLSetConnectAttr(connection_handle C.SQLHDBC, attribute C.SQLINTEGER, value C.SQLPOINTER, string_length C.SQLINTEGER) C.SQLRETURN
+
+fn C.SQLDriverConnect(hdbc C.SQLHDBC, hwnd C.SQLHWND, sz_conn_str_in &C.SQLCHAR, cb_conn_str_in C.SQLSMALLINT, sz_conn_str_out &C.SQLCHAR, cb_conn_str_out_max C.SQLSMALLINT, pcb_conn_str_out &C.SQLSMALLINT, f_driver_completion C.SQLUSMALLINT) C.SQLRETURN
+
+fn C.SQLDisconnect(connection_handle C.SQLHDBC) C.SQLRETURN
+
+fn C.SQLExecDirect(statement_handle C.SQLHSTMT, statement_text &C.SQLCHAR, text_length C.SQLINTEGER) C.SQLRETURN
+
+fn C.SQLBindCol(statement_handle C.SQLHSTMT, column_number C.SQLUSMALLINT, target_type C.SQLSMALLINT, target_value C.SQLPOINTER, buffer_length C.SQLLEN, str_len_or_ind &C.SQLLEN) C.SQLRETURN
+
+fn C.SQLFetch(statement_handle C.SQLHSTMT) C.SQLRETURN
+
+fn C.SQLFreeHandle(handle_type C.SQLSMALLINT, handle C.SQLHANDLE) C.SQLRETURN
+
+fn C.SQLNumResultCols(statement_handle C.SQLHSTMT, column_count &C.SQLSMALLINT) C.SQLRETURN
+
+fn C.SQLColAttribute(statement_handle C.SQLHSTMT, column_number C.SQLUSMALLINT, field_identifier C.SQLUSMALLINT, character_attribute C.SQLPOINTER, buffer_length C.SQLSMALLINT, string_length C.SQLSMALLINT, numeric_attribute &C.SQLLEN) C.SQLRETURN
+
+fn C.SQLRowCount(statement_handle C.SQLHSTMT, row_count &C.SQLLEN) C.SQLRETURN
diff --git a/v_windows/v/vlib/mssql/config.v b/v_windows/v/vlib/mssql/config.v
new file mode 100644
index 0000000..7f26d6f
--- /dev/null
+++ b/v_windows/v/vlib/mssql/config.v
@@ -0,0 +1,20 @@
+module mssql
+
+pub struct Config {
+pub:
+ driver string
+ server string
+ uid string
+ pwd string
+ // if dbname empty, conn str will not contain Database info,
+ // and it is up to the server to choose which db to connect to.
+ dbname string
+}
+
+pub fn (cfg Config) get_conn_str() string {
+ mut str := 'Driver=$cfg.driver;Server=$cfg.server;UID=$cfg.uid;PWD=$cfg.pwd'
+ if cfg.dbname != '' {
+ str += ';Database=$cfg.dbname'
+ }
+ return str
+}
diff --git a/v_windows/v/vlib/mssql/mssql.v b/v_windows/v/vlib/mssql/mssql.v
new file mode 100644
index 0000000..a885e4b
--- /dev/null
+++ b/v_windows/v/vlib/mssql/mssql.v
@@ -0,0 +1,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
+}
diff --git a/v_windows/v/vlib/mssql/result.v b/v_windows/v/vlib/mssql/result.v
new file mode 100644
index 0000000..f5483a2
--- /dev/null
+++ b/v_windows/v/vlib/mssql/result.v
@@ -0,0 +1,13 @@
+module mssql
+
+pub struct Row {
+pub mut:
+ vals []string
+}
+
+pub struct Result {
+pub mut:
+ rows []Row
+ // the number of rows affected by sql statement
+ num_rows_affected int
+}
diff --git a/v_windows/v/vlib/mssql/stmt_handle.v b/v_windows/v/vlib/mssql/stmt_handle.v
new file mode 100644
index 0000000..8e0b792
--- /dev/null
+++ b/v_windows/v/vlib/mssql/stmt_handle.v
@@ -0,0 +1,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
+}