Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
# SQL Server ODBC
* This module wraps the ODBC C API for use with SQL Server.
* It can also be used with any ODBC data source by passing a raw ODBC connection string.
## Scope
* `db.mssql` can connect to any ODBC data source when `mssql.open(...)` or
`Connection.connect(...)` receives a valid ODBC connection string.
* `Config` can build connection strings from `driver`, `server`, `port`, `uid`/`user`,
`pwd`/`password`, `dbname`, `dsn`, and extra ODBC options.
* For non-MSSQL ODBC sources, pass a DSN or raw ODBC string directly.
## Dependencies
* ODBC driver manager development headers/libraries (`sql.h`, `sqlext.h`).
* Linux:
* Install unixODBC development packages (`unixodbc`, `unixodbc-dev`, or distro equivalent).
* macOS:
* Recommended: install unixODBC + pkg-config:
`brew install unixodbc pkg-config`
* Then install your DB vendor ODBC driver (for SQL Server: `msodbcsql18`).
* Details: see the Microsoft SQL Server ODBC driver installation guide.
* Windows:
* `odbc32` is included with the Windows SDK on most systems.
* Details:
https://learn.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 ORM
## Usage
```v ignore
import db.mssql
fn test_example() ? {
// connect to server
mut conn := mssql.connect(
driver: 'ODBC Driver 18 for SQL Server'
server: '127.0.0.1'
port: 1433
user: '<your username>'
password: '<your password>'
dbname: 'master'
options: {
'Encrypt': 'yes'
'TrustServerCertificate': 'yes'
}
)?
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
}
}
```
You can also connect with a raw DSN or ODBC string:
```v ignore
mut conn := mssql.open('DSN=Reporting;Trusted_Connection=Yes')?
```
+23
View File
@@ -0,0 +1,23 @@
module mssql
$if $pkgconfig('odbc') {
#pkgconfig --cflags --libs odbc
} $else {
#flag -lodbc
// Common fallback paths when unixODBC is installed via package managers on macOS.
#flag darwin -I/opt/homebrew/opt/unixodbc/include
#flag darwin -L/opt/homebrew/opt/unixodbc/lib
#flag darwin -I/usr/local/opt/unixodbc/include
#flag darwin -L/usr/local/opt/unixodbc/lib
#flag darwin -I/opt/local/include
#flag darwin -L/opt/local/lib
}
$if tinyc {
// tcc does not search system include paths by default; add /usr/include so
// that it can find the unixODBC headers (sql.h, sqlext.h) on Linux.
#flag linux -I/usr/include
}
#include <sql.h>
#include <sqlext.h>
+12
View File
@@ -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>
+44
View File
@@ -0,0 +1,44 @@
module mssql
type C.SQLCHAR = u8
type C.SQLHDBC = voidptr
type C.SQLHENV = voidptr
type C.SQLHANDLE = voidptr
type C.SQLHSTMT = voidptr
type C.SQLHWND = voidptr
type C.SQLINTEGER = int
type C.SQLLEN = isize
type C.SQLPOINTER = voidptr
type C.SQLRETURN = i16
type C.SQLSMALLINT = i16
type C.SQLUSMALLINT = u16
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
+96
View File
@@ -0,0 +1,96 @@
module mssql
@[params]
pub struct Config {
pub:
conn_str string
dsn string
driver string
server string
port int
uid string
user string
pwd string
password 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
options map[string]string
}
// connect creates a new connection using the provided configuration.
pub fn connect(config Config) !Connection {
return open(config.get_conn_str())
}
// open creates a new connection using a raw ODBC connection string.
pub fn open(conn_str string) !Connection {
mut conn := Connection{}
conn.connect(conn_str)!
return conn
}
fn preferred_value(primary string, fallback string) string {
if primary != '' {
return primary
}
return fallback
}
fn needs_odbc_braces(value string) bool {
return value.contains_any(' \t\r\n;{}')
}
fn format_odbc_value(value string) string {
if value == '' {
return ''
}
if needs_odbc_braces(value) {
return '{' + value.replace('}', '}}') + '}'
}
return value
}
fn append_conn_part(mut parts []string, key string, value string) {
if value == '' {
return
}
parts << '${key}=${format_odbc_value(value)}'
}
// get_conn_str builds an ODBC connection string from the configured fields.
pub fn (cfg Config) get_conn_str() string {
if cfg.conn_str != '' {
return cfg.conn_str
}
mut parts := []string{}
append_conn_part(mut parts, 'DSN', cfg.dsn)
append_conn_part(mut parts, 'Driver', cfg.driver)
append_conn_part(mut parts, 'Server', cfg.server)
if cfg.port > 0 {
parts << 'Port=${cfg.port}'
}
append_conn_part(mut parts, 'UID', preferred_value(cfg.uid, cfg.user))
append_conn_part(mut parts, 'PWD', preferred_value(cfg.pwd, cfg.password))
if cfg.dbname != '' {
append_conn_part(mut parts, 'Database', cfg.dbname)
}
if cfg.options.len > 0 {
mut option_keys := cfg.options.keys()
// Manual insertion sort avoids relying on the generic .sort() helper,
// which has tripped bounds-check builds in CI.
for i in 1 .. option_keys.len {
key := option_keys[i]
mut j := i
for j > 0 && option_keys[j - 1] > key {
option_keys[j] = option_keys[j - 1]
j--
}
option_keys[j] = key
}
for key in option_keys {
append_conn_part(mut parts, key, cfg.options[key])
}
}
return parts.join(';')
}
+129
View File
@@ -0,0 +1,129 @@
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
}
+115
View File
@@ -0,0 +1,115 @@
// vtest build: started_mssql?
module main
import db.mssql
import os
import time
const default_mssql_conn_str = 'Driver={FreeTDS};Server=127.0.0.1;Port=1433;UID=sa;PWD=Vlang12345678!;Database=master;TDS_Version=7.4;ClientCharset=UTF-8'
fn test_config_get_conn_str() {
assert mssql.Config{
driver: 'ODBC Driver 18 for SQL Server'
server: 'tcp:localhost'
uid: 'sa'
pwd: 'secret'
dbname: 'master'
}.get_conn_str() == 'Driver={ODBC Driver 18 for SQL Server};Server=tcp:localhost;UID=sa;PWD=secret;Database=master'
assert mssql.Config{
driver: 'FreeTDS'
server: '127.0.0.1'
port: 1433
user: 'sa'
password: 'secret'
options: {
'ClientCharset': 'UTF-8'
'TDS_Version': '7.4'
}
}.get_conn_str() == 'Driver=FreeTDS;Server=127.0.0.1;Port=1433;UID=sa;PWD=secret;ClientCharset=UTF-8;TDS_Version=7.4'
assert mssql.Config{
dsn: 'Reporting DB'
user: 'sa'
password: 'secret'
dbname: 'master'
options: {
'Encrypt': 'yes'
'TrustServerCertificate': 'yes'
}
}.get_conn_str() == 'DSN={Reporting DB};UID=sa;PWD=secret;Database=master;Encrypt=yes;TrustServerCertificate=yes'
assert mssql.Config{
conn_str: 'DSN=Accounting;Trusted_Connection=Yes'
driver: 'ignored'
}.get_conn_str() == 'DSN=Accounting;Trusted_Connection=Yes'
}
fn test_row_helpers() {
row := mssql.Row{
vals: ['1', 'alice']
}
assert row.val(0) == '1'
assert row.val(1) == 'alice'
assert row.values() == ['1', 'alice']
}
fn test_connection_and_query() {
$if !network ? {
eprintln('> Skipping test ${@FN}, since `-d network` is not passed.')
eprintln('> This test requires a working sql server running on localhost.')
return
}
mut conn := connect_with_retry()!
defer {
conn.close()
}
conn.query("if object_id('vlang_mssql_test', 'U') is not null drop table vlang_mssql_test") or {}
defer {
conn.query("if object_id('vlang_mssql_test', 'U') is not null drop table vlang_mssql_test") or {}
}
create_result := conn.query('create table vlang_mssql_test (
id int identity(1,1) primary key,
name varchar(32) not null
)')!
assert create_result.rows == []mssql.Row{}
first_insert := conn.query("insert into vlang_mssql_test (name) values ('alice')")!
assert first_insert.rows == []mssql.Row{}
assert first_insert.num_rows_affected == 1
second_insert := conn.query("insert into vlang_mssql_test (name) values ('bob')")!
assert second_insert.rows == []mssql.Row{}
assert second_insert.num_rows_affected == 1
result := conn.query('select id, name from vlang_mssql_test order by id')!
assert result.rows == [
mssql.Row{
vals: ['1', 'alice']
},
mssql.Row{
vals: ['2', 'bob']
},
]
}
fn connect_with_retry() !mssql.Connection {
conn_str := os.getenv_opt('VMSSQL_CONN_STR') or { default_mssql_conn_str }
mut last_err := 'could not connect to sql server'
for _ in 0 .. 30 {
mut conn := mssql.Connection{}
if _ := conn.connect(conn_str) {
// SQL Server can accept a connection before it is ready to execute statements.
if _ := conn.query('select 1') {
return conn
} else {
last_err = err.msg()
conn.close()
}
} else {
last_err = err.msg()
}
time.sleep(2 * time.second)
}
return error(last_err)
}
+23
View File
@@ -0,0 +1,23 @@
module mssql
pub struct Row {
pub mut:
vals []string
}
// val returns the value at `index`.
pub fn (row Row) val(index int) string {
return row.vals[index]
}
// values returns all row values.
pub fn (row Row) values() []string {
return row.vals.clone()
}
pub struct Result {
pub mut:
rows []Row
// the number of rows affected by sql statement
num_rows_affected int
}
+133
View File
@@ -0,0 +1,133 @@
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. Use a V pointer-sized integer to avoid arrays
// of C typedefs in generated code while still matching SQLLEN width.
indicators []isize
}
// 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}
h.indicators = []isize{len: 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}
// bind the buffer
retcode = C.SQLBindCol(h.hstmt, C.SQLUSMALLINT(i_col), C.SQLSMALLINT(C.SQL_C_CHAR),
C.SQLPOINTER(&buff[0]), allocate_size, unsafe { &C.SQLLEN(&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 << unsafe { cstring_to_vstring(content.data) }
}
} 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
}