mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
vlib: move the mysql/sqlite/pg/mssql modules under vlib/db (#16820)
This commit is contained in:
parent
2d8f160ef1
commit
64558df764
@ -1752,6 +1752,7 @@ fn (t Tree) sql_expr(node ast.SqlExpr) &Node {
|
|||||||
fn (t Tree) sql_stmt(node ast.SqlStmt) &Node {
|
fn (t Tree) sql_stmt(node ast.SqlStmt) &Node {
|
||||||
mut obj := new_object()
|
mut obj := new_object()
|
||||||
obj.add_terse('ast_type', t.string_node('SqlStmt'))
|
obj.add_terse('ast_type', t.string_node('SqlStmt'))
|
||||||
|
obj.add_terse('db_expr_type', t.type_node(node.db_expr_type))
|
||||||
obj.add_terse('db_expr', t.expr(node.db_expr))
|
obj.add_terse('db_expr', t.expr(node.db_expr))
|
||||||
obj.add_terse('or_expr', t.or_expr(node.or_expr))
|
obj.add_terse('or_expr', t.or_expr(node.or_expr))
|
||||||
obj.add('pos', t.pos(node.pos))
|
obj.add('pos', t.pos(node.pos))
|
||||||
|
@ -88,8 +88,8 @@ const (
|
|||||||
'cmd/tools/vdoc/tests/vdoc_file_test.v', /* fails on Windows; order of output is not as expected */
|
'cmd/tools/vdoc/tests/vdoc_file_test.v', /* fails on Windows; order of output is not as expected */
|
||||||
'vlib/context/onecontext/onecontext_test.v',
|
'vlib/context/onecontext/onecontext_test.v',
|
||||||
'vlib/context/deadline_test.v' /* sometimes blocks */,
|
'vlib/context/deadline_test.v' /* sometimes blocks */,
|
||||||
'vlib/mysql/mysql_orm_test.v' /* mysql not installed */,
|
'vlib/db/mysql/mysql_orm_test.v' /* mysql not installed */,
|
||||||
'vlib/pg/pg_orm_test.v' /* pg not installed */,
|
'vlib/db/pg/pg_orm_test.v' /* pg not installed */,
|
||||||
]
|
]
|
||||||
skip_fsanitize_too_slow = [
|
skip_fsanitize_too_slow = [
|
||||||
// These tests are too slow to be run in the CI on each PR/commit
|
// These tests are too slow to be run in the CI on each PR/commit
|
||||||
@ -120,9 +120,9 @@ const (
|
|||||||
'vlib/net/tcp_test.v',
|
'vlib/net/tcp_test.v',
|
||||||
'vlib/orm/orm_test.v',
|
'vlib/orm/orm_test.v',
|
||||||
'vlib/orm/orm_sql_or_blocks_test.v',
|
'vlib/orm/orm_sql_or_blocks_test.v',
|
||||||
'vlib/sqlite/sqlite_test.v',
|
'vlib/db/sqlite/sqlite_test.v',
|
||||||
'vlib/sqlite/sqlite_orm_test.v',
|
'vlib/db/sqlite/sqlite_orm_test.v',
|
||||||
'vlib/sqlite/sqlite_vfs_lowlevel_test.v',
|
'vlib/db/sqlite/sqlite_vfs_lowlevel_test.v',
|
||||||
'vlib/v/tests/orm_sub_struct_test.v',
|
'vlib/v/tests/orm_sub_struct_test.v',
|
||||||
'vlib/v/tests/orm_sub_array_struct_test.v',
|
'vlib/v/tests/orm_sub_array_struct_test.v',
|
||||||
'vlib/v/tests/orm_joined_tables_select_test.v',
|
'vlib/v/tests/orm_joined_tables_select_test.v',
|
||||||
@ -170,9 +170,9 @@ const (
|
|||||||
'vlib/net/http/http_test.v',
|
'vlib/net/http/http_test.v',
|
||||||
'vlib/net/http/status_test.v',
|
'vlib/net/http/status_test.v',
|
||||||
'vlib/net/websocket/ws_test.v',
|
'vlib/net/websocket/ws_test.v',
|
||||||
'vlib/sqlite/sqlite_test.v',
|
'vlib/db/sqlite/sqlite_test.v',
|
||||||
'vlib/sqlite/sqlite_orm_test.v',
|
'vlib/db/sqlite/sqlite_orm_test.v',
|
||||||
'vlib/sqlite/sqlite_vfs_lowlevel_test.v',
|
'vlib/db/sqlite/sqlite_vfs_lowlevel_test.v',
|
||||||
'vlib/orm/orm_test.v',
|
'vlib/orm/orm_test.v',
|
||||||
'vlib/orm/orm_sql_or_blocks_test.v',
|
'vlib/orm/orm_sql_or_blocks_test.v',
|
||||||
'vlib/v/tests/orm_sub_struct_test.v',
|
'vlib/v/tests/orm_sub_struct_test.v',
|
||||||
|
@ -4715,7 +4715,7 @@ V's ORM provides a number of benefits:
|
|||||||
then manually construct objects from the parsed results.)
|
then manually construct objects from the parsed results.)
|
||||||
|
|
||||||
```v
|
```v
|
||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
// sets a custom table name. Default is struct name (case-sensitive)
|
// sets a custom table name. Default is struct name (case-sensitive)
|
||||||
[table: 'customers']
|
[table: 'customers']
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import mysql
|
import db.mysql
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
mut conn := mysql.Connection{
|
mut conn := mysql.Connection{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
import mysql
|
import db.mysql
|
||||||
import pg
|
import db.pg
|
||||||
|
|
||||||
[table: 'modules']
|
[table: 'modules']
|
||||||
struct Module {
|
struct Module {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module main
|
module main
|
||||||
|
|
||||||
import pg
|
import db.pg
|
||||||
|
|
||||||
const dash = '----------------------------------------------------------------'
|
const dash = '----------------------------------------------------------------'
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
db := sqlite.connect(':memory:')!
|
db := sqlite.connect(':memory:')!
|
||||||
|
@ -2,7 +2,7 @@ module main
|
|||||||
|
|
||||||
import vweb
|
import vweb
|
||||||
import time
|
import time
|
||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
vweb.Context
|
vweb.Context
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module databases
|
module databases
|
||||||
|
|
||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
pub fn create_db_connection() !sqlite.DB {
|
pub fn create_db_connection() !sqlite.DB {
|
||||||
mut db := sqlite.connect('database.db')!
|
mut db := sqlite.connect('database.db')!
|
||||||
|
@ -182,7 +182,7 @@ Add a SQLite handle to `App`:
|
|||||||
|
|
||||||
```v oksyntax
|
```v oksyntax
|
||||||
// blog.v
|
// blog.v
|
||||||
import sqlite
|
import db.sqlite
|
||||||
import vweb
|
import vweb
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
|
@ -2,7 +2,7 @@ module main
|
|||||||
|
|
||||||
import vweb
|
import vweb
|
||||||
import time
|
import time
|
||||||
import sqlite
|
import db.sqlite
|
||||||
import json
|
import json
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
|
69
vlib/db/mssql/README.md
Normal file
69
vlib/db/mssql/README.md
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
6
vlib/db/mssql/_cdef_nix.c.v
Normal file
6
vlib/db/mssql/_cdef_nix.c.v
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module mssql
|
||||||
|
|
||||||
|
#flag -lodbc
|
||||||
|
|
||||||
|
#include <sql.h>
|
||||||
|
#include <sqlext.h>
|
12
vlib/db/mssql/_cdef_windows.c.v
Normal file
12
vlib/db/mssql/_cdef_windows.c.v
Normal 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>
|
27
vlib/db/mssql/_cdefs.c.v
Normal file
27
vlib/db/mssql/_cdefs.c.v
Normal file
@ -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
|
22
vlib/db/mssql/config.v
Normal file
22
vlib/db/mssql/config.v
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
module mssql
|
||||||
|
|
||||||
|
// Config TODO
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_conn_str TODO
|
||||||
|
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
|
||||||
|
}
|
125
vlib/db/mssql/mssql.v
Normal file
125
vlib/db/mssql/mssql.v
Normal file
@ -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
|
||||||
|
}
|
13
vlib/db/mssql/result.v
Normal file
13
vlib/db/mssql/result.v
Normal file
@ -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
|
||||||
|
}
|
127
vlib/db/mssql/stmt_handle.v
Normal file
127
vlib/db/mssql/stmt_handle.v
Normal file
@ -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}
|
||||||
|
h.indicators = []C.SQLLEN{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, &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
|
||||||
|
}
|
39
vlib/db/mysql/README.md
Normal file
39
vlib/db/mysql/README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
For Linux, you need to install `MySQL development` package and `pkg-config`.
|
||||||
|
|
||||||
|
For Windows, install [the installer](https://dev.mysql.com/downloads/installer/) ,
|
||||||
|
then copy the `include` and `lib` folders to `<V install directory>\thirdparty\mysql`.
|
||||||
|
|
||||||
|
Note: if you encounter weird errors (your program just exits right away, without
|
||||||
|
printing any messages, even though you have `println('hi')` statements in your
|
||||||
|
`fn main()`), when trying to run a program that does `import db.mysql` on windows, you
|
||||||
|
may need to copy the .dll file: `thirdparty/mysql/lib/libmysql.dll` , into the folder
|
||||||
|
of the executable too (it should be right next to the .exe file).
|
||||||
|
This is a temporary workaround, until we have a more permanent solution, or at least
|
||||||
|
more user friendly errors for that situation.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```v oksyntax
|
||||||
|
import db.mysql
|
||||||
|
|
||||||
|
// Create connection
|
||||||
|
mut connection := mysql.Connection{
|
||||||
|
username: 'root'
|
||||||
|
dbname: 'mysql'
|
||||||
|
}
|
||||||
|
// Connect to server
|
||||||
|
connection.connect()?
|
||||||
|
// Change the default database
|
||||||
|
connection.select_db('db_users')?
|
||||||
|
// Do a query
|
||||||
|
get_users_query_result := connection.query('SELECT * FROM users')?
|
||||||
|
// Get the result as maps
|
||||||
|
for user in get_users_query_result.maps() {
|
||||||
|
// Access the name of user
|
||||||
|
println(user['name'])
|
||||||
|
}
|
||||||
|
// Free the query result
|
||||||
|
get_users_query_result.free()
|
||||||
|
// Close the connection if needed
|
||||||
|
connection.close()
|
||||||
|
```
|
103
vlib/db/mysql/_cdefs.c.v
Normal file
103
vlib/db/mysql/_cdefs.c.v
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
module mysql
|
||||||
|
|
||||||
|
[typedef]
|
||||||
|
struct C.MYSQL {
|
||||||
|
}
|
||||||
|
|
||||||
|
[typedef]
|
||||||
|
struct C.MYSQL_RES {
|
||||||
|
}
|
||||||
|
|
||||||
|
[typedef]
|
||||||
|
struct C.MYSQL_FIELD {
|
||||||
|
name &u8 // Name of column
|
||||||
|
org_name &u8 // Original column name, if an alias
|
||||||
|
table &u8 // Table of column if column was a field
|
||||||
|
org_table &u8 // Org table name, if table was an alias
|
||||||
|
db &u8 // Database for table
|
||||||
|
catalog &u8 // Catalog for table
|
||||||
|
def &u8 // Default value (set by mysql_list_fields)
|
||||||
|
length int // Width of column (create length)
|
||||||
|
max_length int // Max width for selected set
|
||||||
|
name_length u32
|
||||||
|
org_name_length u32
|
||||||
|
table_length u32
|
||||||
|
org_table_length u32
|
||||||
|
db_length u32
|
||||||
|
catalog_length u32
|
||||||
|
def_length u32
|
||||||
|
flags u32 // Div flags
|
||||||
|
decimals u32 // Number of decimals in field
|
||||||
|
charsetnr u32 // Character set
|
||||||
|
@type int // Type of field. See mysql_com.h for types
|
||||||
|
}
|
||||||
|
|
||||||
|
fn C.mysql_init(mysql &C.MYSQL) &C.MYSQL
|
||||||
|
|
||||||
|
fn C.mysql_real_connect(mysql &C.MYSQL, host &char, user &char, passwd &char, db &char, port u32, unix_socket &char, client_flag ConnectionFlag) &C.MYSQL
|
||||||
|
|
||||||
|
fn C.mysql_query(mysql &C.MYSQL, q &u8) int
|
||||||
|
|
||||||
|
fn C.mysql_use_result(mysql &C.MYSQL)
|
||||||
|
|
||||||
|
fn C.mysql_real_query(mysql &C.MYSQL, q &u8, len u32) int
|
||||||
|
|
||||||
|
fn C.mysql_select_db(mysql &C.MYSQL, db &u8) int
|
||||||
|
|
||||||
|
fn C.mysql_change_user(mysql &C.MYSQL, user &u8, password &u8, db &u8) bool
|
||||||
|
|
||||||
|
fn C.mysql_affected_rows(mysql &C.MYSQL) u64
|
||||||
|
|
||||||
|
fn C.mysql_options(mysql &C.MYSQL, option int, arg voidptr) int
|
||||||
|
|
||||||
|
fn C.mysql_get_option(mysql &C.MYSQL, option int, arg voidptr) int
|
||||||
|
|
||||||
|
fn C.mysql_list_tables(mysql &C.MYSQL, wild &u8) &C.MYSQL_RES
|
||||||
|
|
||||||
|
fn C.mysql_num_fields(res &C.MYSQL_RES) int
|
||||||
|
|
||||||
|
fn C.mysql_num_rows(res &C.MYSQL_RES) u64
|
||||||
|
|
||||||
|
fn C.mysql_autocommit(mysql &C.MYSQL, mode bool)
|
||||||
|
|
||||||
|
fn C.mysql_refresh(mysql &C.MYSQL, options u32) int
|
||||||
|
|
||||||
|
fn C.mysql_reset_connection(mysql &C.MYSQL) int
|
||||||
|
|
||||||
|
fn C.mysql_ping(mysql &C.MYSQL) int
|
||||||
|
|
||||||
|
fn C.mysql_store_result(mysql &C.MYSQL) &C.MYSQL_RES
|
||||||
|
|
||||||
|
fn C.mysql_fetch_row(res &C.MYSQL_RES) &&u8
|
||||||
|
|
||||||
|
fn C.mysql_fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD
|
||||||
|
|
||||||
|
fn C.mysql_free_result(res &C.MYSQL_RES)
|
||||||
|
|
||||||
|
fn C.mysql_real_escape_string(mysql &C.MYSQL, to &u8, from &u8, len u64) u64
|
||||||
|
|
||||||
|
// fn C.mysql_real_escape_string_quote(mysql &C.MYSQL, to &byte, from &byte, len u64, quote byte) u64 (Don't exist in mariadb)
|
||||||
|
|
||||||
|
fn C.mysql_close(sock &C.MYSQL)
|
||||||
|
|
||||||
|
// INFO & VERSION
|
||||||
|
fn C.mysql_info(mysql &C.MYSQL) &u8
|
||||||
|
|
||||||
|
fn C.mysql_get_host_info(mysql &C.MYSQL) &u8
|
||||||
|
|
||||||
|
fn C.mysql_get_server_info(mysql &C.MYSQL) &u8
|
||||||
|
|
||||||
|
fn C.mysql_get_server_version(mysql &C.MYSQL) u64
|
||||||
|
|
||||||
|
fn C.mysql_get_client_version() u64
|
||||||
|
|
||||||
|
fn C.mysql_get_client_info() &u8
|
||||||
|
|
||||||
|
// DEBUG & ERROR INFO
|
||||||
|
fn C.mysql_error(mysql &C.MYSQL) &u8
|
||||||
|
|
||||||
|
fn C.mysql_errno(mysql &C.MYSQL) int
|
||||||
|
|
||||||
|
fn C.mysql_dump_debug_info(mysql &C.MYSQL) int
|
||||||
|
|
||||||
|
fn C.mysql_debug(debug &u8)
|
11
vlib/db/mysql/_cdefs_nix.c.v
Normal file
11
vlib/db/mysql/_cdefs_nix.c.v
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
module mysql
|
||||||
|
|
||||||
|
// Need to check if mysqlclient is not there and use mariadb as alternative because newer system doesn't support mysql 8.0 as default
|
||||||
|
|
||||||
|
$if $pkgconfig('mysqlclient') {
|
||||||
|
#pkgconfig mysqlclient
|
||||||
|
#include <mysql.h> # Please install the libmysqlclient-dev development headers
|
||||||
|
} $else {
|
||||||
|
#pkgconfig mariadb
|
||||||
|
#include <mysql.h> # Please install the libmariadb-dev development headers
|
||||||
|
}
|
5
vlib/db/mysql/_cdefs_windows.c.v
Normal file
5
vlib/db/mysql/_cdefs_windows.c.v
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module mysql
|
||||||
|
|
||||||
|
#flag windows -I@VEXEROOT/thirdparty/mysql/include
|
||||||
|
#flag windows @VEXEROOT/thirdparty/mysql/lib/libmysql.dll
|
||||||
|
#include <mysql.h> # Please install https://dev.mysql.com/downloads/installer/ , then put the include/ and lib/ folders in thirdparty/mysql
|
13
vlib/db/mysql/consts.v
Normal file
13
vlib/db/mysql/consts.v
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module mysql
|
||||||
|
|
||||||
|
// MYSQL REFRESH FLAGS
|
||||||
|
pub const (
|
||||||
|
refresh_grant = u32(C.REFRESH_GRANT)
|
||||||
|
refresh_log = u32(C.REFRESH_LOG)
|
||||||
|
refresh_tables = u32(C.REFRESH_TABLES)
|
||||||
|
refresh_hosts = u32(C.REFRESH_HOSTS)
|
||||||
|
refresh_status = u32(C.REFRESH_STATUS)
|
||||||
|
refresh_threads = u32(C.REFRESH_THREADS)
|
||||||
|
refresh_slave = u32(C.REFRESH_SLAVE)
|
||||||
|
refresh_master = u32(C.REFRESH_MASTER)
|
||||||
|
)
|
81
vlib/db/mysql/enums.v
Normal file
81
vlib/db/mysql/enums.v
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
module mysql
|
||||||
|
|
||||||
|
// FieldType is a list of all supported MYSQL field types
|
||||||
|
pub enum FieldType {
|
||||||
|
type_decimal
|
||||||
|
type_tiny
|
||||||
|
type_short
|
||||||
|
type_long
|
||||||
|
type_float
|
||||||
|
type_double
|
||||||
|
type_null
|
||||||
|
type_timestamp
|
||||||
|
type_longlong
|
||||||
|
type_int24
|
||||||
|
type_date
|
||||||
|
type_time
|
||||||
|
type_datetime
|
||||||
|
type_year
|
||||||
|
type_newdate
|
||||||
|
type_varchar
|
||||||
|
type_bit
|
||||||
|
type_timestamp2
|
||||||
|
type_datetime2
|
||||||
|
type_time2
|
||||||
|
type_json = 245
|
||||||
|
type_newdecimal
|
||||||
|
type_enum
|
||||||
|
type_set
|
||||||
|
type_tiny_blob
|
||||||
|
type_medium_blob
|
||||||
|
type_long_blob
|
||||||
|
type_blob
|
||||||
|
type_var_string
|
||||||
|
type_string
|
||||||
|
type_geometry
|
||||||
|
}
|
||||||
|
|
||||||
|
// str returns a text representation of the field type `f`
|
||||||
|
pub fn (f FieldType) str() string {
|
||||||
|
return match f {
|
||||||
|
.type_decimal { 'decimal' }
|
||||||
|
.type_tiny { 'tiny' }
|
||||||
|
.type_short { 'short' }
|
||||||
|
.type_long { 'long' }
|
||||||
|
.type_float { 'float' }
|
||||||
|
.type_double { 'double' }
|
||||||
|
.type_null { 'null' }
|
||||||
|
.type_timestamp { 'timestamp' }
|
||||||
|
.type_longlong { 'longlong' }
|
||||||
|
.type_int24 { 'int24' }
|
||||||
|
.type_date { 'date' }
|
||||||
|
.type_time { 'time' }
|
||||||
|
.type_datetime { 'datetime' }
|
||||||
|
.type_year { 'year' }
|
||||||
|
.type_newdate { 'newdate' }
|
||||||
|
.type_varchar { 'varchar' }
|
||||||
|
.type_bit { 'bit' }
|
||||||
|
.type_timestamp2 { 'timestamp2' }
|
||||||
|
.type_datetime2 { 'datetime2' }
|
||||||
|
.type_time2 { 'time2' }
|
||||||
|
.type_json { 'json' }
|
||||||
|
.type_newdecimal { 'newdecimal' }
|
||||||
|
.type_enum { 'enum' }
|
||||||
|
.type_set { 'set' }
|
||||||
|
.type_tiny_blob { 'tiny_blob' }
|
||||||
|
.type_medium_blob { 'medium_blob' }
|
||||||
|
.type_long_blob { 'long_blob' }
|
||||||
|
.type_blob { 'blob' }
|
||||||
|
.type_var_string { 'var_string' }
|
||||||
|
.type_string { 'string' }
|
||||||
|
.type_geometry { 'geometry' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_len returns the length in bytes, for the given field type `f`
|
||||||
|
pub fn (f FieldType) get_len() u32 {
|
||||||
|
return match f {
|
||||||
|
.type_blob { 262140 }
|
||||||
|
else { 0 }
|
||||||
|
}
|
||||||
|
}
|
250
vlib/db/mysql/mysql.v
Normal file
250
vlib/db/mysql/mysql.v
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
module mysql
|
||||||
|
|
||||||
|
// Values for the capabilities flag bitmask used by the MySQL protocol.
|
||||||
|
// See more on https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__capabilities__flags.html#details
|
||||||
|
pub enum ConnectionFlag {
|
||||||
|
// CAN_HANDLE_EXPIRED_PASSWORDS = C.CAN_HANDLE_EXPIRED_PASSWORDS
|
||||||
|
client_compress = C.CLIENT_COMPRESS
|
||||||
|
client_found_rows = C.CLIENT_FOUND_ROWS
|
||||||
|
client_ignore_sigpipe = C.CLIENT_IGNORE_SIGPIPE
|
||||||
|
client_ignore_space = C.CLIENT_IGNORE_SPACE
|
||||||
|
client_interactive = C.CLIENT_INTERACTIVE
|
||||||
|
client_local_files = C.CLIENT_LOCAL_FILES
|
||||||
|
client_multi_results = C.CLIENT_MULTI_RESULTS
|
||||||
|
client_multi_statements = C.CLIENT_MULTI_STATEMENTS
|
||||||
|
client_no_schema = C.CLIENT_NO_SCHEMA
|
||||||
|
client_odbc = C.CLIENT_ODBC
|
||||||
|
// client_optional_resultset_metadata = C.CLIENT_OPTIONAL_RESULTSET_METADATA
|
||||||
|
client_ssl = C.CLIENT_SSL
|
||||||
|
client_remember_options = C.CLIENT_REMEMBER_OPTIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SQLError {
|
||||||
|
MessageError
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Documentation
|
||||||
|
pub struct Connection {
|
||||||
|
mut:
|
||||||
|
conn &C.MYSQL = C.mysql_init(0)
|
||||||
|
pub mut:
|
||||||
|
host string = '127.0.0.1'
|
||||||
|
port u32 = 3306
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
dbname string
|
||||||
|
flag ConnectionFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect - create a new connection to the MySQL server.
|
||||||
|
pub fn (mut conn Connection) connect() !bool {
|
||||||
|
instance := C.mysql_init(conn.conn)
|
||||||
|
conn.conn = C.mysql_real_connect(instance, conn.host.str, conn.username.str, conn.password.str,
|
||||||
|
conn.dbname.str, conn.port, 0, conn.flag)
|
||||||
|
if isnil(conn.conn) {
|
||||||
|
return error_with_code(get_error_msg(instance), get_errno(instance))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// query - make an SQL query and receive the results.
|
||||||
|
// `query()` cannot be used for statements that contain binary data;
|
||||||
|
// Use `real_query()` instead.
|
||||||
|
pub fn (conn Connection) query(q string) !Result {
|
||||||
|
if C.mysql_query(conn.conn, q.str) != 0 {
|
||||||
|
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
|
||||||
|
}
|
||||||
|
res := C.mysql_store_result(conn.conn)
|
||||||
|
return Result{res}
|
||||||
|
}
|
||||||
|
|
||||||
|
// use_result - reads the result of a query
|
||||||
|
// used after invoking mysql_real_query() or mysql_query(),
|
||||||
|
// for every statement that successfully produces a result set
|
||||||
|
// (SELECT, SHOW, DESCRIBE, EXPLAIN, CHECK TABLE, and so forth).
|
||||||
|
// This reads the result of a query directly from the server
|
||||||
|
// without storing it in a temporary table or local buffer,
|
||||||
|
// mysql_use_result is faster and uses much less memory than C.mysql_store_result().
|
||||||
|
// You must mysql_free_result() after you are done with the result set.
|
||||||
|
pub fn (conn Connection) use_result() {
|
||||||
|
C.mysql_use_result(conn.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// real_query - make an SQL query and receive the results.
|
||||||
|
// `real_query()` can be used for statements containing binary data.
|
||||||
|
// (Binary data may contain the `\0` character, which `query()`
|
||||||
|
// interprets as the end of the statement string). In addition,
|
||||||
|
// `real_query()` is faster than `query()`.
|
||||||
|
pub fn (mut conn Connection) real_query(q string) !Result {
|
||||||
|
if C.mysql_real_query(conn.conn, q.str, q.len) != 0 {
|
||||||
|
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
|
||||||
|
}
|
||||||
|
res := C.mysql_store_result(conn.conn)
|
||||||
|
return Result{res}
|
||||||
|
}
|
||||||
|
|
||||||
|
// select_db - change the default database for database queries.
|
||||||
|
pub fn (mut conn Connection) select_db(dbname string) !bool {
|
||||||
|
if C.mysql_select_db(conn.conn, dbname.str) != 0 {
|
||||||
|
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// change_user - change the mysql user for the connection.
|
||||||
|
// Passing an empty string for the `dbname` parameter, resultsg in only changing
|
||||||
|
// the user and not changing the default database for the connection.
|
||||||
|
pub fn (mut conn Connection) change_user(username string, password string, dbname string) !bool {
|
||||||
|
mut ret := true
|
||||||
|
if dbname != '' {
|
||||||
|
ret = C.mysql_change_user(conn.conn, username.str, password.str, dbname.str)
|
||||||
|
} else {
|
||||||
|
ret = C.mysql_change_user(conn.conn, username.str, password.str, 0)
|
||||||
|
}
|
||||||
|
if !ret {
|
||||||
|
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// affected_rows - return the number of rows changed/deleted/inserted
|
||||||
|
// by the last `UPDATE`, `DELETE`, or `INSERT` query.
|
||||||
|
pub fn (conn &Connection) affected_rows() u64 {
|
||||||
|
return C.mysql_affected_rows(conn.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// autocommit - turns on/off the auto-committing mode for the connection.
|
||||||
|
// When it is on, then each query is commited right away.
|
||||||
|
pub fn (mut conn Connection) autocommit(mode bool) {
|
||||||
|
C.mysql_autocommit(conn.conn, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tables - returns a list of the names of the tables in the current database,
|
||||||
|
// that match the simple regular expression specified by the `wildcard` parameter.
|
||||||
|
// The `wildcard` parameter may contain the wildcard characters `%` or `_`.
|
||||||
|
// If an empty string is passed, it will return all tables.
|
||||||
|
// Calling `tables()` is similar to executing query `SHOW TABLES [LIKE wildcard]`.
|
||||||
|
pub fn (conn &Connection) tables(wildcard string) ![]string {
|
||||||
|
cres := C.mysql_list_tables(conn.conn, wildcard.str)
|
||||||
|
if isnil(cres) {
|
||||||
|
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
|
||||||
|
}
|
||||||
|
res := Result{cres}
|
||||||
|
mut tables := []string{}
|
||||||
|
for row in res.rows() {
|
||||||
|
tables << row.vals[0]
|
||||||
|
}
|
||||||
|
return tables
|
||||||
|
}
|
||||||
|
|
||||||
|
// escape_string - creates a legal SQL string for use in an SQL statement.
|
||||||
|
// The `s` argument is encoded to produce an escaped SQL string,
|
||||||
|
// taking into account the current character set of the connection.
|
||||||
|
pub fn (conn &Connection) escape_string(s string) string {
|
||||||
|
unsafe {
|
||||||
|
to := malloc_noscan(2 * s.len + 1)
|
||||||
|
C.mysql_real_escape_string(conn.conn, to, s.str, s.len)
|
||||||
|
return to.vstring()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set_option - sets extra connect options that affect the behavior of
|
||||||
|
// a connection. This function may be called multiple times to set several
|
||||||
|
// options. To retrieve the current values for an option, use `get_option()`.
|
||||||
|
pub fn (mut conn Connection) set_option(option_type int, val voidptr) {
|
||||||
|
C.mysql_options(conn.conn, option_type, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_option - return the value of an option, settable by `set_option`.
|
||||||
|
// https://dev.mysql.com/doc/c-api/5.7/en/mysql-get-option.html
|
||||||
|
pub fn (conn &Connection) get_option(option_type int) !voidptr {
|
||||||
|
ret := unsafe { nil }
|
||||||
|
if C.mysql_get_option(conn.conn, option_type, &ret) != 0 {
|
||||||
|
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh - flush the tables or caches, or resets replication server
|
||||||
|
// information. The connected user must have the `RELOAD` privilege.
|
||||||
|
pub fn (mut conn Connection) refresh(options u32) !bool {
|
||||||
|
if C.mysql_refresh(conn.conn, options) != 0 {
|
||||||
|
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset - resets the connection, and clear the session state.
|
||||||
|
pub fn (mut conn Connection) reset() !bool {
|
||||||
|
if C.mysql_reset_connection(conn.conn) != 0 {
|
||||||
|
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ping - pings a server connection, or tries to reconnect if the connection
|
||||||
|
// has gone down.
|
||||||
|
pub fn (mut conn Connection) ping() !bool {
|
||||||
|
if C.mysql_ping(conn.conn) != 0 {
|
||||||
|
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// close - closes the connection.
|
||||||
|
pub fn (mut conn Connection) close() {
|
||||||
|
C.mysql_close(conn.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// info - returns information about the most recently executed query.
|
||||||
|
// See more on https://dev.mysql.com/doc/c-api/8.0/en/mysql-info.html
|
||||||
|
pub fn (conn &Connection) info() string {
|
||||||
|
return resolve_nil_str(C.mysql_info(conn.conn))
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_host_info - returns a string describing the type of connection in use,
|
||||||
|
// including the server host name.
|
||||||
|
pub fn (conn &Connection) get_host_info() string {
|
||||||
|
return unsafe { C.mysql_get_host_info(conn.conn).vstring() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_server_info - returns a string representing the MySQL server version.
|
||||||
|
// For example, `8.0.24`.
|
||||||
|
pub fn (conn &Connection) get_server_info() string {
|
||||||
|
return unsafe { C.mysql_get_server_info(conn.conn).vstring() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_server_version - returns an integer, representing the MySQL server
|
||||||
|
// version. The value has the format `XYYZZ` where `X` is the major version,
|
||||||
|
// `YY` is the release level (or minor version), and `ZZ` is the sub-version
|
||||||
|
// within the release level. For example, `8.0.24` is returned as `80024`.
|
||||||
|
pub fn (conn &Connection) get_server_version() u64 {
|
||||||
|
return C.mysql_get_server_version(conn.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump_debug_info - instructs the server to write debugging information
|
||||||
|
// to the error log. The connected user must have the `SUPER` privilege.
|
||||||
|
pub fn (mut conn Connection) dump_debug_info() !bool {
|
||||||
|
if C.mysql_dump_debug_info(conn.conn) != 0 {
|
||||||
|
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_client_info - returns client version information as a string.
|
||||||
|
pub fn get_client_info() string {
|
||||||
|
return unsafe { C.mysql_get_client_info().vstring() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_client_version - returns the client version information as an integer.
|
||||||
|
pub fn get_client_version() u64 {
|
||||||
|
return C.mysql_get_client_version()
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug - does a `DBUG_PUSH` with the given string.
|
||||||
|
// `debug()` uses the Fred Fish debug library.
|
||||||
|
// To use this function, you must compile the client library to support debugging.
|
||||||
|
// See https://dev.mysql.com/doc/c-api/8.0/en/mysql-debug.html
|
||||||
|
pub fn debug(debug string) {
|
||||||
|
C.mysql_debug(debug.str)
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import orm
|
import orm
|
||||||
import mysql
|
import db.mysql
|
||||||
import time
|
import time
|
||||||
|
|
||||||
struct TestCustomSqlType {
|
struct TestCustomSqlType {
|
387
vlib/db/mysql/orm.v
Normal file
387
vlib/db/mysql/orm.v
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
module mysql
|
||||||
|
|
||||||
|
import orm
|
||||||
|
import time
|
||||||
|
|
||||||
|
type Prims = f32 | f64 | i16 | i64 | i8 | int | string | u16 | u32 | u64 | u8
|
||||||
|
|
||||||
|
// sql expr
|
||||||
|
|
||||||
|
// @select is used internally by V's ORM for processing `SELECT ` queries
|
||||||
|
pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive {
|
||||||
|
query := orm.orm_select_gen(config, '`', false, '?', 0, where)
|
||||||
|
mut ret := [][]orm.Primitive{}
|
||||||
|
mut stmt := db.init_stmt(query)
|
||||||
|
stmt.prepare()!
|
||||||
|
|
||||||
|
mysql_stmt_binder(mut stmt, where)!
|
||||||
|
mysql_stmt_binder(mut stmt, data)!
|
||||||
|
|
||||||
|
if data.data.len > 0 || where.data.len > 0 {
|
||||||
|
stmt.bind_params()!
|
||||||
|
}
|
||||||
|
|
||||||
|
mut status := stmt.execute()!
|
||||||
|
num_fields := stmt.get_field_count()
|
||||||
|
metadata := stmt.gen_metadata()
|
||||||
|
fields := stmt.fetch_fields(metadata)
|
||||||
|
mut dataptr := []&u8{}
|
||||||
|
|
||||||
|
for i in 0 .. num_fields {
|
||||||
|
f := unsafe { fields[i] }
|
||||||
|
match unsafe { FieldType(f.@type) } {
|
||||||
|
.type_tiny {
|
||||||
|
dataptr << unsafe { malloc(1) }
|
||||||
|
}
|
||||||
|
.type_short {
|
||||||
|
dataptr << unsafe { malloc(2) }
|
||||||
|
}
|
||||||
|
.type_long {
|
||||||
|
dataptr << unsafe { malloc(4) }
|
||||||
|
}
|
||||||
|
.type_longlong {
|
||||||
|
dataptr << unsafe { malloc(8) }
|
||||||
|
}
|
||||||
|
.type_float {
|
||||||
|
dataptr << unsafe { malloc(4) }
|
||||||
|
}
|
||||||
|
.type_double {
|
||||||
|
dataptr << unsafe { malloc(8) }
|
||||||
|
}
|
||||||
|
.type_time, .type_date, .type_datetime, .type_time2, .type_datetime2 {
|
||||||
|
dataptr << unsafe { malloc(sizeof(C.MYSQL_TIME)) }
|
||||||
|
}
|
||||||
|
.type_string, .type_blob {
|
||||||
|
dataptr << unsafe { malloc(512) }
|
||||||
|
}
|
||||||
|
.type_var_string {
|
||||||
|
dataptr << unsafe { malloc(2) }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return error('\'${unsafe { FieldType(f.@type) }}\' is not yet implemented. Please create a new issue at https://github.com/vlang/v/issues/new')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lens := []u32{len: int(num_fields), init: 0}
|
||||||
|
stmt.bind_res(fields, dataptr, lens, num_fields)
|
||||||
|
|
||||||
|
mut row := 0
|
||||||
|
mut types := config.types.clone()
|
||||||
|
mut field_types := []FieldType{}
|
||||||
|
if config.is_count {
|
||||||
|
types = [orm.type_idx['u64']]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, mut mysql_bind in stmt.res {
|
||||||
|
f := unsafe { fields[i] }
|
||||||
|
field_types << unsafe { FieldType(f.@type) }
|
||||||
|
match types[i] {
|
||||||
|
orm.type_string {
|
||||||
|
mysql_bind.buffer_type = C.MYSQL_TYPE_BLOB
|
||||||
|
mysql_bind.buffer_length = FieldType.type_blob.get_len()
|
||||||
|
}
|
||||||
|
orm.time {
|
||||||
|
match unsafe { FieldType(f.@type) } {
|
||||||
|
.type_long {
|
||||||
|
mysql_bind.buffer_type = C.MYSQL_TYPE_LONG
|
||||||
|
}
|
||||||
|
.type_time, .type_date, .type_datetime {
|
||||||
|
mysql_bind.buffer_type = C.MYSQL_TYPE_BLOB
|
||||||
|
mysql_bind.buffer_length = FieldType.type_blob.get_len()
|
||||||
|
}
|
||||||
|
.type_string, .type_blob {}
|
||||||
|
else {
|
||||||
|
return error('Unknown type ${f.@type}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt.bind_result_buffer()!
|
||||||
|
stmt.store_result()!
|
||||||
|
for {
|
||||||
|
status = stmt.fetch_stmt()!
|
||||||
|
|
||||||
|
if status == 1 || status == 100 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
row++
|
||||||
|
|
||||||
|
data_list := buffer_to_primitive(dataptr, types, field_types)!
|
||||||
|
ret << data_list
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt.close()!
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// sql stmt
|
||||||
|
|
||||||
|
// insert is used internally by V's ORM for processing `INSERT ` queries
|
||||||
|
pub fn (db Connection) insert(table string, data orm.QueryData) ! {
|
||||||
|
mut converted_primitive_array := db.factory_orm_primitive_converted_from_sql(table,
|
||||||
|
data)!
|
||||||
|
|
||||||
|
converted_primitive_data := orm.QueryData{
|
||||||
|
fields: data.fields
|
||||||
|
data: converted_primitive_array
|
||||||
|
types: []
|
||||||
|
kinds: []
|
||||||
|
is_and: []
|
||||||
|
}
|
||||||
|
|
||||||
|
query, converted_data := orm.orm_stmt_gen(table, '`', .insert, false, '?', 1, converted_primitive_data,
|
||||||
|
orm.QueryData{})
|
||||||
|
mysql_stmt_worker(db, query, converted_data, orm.QueryData{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// update is used internally by V's ORM for processing `UPDATE ` queries
|
||||||
|
pub fn (db Connection) update(table string, data orm.QueryData, where orm.QueryData) ! {
|
||||||
|
query, _ := orm.orm_stmt_gen(table, '`', .update, false, '?', 1, data, where)
|
||||||
|
mysql_stmt_worker(db, query, data, where)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete is used internally by V's ORM for processing `DELETE ` queries
|
||||||
|
pub fn (db Connection) delete(table string, where orm.QueryData) ! {
|
||||||
|
query, _ := orm.orm_stmt_gen(table, '`', .delete, false, '?', 1, orm.QueryData{},
|
||||||
|
where)
|
||||||
|
mysql_stmt_worker(db, query, orm.QueryData{}, where)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// last_id is used internally by V's ORM for post-processing `INSERT ` queries
|
||||||
|
pub fn (db Connection) last_id() orm.Primitive {
|
||||||
|
query := 'SELECT last_insert_id();'
|
||||||
|
id := db.query(query) or {
|
||||||
|
Result{
|
||||||
|
result: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orm.Primitive(id.rows()[0].vals[0].int())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DDL (table creation/destroying etc)
|
||||||
|
|
||||||
|
// create is used internally by V's ORM for processing table creation queries (DDL)
|
||||||
|
pub fn (db Connection) create(table string, fields []orm.TableField) ! {
|
||||||
|
query := orm.orm_table_gen(table, '`', true, 0, fields, mysql_type_from_v, false) or {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// drop is used internally by V's ORM for processing table destroying queries (DDL)
|
||||||
|
pub fn (db Connection) drop(table string) ! {
|
||||||
|
query := 'DROP TABLE `${table}`;'
|
||||||
|
mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mysql_stmt_worker(db Connection, query string, data orm.QueryData, where orm.QueryData) ! {
|
||||||
|
mut stmt := db.init_stmt(query)
|
||||||
|
stmt.prepare()!
|
||||||
|
mysql_stmt_binder(mut stmt, data)!
|
||||||
|
mysql_stmt_binder(mut stmt, where)!
|
||||||
|
if data.data.len > 0 || where.data.len > 0 {
|
||||||
|
stmt.bind_params()!
|
||||||
|
}
|
||||||
|
stmt.execute()!
|
||||||
|
stmt.close()!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mysql_stmt_binder(mut stmt Stmt, d orm.QueryData) ! {
|
||||||
|
for data in d.data {
|
||||||
|
stmt_binder_match(mut stmt, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stmt_binder_match(mut stmt Stmt, data orm.Primitive) {
|
||||||
|
match data {
|
||||||
|
bool {
|
||||||
|
stmt.bind_bool(&data)
|
||||||
|
}
|
||||||
|
i8 {
|
||||||
|
stmt.bind_i8(&data)
|
||||||
|
}
|
||||||
|
i16 {
|
||||||
|
stmt.bind_i16(&data)
|
||||||
|
}
|
||||||
|
int {
|
||||||
|
stmt.bind_int(&data)
|
||||||
|
}
|
||||||
|
i64 {
|
||||||
|
stmt.bind_i64(&data)
|
||||||
|
}
|
||||||
|
u8 {
|
||||||
|
stmt.bind_u8(&data)
|
||||||
|
}
|
||||||
|
u16 {
|
||||||
|
stmt.bind_u16(&data)
|
||||||
|
}
|
||||||
|
u32 {
|
||||||
|
stmt.bind_u32(&data)
|
||||||
|
}
|
||||||
|
u64 {
|
||||||
|
stmt.bind_u64(&data)
|
||||||
|
}
|
||||||
|
f32 {
|
||||||
|
stmt.bind_f32(unsafe { &f32(&data) })
|
||||||
|
}
|
||||||
|
f64 {
|
||||||
|
stmt.bind_f64(unsafe { &f64(&data) })
|
||||||
|
}
|
||||||
|
string {
|
||||||
|
stmt.bind_text(data)
|
||||||
|
}
|
||||||
|
time.Time {
|
||||||
|
unix := int(data.unix)
|
||||||
|
stmt_binder_match(mut stmt, unix)
|
||||||
|
}
|
||||||
|
orm.InfixType {
|
||||||
|
stmt_binder_match(mut stmt, data.right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_to_primitive(data_list []&u8, types []int, field_types []FieldType) ![]orm.Primitive {
|
||||||
|
mut res := []orm.Primitive{}
|
||||||
|
|
||||||
|
for i, data in data_list {
|
||||||
|
mut primitive := orm.Primitive(0)
|
||||||
|
match types[i] {
|
||||||
|
orm.type_idx['i8'] {
|
||||||
|
primitive = *(unsafe { &i8(data) })
|
||||||
|
}
|
||||||
|
orm.type_idx['i16'] {
|
||||||
|
primitive = *(unsafe { &i16(data) })
|
||||||
|
}
|
||||||
|
orm.type_idx['int'], orm.serial {
|
||||||
|
primitive = *(unsafe { &int(data) })
|
||||||
|
}
|
||||||
|
orm.type_idx['i64'] {
|
||||||
|
primitive = *(unsafe { &i64(data) })
|
||||||
|
}
|
||||||
|
orm.type_idx['u8'] {
|
||||||
|
primitive = *(unsafe { &u8(data) })
|
||||||
|
}
|
||||||
|
orm.type_idx['u16'] {
|
||||||
|
primitive = *(unsafe { &u16(data) })
|
||||||
|
}
|
||||||
|
orm.type_idx['u32'] {
|
||||||
|
primitive = *(unsafe { &u32(data) })
|
||||||
|
}
|
||||||
|
orm.type_idx['u64'] {
|
||||||
|
primitive = *(unsafe { &u64(data) })
|
||||||
|
}
|
||||||
|
orm.type_idx['f32'] {
|
||||||
|
primitive = *(unsafe { &f32(data) })
|
||||||
|
}
|
||||||
|
orm.type_idx['f64'] {
|
||||||
|
primitive = *(unsafe { &f64(data) })
|
||||||
|
}
|
||||||
|
orm.type_idx['bool'] {
|
||||||
|
primitive = *(unsafe { &bool(data) })
|
||||||
|
}
|
||||||
|
orm.type_string {
|
||||||
|
primitive = unsafe { cstring_to_vstring(&char(data)) }
|
||||||
|
}
|
||||||
|
orm.time {
|
||||||
|
match field_types[i] {
|
||||||
|
.type_long {
|
||||||
|
timestamp := *(unsafe { &int(data) })
|
||||||
|
primitive = time.unix(timestamp)
|
||||||
|
}
|
||||||
|
.type_datetime {
|
||||||
|
string_time := unsafe { cstring_to_vstring(&char(data)) }
|
||||||
|
primitive = time.parse(string_time)!
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return error('Unknown type ${types[i]}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res << primitive
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mysql_type_from_v(typ int) !string {
|
||||||
|
str := match typ {
|
||||||
|
orm.type_idx['i8'], orm.type_idx['u8'] {
|
||||||
|
'TINYINT'
|
||||||
|
}
|
||||||
|
orm.type_idx['i16'], orm.type_idx['u16'] {
|
||||||
|
'SMALLINT'
|
||||||
|
}
|
||||||
|
orm.type_idx['int'], orm.type_idx['u32'], orm.time {
|
||||||
|
'INT'
|
||||||
|
}
|
||||||
|
orm.type_idx['i64'], orm.type_idx['u64'] {
|
||||||
|
'BIGINT'
|
||||||
|
}
|
||||||
|
orm.type_idx['f32'] {
|
||||||
|
'FLOAT'
|
||||||
|
}
|
||||||
|
orm.type_idx['f64'] {
|
||||||
|
'DOUBLE'
|
||||||
|
}
|
||||||
|
orm.type_string {
|
||||||
|
'TEXT'
|
||||||
|
}
|
||||||
|
orm.serial {
|
||||||
|
'SERIAL'
|
||||||
|
}
|
||||||
|
orm.type_idx['bool'] {
|
||||||
|
'BOOLEAN'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if str == '' {
|
||||||
|
return error('Unknown type ${typ}')
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db Connection) factory_orm_primitive_converted_from_sql(table string, data orm.QueryData) ![]orm.Primitive {
|
||||||
|
mut map_val := db.get_table_data_type_map(table)!
|
||||||
|
|
||||||
|
// adapt v type to sql time
|
||||||
|
mut converted_data := []orm.Primitive{}
|
||||||
|
for i, field in data.fields {
|
||||||
|
match data.data[i].type_name() {
|
||||||
|
'time.Time' {
|
||||||
|
if map_val[field] == 'datetime' {
|
||||||
|
converted_data << orm.Primitive((data.data[i] as time.Time).str())
|
||||||
|
} else {
|
||||||
|
converted_data << data.data[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
converted_data << data.data[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return converted_data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db Connection) get_table_data_type_map(table string) !map[string]string {
|
||||||
|
data_type_querys := "SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '${table}'"
|
||||||
|
mut map_val := map[string]string{}
|
||||||
|
|
||||||
|
results := db.query(data_type_querys)!
|
||||||
|
db.use_result()
|
||||||
|
|
||||||
|
for row in results.rows() {
|
||||||
|
map_val[row.vals[0]] = row.vals[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { results.free() }
|
||||||
|
return map_val
|
||||||
|
}
|
153
vlib/db/mysql/result.v
Normal file
153
vlib/db/mysql/result.v
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
module mysql
|
||||||
|
|
||||||
|
pub struct Result {
|
||||||
|
result &C.MYSQL_RES = unsafe { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Row {
|
||||||
|
pub mut:
|
||||||
|
vals []string
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Field {
|
||||||
|
name string
|
||||||
|
org_name string
|
||||||
|
table string
|
||||||
|
org_table string
|
||||||
|
db string
|
||||||
|
catalog string
|
||||||
|
def string
|
||||||
|
length int
|
||||||
|
max_length int
|
||||||
|
name_length u32
|
||||||
|
org_name_length u32
|
||||||
|
table_length u32
|
||||||
|
org_table_length u32
|
||||||
|
db_length u32
|
||||||
|
catalog_length u32
|
||||||
|
def_length u32
|
||||||
|
flags u32
|
||||||
|
decimals u32
|
||||||
|
charsetnr u32
|
||||||
|
type_ FieldType
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch_row - fetches the next row from a result.
|
||||||
|
pub fn (r Result) fetch_row() &&u8 {
|
||||||
|
return C.mysql_fetch_row(r.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// n_rows - returns the number of rows from a result.
|
||||||
|
pub fn (r Result) n_rows() u64 {
|
||||||
|
return C.mysql_num_rows(r.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// n_fields - returns the number of columns from a result.
|
||||||
|
pub fn (r Result) n_fields() int {
|
||||||
|
return C.mysql_num_fields(r.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rows - returns array of rows, each containing an array of values,
|
||||||
|
// one for each column.
|
||||||
|
pub fn (r Result) rows() []Row {
|
||||||
|
mut rows := []Row{}
|
||||||
|
nr_cols := r.n_fields()
|
||||||
|
for rr := r.fetch_row(); rr; rr = r.fetch_row() {
|
||||||
|
mut row := Row{}
|
||||||
|
for i in 0 .. nr_cols {
|
||||||
|
if unsafe { rr[i] == 0 } {
|
||||||
|
row.vals << ''
|
||||||
|
} else {
|
||||||
|
row.vals << mystring(unsafe { &u8(rr[i]) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows << row
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps - returns an array of maps, each containing a set of
|
||||||
|
// field name: field value pairs.
|
||||||
|
pub fn (r Result) maps() []map[string]string {
|
||||||
|
mut array_map := []map[string]string{}
|
||||||
|
rows := r.rows()
|
||||||
|
fields := r.fields()
|
||||||
|
for i in 0 .. rows.len {
|
||||||
|
mut map_val := map[string]string{}
|
||||||
|
for j in 0 .. fields.len {
|
||||||
|
map_val[fields[j].name] = rows[i].vals[j]
|
||||||
|
}
|
||||||
|
array_map << map_val
|
||||||
|
}
|
||||||
|
return array_map
|
||||||
|
}
|
||||||
|
|
||||||
|
// fields - returns an array of fields/columns.
|
||||||
|
// The definitions apply primarily for columns of results,
|
||||||
|
// such as those produced by `SELECT` statements.
|
||||||
|
pub fn (r Result) fields() []Field {
|
||||||
|
mut fields := []Field{}
|
||||||
|
nr_cols := r.n_fields()
|
||||||
|
orig_fields := C.mysql_fetch_fields(r.result)
|
||||||
|
for i in 0 .. nr_cols {
|
||||||
|
unsafe {
|
||||||
|
fields << Field{
|
||||||
|
name: mystring(orig_fields[i].name)
|
||||||
|
org_name: mystring(orig_fields[i].org_name)
|
||||||
|
table: mystring(orig_fields[i].table)
|
||||||
|
org_table: mystring(orig_fields[i].org_table)
|
||||||
|
db: mystring(orig_fields[i].db)
|
||||||
|
catalog: mystring(orig_fields[i].catalog)
|
||||||
|
def: resolve_nil_str(orig_fields[i].def)
|
||||||
|
length: orig_fields.length
|
||||||
|
max_length: orig_fields.max_length
|
||||||
|
name_length: orig_fields.name_length
|
||||||
|
org_name_length: orig_fields.org_name_length
|
||||||
|
table_length: orig_fields.table_length
|
||||||
|
org_table_length: orig_fields.org_table_length
|
||||||
|
db_length: orig_fields.db_length
|
||||||
|
catalog_length: orig_fields.catalog_length
|
||||||
|
def_length: orig_fields.def_length
|
||||||
|
flags: orig_fields.flags
|
||||||
|
decimals: orig_fields.decimals
|
||||||
|
charsetnr: orig_fields.charsetnr
|
||||||
|
type_: FieldType(orig_fields.@type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// str - serializes the field
|
||||||
|
pub fn (f Field) str() string {
|
||||||
|
return '
|
||||||
|
{
|
||||||
|
name: "${f.name}"
|
||||||
|
org_name: "${f.org_name}"
|
||||||
|
table: "${f.table}"
|
||||||
|
org_table: "${f.org_table}"
|
||||||
|
db: "${f.db}"
|
||||||
|
catalog: "${f.catalog}"
|
||||||
|
def: "${f.def}"
|
||||||
|
length: ${f.length}
|
||||||
|
max_length: ${f.max_length}
|
||||||
|
name_length: ${f.name_length}
|
||||||
|
org_name_length: ${f.org_name_length}
|
||||||
|
table_length: ${f.table_length}
|
||||||
|
org_table_length: ${f.org_table_length}
|
||||||
|
db_length: ${f.db_length}
|
||||||
|
catalog_length: ${f.catalog_length}
|
||||||
|
def_length: ${f.def_length}
|
||||||
|
flags: ${f.flags}
|
||||||
|
decimals: ${f.decimals}
|
||||||
|
charsetnr: ${f.charsetnr}
|
||||||
|
type: ${f.type_.str()}
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
// free - frees the memory used by a result
|
||||||
|
[unsafe]
|
||||||
|
pub fn (r &Result) free() {
|
||||||
|
C.mysql_free_result(r.result)
|
||||||
|
}
|
285
vlib/db/mysql/stmt.c.v
Normal file
285
vlib/db/mysql/stmt.c.v
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
module mysql
|
||||||
|
|
||||||
|
[typedef]
|
||||||
|
struct C.MYSQL_STMT {
|
||||||
|
mysql &C.MYSQL
|
||||||
|
stmt_id u32
|
||||||
|
}
|
||||||
|
|
||||||
|
[typedef]
|
||||||
|
struct C.MYSQL_BIND {
|
||||||
|
mut:
|
||||||
|
buffer_type int
|
||||||
|
buffer voidptr
|
||||||
|
buffer_length u32
|
||||||
|
length &u32
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
mysql_type_decimal = C.MYSQL_TYPE_DECIMAL
|
||||||
|
mysql_type_tiny = C.MYSQL_TYPE_TINY
|
||||||
|
mysql_type_short = C.MYSQL_TYPE_SHORT
|
||||||
|
mysql_type_long = C.MYSQL_TYPE_LONG
|
||||||
|
mysql_type_float = C.MYSQL_TYPE_FLOAT
|
||||||
|
mysql_type_double = C.MYSQL_TYPE_DOUBLE
|
||||||
|
mysql_type_null = C.MYSQL_TYPE_NULL
|
||||||
|
mysql_type_timestamp = C.MYSQL_TYPE_TIMESTAMP
|
||||||
|
mysql_type_longlong = C.MYSQL_TYPE_LONGLONG
|
||||||
|
mysql_type_int24 = C.MYSQL_TYPE_INT24
|
||||||
|
mysql_type_date = C.MYSQL_TYPE_DATE
|
||||||
|
mysql_type_time = C.MYSQL_TYPE_TIME
|
||||||
|
mysql_type_datetime = C.MYSQL_TYPE_DATETIME
|
||||||
|
mysql_type_year = C.MYSQL_TYPE_YEAR
|
||||||
|
mysql_type_varchar = C.MYSQL_TYPE_VARCHAR
|
||||||
|
mysql_type_bit = C.MYSQL_TYPE_BIT
|
||||||
|
mysql_type_timestamp22 = C.MYSQL_TYPE_TIMESTAMP
|
||||||
|
mysql_type_json = C.MYSQL_TYPE_JSON
|
||||||
|
mysql_type_newdecimal = C.MYSQL_TYPE_NEWDECIMAL
|
||||||
|
mysql_type_enum = C.MYSQL_TYPE_ENUM
|
||||||
|
mysql_type_set = C.MYSQL_TYPE_SET
|
||||||
|
mysql_type_tiny_blob = C.MYSQL_TYPE_TINY_BLOB
|
||||||
|
mysql_type_medium_blob = C.MYSQL_TYPE_MEDIUM_BLOB
|
||||||
|
mysql_type_long_blob = C.MYSQL_TYPE_LONG_BLOB
|
||||||
|
mysql_type_blob = C.MYSQL_TYPE_BLOB
|
||||||
|
mysql_type_var_string = C.MYSQL_TYPE_VAR_STRING
|
||||||
|
mysql_type_string = C.MYSQL_TYPE_STRING
|
||||||
|
mysql_type_geometry = C.MYSQL_TYPE_GEOMETRY
|
||||||
|
mysql_no_data = C.MYSQL_NO_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
fn C.mysql_stmt_init(&C.MYSQL) &C.MYSQL_STMT
|
||||||
|
fn C.mysql_stmt_prepare(&C.MYSQL_STMT, &char, u32) int
|
||||||
|
fn C.mysql_stmt_bind_param(&C.MYSQL_STMT, &C.MYSQL_BIND) bool
|
||||||
|
fn C.mysql_stmt_execute(&C.MYSQL_STMT) int
|
||||||
|
fn C.mysql_stmt_close(&C.MYSQL_STMT) bool
|
||||||
|
fn C.mysql_stmt_free_result(&C.MYSQL_STMT) bool
|
||||||
|
fn C.mysql_stmt_error(&C.MYSQL_STMT) &char
|
||||||
|
fn C.mysql_stmt_result_metadata(&C.MYSQL_STMT) &C.MYSQL_RES
|
||||||
|
|
||||||
|
fn C.mysql_stmt_field_count(&C.MYSQL_STMT) u16
|
||||||
|
fn C.mysql_stmt_bind_result(&C.MYSQL_STMT, &C.MYSQL_BIND) bool
|
||||||
|
fn C.mysql_stmt_fetch(&C.MYSQL_STMT) int
|
||||||
|
fn C.mysql_stmt_next_result(&C.MYSQL_STMT) int
|
||||||
|
fn C.mysql_stmt_store_result(&C.MYSQL_STMT) int
|
||||||
|
|
||||||
|
pub struct Stmt {
|
||||||
|
stmt &C.MYSQL_STMT = &C.MYSQL_STMT(0)
|
||||||
|
query string
|
||||||
|
mut:
|
||||||
|
binds []C.MYSQL_BIND
|
||||||
|
res []C.MYSQL_BIND
|
||||||
|
}
|
||||||
|
|
||||||
|
// str returns a text representation of the given mysql statement `s`
|
||||||
|
pub fn (s &Stmt) str() string {
|
||||||
|
return 'mysql.Stmt{ stmt: ${voidptr(s.stmt):x}, query: `${s.query}`, binds.len: ${s.binds.len}, res.len: ${s.res.len} }'
|
||||||
|
}
|
||||||
|
|
||||||
|
// init_stmt creates a new statement, given the `query`
|
||||||
|
pub fn (db Connection) init_stmt(query string) Stmt {
|
||||||
|
return Stmt{
|
||||||
|
stmt: C.mysql_stmt_init(db.conn)
|
||||||
|
query: query
|
||||||
|
binds: []C.MYSQL_BIND{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare a statement for execution
|
||||||
|
pub fn (stmt Stmt) prepare() ! {
|
||||||
|
res := C.mysql_stmt_prepare(stmt.stmt, stmt.query.str, stmt.query.len)
|
||||||
|
if res != 0 && stmt.get_error_msg() != '' {
|
||||||
|
return stmt.error(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_params binds all the parameters in `stmt`
|
||||||
|
pub fn (stmt Stmt) bind_params() ! {
|
||||||
|
res := C.mysql_stmt_bind_param(stmt.stmt, unsafe { &C.MYSQL_BIND(stmt.binds.data) })
|
||||||
|
if res && stmt.get_error_msg() != '' {
|
||||||
|
return stmt.error(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute executes the given `stmt` and waits for the result
|
||||||
|
pub fn (stmt Stmt) execute() !int {
|
||||||
|
res := C.mysql_stmt_execute(stmt.stmt)
|
||||||
|
if res != 0 && stmt.get_error_msg() != '' {
|
||||||
|
return stmt.error(res)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// next retrieves the next available result from the execution of `stmt`
|
||||||
|
pub fn (stmt Stmt) next() !int {
|
||||||
|
res := C.mysql_stmt_next_result(stmt.stmt)
|
||||||
|
if res > 0 && stmt.get_error_msg() != '' {
|
||||||
|
return stmt.error(res)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// gen_metadata executes mysql_stmt_result_metadata over the given `stmt`
|
||||||
|
// It requires that the statement has produced a result set, since the metadata will be for that result set.
|
||||||
|
// See https://dev.mysql.com/doc/c-api/5.7/en/mysql-stmt-result-metadata.html
|
||||||
|
pub fn (stmt Stmt) gen_metadata() &C.MYSQL_RES {
|
||||||
|
return C.mysql_stmt_result_metadata(stmt.stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch_fields retrieves the fields from the metadata result of the execution of `stmt`.
|
||||||
|
// See https://dev.mysql.com/doc/c-api/5.7/en/mysql-fetch-fields.html
|
||||||
|
// See also Result.n_fields for the size of the returned C array.
|
||||||
|
pub fn (stmt Stmt) fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD {
|
||||||
|
return C.mysql_fetch_fields(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch_stmt fetches the next row in the result set. It returns the status of the execution of mysql_stmt_fetch .
|
||||||
|
// See https://dev.mysql.com/doc/c-api/5.7/en/mysql-stmt-fetch.html
|
||||||
|
pub fn (stmt Stmt) fetch_stmt() !int {
|
||||||
|
res := C.mysql_stmt_fetch(stmt.stmt)
|
||||||
|
if res !in [0, 100] && stmt.get_error_msg() != '' {
|
||||||
|
return stmt.error(res)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// close disposes the prepared `stmt`. The statement becomes invalid, and should not be used anymore after this call.
|
||||||
|
// If the current statement has pending or unread results, this method cancels them too.
|
||||||
|
// See https://dev.mysql.com/doc/c-api/5.7/en/mysql-stmt-close.html
|
||||||
|
pub fn (stmt Stmt) close() ! {
|
||||||
|
if !C.mysql_stmt_close(stmt.stmt) && stmt.get_error_msg() != '' {
|
||||||
|
return stmt.error(1)
|
||||||
|
}
|
||||||
|
if !C.mysql_stmt_free_result(stmt.stmt) && stmt.get_error_msg() != '' {
|
||||||
|
return stmt.error(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt Stmt) get_error_msg() string {
|
||||||
|
return unsafe { cstring_to_vstring(&char(C.mysql_stmt_error(stmt.stmt))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// error returns a proper V error with a human readable description, given the error code returned by MySQL
|
||||||
|
pub fn (stmt Stmt) error(code int) IError {
|
||||||
|
msg := stmt.get_error_msg()
|
||||||
|
return &SQLError{
|
||||||
|
msg: '${msg} (${code}) (${stmt.query})'
|
||||||
|
code: code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt Stmt) get_field_count() u16 {
|
||||||
|
return C.mysql_stmt_field_count(stmt.stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_bool binds a single boolean value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_bool(b &bool) {
|
||||||
|
stmt.bind(mysql.mysql_type_tiny, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_byte binds a single byte value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_byte(b &byte) {
|
||||||
|
stmt.bind(mysql.mysql_type_tiny, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_u8 binds a single u8 value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_u8(b &u8) {
|
||||||
|
stmt.bind(mysql.mysql_type_tiny, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_i8 binds a single i8 value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_i8(b &i8) {
|
||||||
|
stmt.bind(mysql.mysql_type_tiny, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_i16 binds a single i16 value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_i16(b &i16) {
|
||||||
|
stmt.bind(mysql.mysql_type_short, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_u16 binds a single u16 value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_u16(b &u16) {
|
||||||
|
stmt.bind(mysql.mysql_type_short, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_int binds a single int value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_int(b &int) {
|
||||||
|
stmt.bind(mysql.mysql_type_long, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_u32 binds a single u32 value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_u32(b &u32) {
|
||||||
|
stmt.bind(mysql.mysql_type_long, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_i64 binds a single i64 value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_i64(b &i64) {
|
||||||
|
stmt.bind(mysql.mysql_type_longlong, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_u64 binds a single u64 value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_u64(b &u64) {
|
||||||
|
stmt.bind(mysql.mysql_type_longlong, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_f32 binds a single f32 value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_f32(b &f32) {
|
||||||
|
stmt.bind(mysql.mysql_type_float, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_f64 binds a single f64 value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_f64(b &f64) {
|
||||||
|
stmt.bind(mysql.mysql_type_double, b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_text binds a single string value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_text(b string) {
|
||||||
|
stmt.bind(mysql.mysql_type_string, b.str, u32(b.len))
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind binds a single value pointed by `buffer`, to the statement `stmt`. The buffer length must be passed as well in `buf_len`.
|
||||||
|
// Note: it is more convenient to use one of the other bind_XYZ methods.
|
||||||
|
pub fn (mut stmt Stmt) bind(typ int, buffer voidptr, buf_len u32) {
|
||||||
|
stmt.binds << C.MYSQL_BIND{
|
||||||
|
buffer_type: typ
|
||||||
|
buffer: buffer
|
||||||
|
buffer_length: buf_len
|
||||||
|
length: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_res will store one result in the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_res(fields &C.MYSQL_FIELD, dataptr []&u8, lens []u32, num_fields int) {
|
||||||
|
for i in 0 .. num_fields {
|
||||||
|
len := unsafe { FieldType(fields[i].@type).get_len() }
|
||||||
|
stmt.res << C.MYSQL_BIND{
|
||||||
|
buffer_type: unsafe { fields[i].@type }
|
||||||
|
buffer: dataptr[i]
|
||||||
|
length: &lens[i]
|
||||||
|
buffer_length: len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind_result_buffer binds one result value, by calling mysql_stmt_bind_result .
|
||||||
|
// See https://dev.mysql.com/doc/c-api/8.0/en/mysql-stmt-bind-result.html
|
||||||
|
pub fn (mut stmt Stmt) bind_result_buffer() ! {
|
||||||
|
res := C.mysql_stmt_bind_result(stmt.stmt, unsafe { &C.MYSQL_BIND(stmt.res.data) })
|
||||||
|
if res && stmt.get_error_msg() != '' {
|
||||||
|
return stmt.error(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store_result will *buffer the complete result set* from the execution of `stmt` *on the client side*.
|
||||||
|
// Note: result sets are produced by calling mysql_stmt_execute() to executed prepared statements for SQL
|
||||||
|
// statements such as SELECT, SHOW, DESCRIBE, and EXPLAIN.
|
||||||
|
// By default, result sets for successfully executed prepared statements are *not buffered on the client*,
|
||||||
|
// and mysql_stmt_fetch() fetches them one at a time from the server.
|
||||||
|
// Note 2: call store_result, *after* binding data buffers with bind_result_buffer,
|
||||||
|
// and *before* calling fetch_stmt to fetch rows.
|
||||||
|
// See https://dev.mysql.com/doc/c-api/8.0/en/mysql-stmt-store-result.html
|
||||||
|
pub fn (mut stmt Stmt) store_result() ! {
|
||||||
|
res := C.mysql_stmt_store_result(stmt.stmt)
|
||||||
|
if res != 0 && stmt.get_error_msg() != '' {
|
||||||
|
return stmt.error(res)
|
||||||
|
}
|
||||||
|
}
|
26
vlib/db/mysql/utils.v
Normal file
26
vlib/db/mysql/utils.v
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
module mysql
|
||||||
|
|
||||||
|
// get_error_msg - returns error message from MySQL instance.
|
||||||
|
fn get_error_msg(conn &C.MYSQL) string {
|
||||||
|
return unsafe { C.mysql_error(conn).vstring() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_errno - returns error number from MySQL instance.
|
||||||
|
fn get_errno(conn &C.MYSQL) int {
|
||||||
|
return C.mysql_errno(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve_nil_str - returns an empty string if passed value is a nil pointer.
|
||||||
|
fn resolve_nil_str(ptr &u8) string {
|
||||||
|
if isnil(ptr) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return unsafe { ptr.vstring() }
|
||||||
|
}
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn mystring(b &u8) string {
|
||||||
|
unsafe {
|
||||||
|
return b.vstring()
|
||||||
|
}
|
||||||
|
}
|
52
vlib/db/pg/README.md
Normal file
52
vlib/db/pg/README.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
## Description:
|
||||||
|
|
||||||
|
`pg` is a wrapper for the PostgreSQL client library. It provides access to a PostgreSQL
|
||||||
|
database server.
|
||||||
|
|
||||||
|
Before you can use this module, you must first have PostgreSQL installed on your system.
|
||||||
|
To do this, find your OS and perform the actions listed.
|
||||||
|
|
||||||
|
**NOTE**: These instructions are meant only as a convenience. If your OS is not listed
|
||||||
|
or you need extra help, [go here](https://www.postgresql.org/download/).
|
||||||
|
|
||||||
|
### Fedora 31
|
||||||
|
```
|
||||||
|
sudo dnf install postgresql-server postgresql-contrib
|
||||||
|
sudo systemctl enable postgresql # to autostart on startup
|
||||||
|
sudo systemctl start postgresql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ubuntu/Debian
|
||||||
|
```
|
||||||
|
sudo apt-get install postgresql postgresql-client
|
||||||
|
sudo systemctl enable postgresql # to autostart on startup
|
||||||
|
sudo systemctl start postgresql
|
||||||
|
```
|
||||||
|
|
||||||
|
### MacOSX (Homebrew)
|
||||||
|
```
|
||||||
|
brew install postgresql
|
||||||
|
brew services start postgresql
|
||||||
|
```
|
||||||
|
|
||||||
|
### MacOSX (MacPorts)
|
||||||
|
```
|
||||||
|
gem install pg -- --with-pg-config=/opt/local/lib/postgresql[version number]/bin/pg_config
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing libpq-dev or its equivalent for your OS: ##
|
||||||
|
|
||||||
|
**Ubuntu/Debian**: `sudo apt-get install libpq-dev`
|
||||||
|
|
||||||
|
**Red Hat Linux (RHEL)**: `yum install postgresql-devel`
|
||||||
|
|
||||||
|
**OpenSuse**: `zypper in postgresql-devel`
|
||||||
|
|
||||||
|
**ArchLinux**: `pacman -S postgresql-libs`
|
||||||
|
|
||||||
|
##Getting Started with [PostgreSQL](https://www.postgresqltutorial.com/postgresql-getting-started)
|
||||||
|
|
||||||
|
Read this section to learn how to install and connect to PostgreSQL
|
||||||
|
*[Windows](https://www.postgresqltutorial.com/install-postgresql)*;
|
||||||
|
*[Linux](https://www.postgresqltutorial.com/postgresql-getting-started/install-postgresql-linux)*;
|
||||||
|
*[macOS](https://www.postgresqltutorial.com/postgresql-getting-started/install-postgresql-macos)*.
|
16
vlib/db/pg/compatibility.h
Normal file
16
vlib/db/pg/compatibility.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
#if !defined(PG_VERSION_NUM)
|
||||||
|
#error VERROR_MESSAGE PG_VERSION_NUM is not defined. Please install the development headers for PostgreSQL, they are usually in a package named libpq-dev
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM < 100000
|
||||||
|
#define CONNECTION_CHECK_WRITABLE 9
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM < 100000
|
||||||
|
#define CONNECTION_CONSUME 10
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM < 120000
|
||||||
|
#define CONNECTION_GSS_STARTUP 11
|
||||||
|
#endif
|
171
vlib/db/pg/oid.v
Normal file
171
vlib/db/pg/oid.v
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
module pg
|
||||||
|
|
||||||
|
pub enum Oid {
|
||||||
|
t_bool = 16
|
||||||
|
t_bytea = 17
|
||||||
|
t_char = 18
|
||||||
|
t_name = 19
|
||||||
|
t_int8 = 20
|
||||||
|
t_int2 = 21
|
||||||
|
t_int2vector = 22
|
||||||
|
t_int4 = 23
|
||||||
|
t_regproc = 24
|
||||||
|
t_text = 25
|
||||||
|
t_oid = 26
|
||||||
|
t_tid = 27
|
||||||
|
t_xid = 28
|
||||||
|
t_cid = 29
|
||||||
|
t_vector = 30
|
||||||
|
t_pg_ddl_command = 32
|
||||||
|
t_pg_type = 71
|
||||||
|
t_pg_attribute = 75
|
||||||
|
t_pg_proc = 81
|
||||||
|
t_pg_class = 83
|
||||||
|
t_json = 114
|
||||||
|
t_xml = 142
|
||||||
|
t__xml = 143
|
||||||
|
t_pg_node_tree = 194
|
||||||
|
t__json = 199
|
||||||
|
t_smgr = 210
|
||||||
|
t_index_am_handler = 325
|
||||||
|
t_point = 600
|
||||||
|
t_lseg = 601
|
||||||
|
t_path = 602
|
||||||
|
t_box = 603
|
||||||
|
t_polygon = 604
|
||||||
|
t_line = 628
|
||||||
|
t__line = 629
|
||||||
|
t_cidr = 650
|
||||||
|
t__cidr = 651
|
||||||
|
t_float4 = 700
|
||||||
|
t_float8 = 701
|
||||||
|
t_abstime = 702
|
||||||
|
t_reltime = 703
|
||||||
|
t_tinterval = 704
|
||||||
|
t_unknown = 705
|
||||||
|
t_circle = 718
|
||||||
|
t__circle = 719
|
||||||
|
t_money = 790
|
||||||
|
t__money = 791
|
||||||
|
t_macaddr = 829
|
||||||
|
t_inet = 869
|
||||||
|
t__bool = 1000
|
||||||
|
t__bytea = 1001
|
||||||
|
t__char = 1002
|
||||||
|
t__name = 1003
|
||||||
|
t__int2 = 1005
|
||||||
|
t__int2vector = 1006
|
||||||
|
t__int4 = 1007
|
||||||
|
t__regproc = 1008
|
||||||
|
t__text = 1009
|
||||||
|
t__tid = 1010
|
||||||
|
t__xid = 1011
|
||||||
|
t__cid = 1012
|
||||||
|
t__vector = 1013
|
||||||
|
t__bpchar = 1014
|
||||||
|
t__varchar = 1015
|
||||||
|
t__int8 = 1016
|
||||||
|
t__point = 1017
|
||||||
|
t__lseg = 1018
|
||||||
|
t__path = 1019
|
||||||
|
t__box = 1020
|
||||||
|
t__float4 = 1021
|
||||||
|
t__float8 = 1022
|
||||||
|
t__abstime = 1023
|
||||||
|
t__reltime = 1024
|
||||||
|
t__tinterval = 1025
|
||||||
|
t__polygon = 1027
|
||||||
|
t__ = 1028
|
||||||
|
t_aclitem = 1033
|
||||||
|
t__aclitem = 1034
|
||||||
|
t__macaddr = 1040
|
||||||
|
t__inet = 1041
|
||||||
|
t_bpchar = 1042
|
||||||
|
t_varchar = 1043
|
||||||
|
t_date = 1082
|
||||||
|
t_time = 1083
|
||||||
|
t_timestamp = 1114
|
||||||
|
t__timestamp = 1115
|
||||||
|
t__date = 1182
|
||||||
|
t__time = 1183
|
||||||
|
t_timestamptz = 1184
|
||||||
|
t__timestamptz = 1185
|
||||||
|
t_interval = 1186
|
||||||
|
t__interval = 1187
|
||||||
|
t__numeric = 1231
|
||||||
|
t_pg_database = 1248
|
||||||
|
t__cstring = 1263
|
||||||
|
t_timetz = 1266
|
||||||
|
t__timetz = 1270
|
||||||
|
t_bit = 1560
|
||||||
|
t__bit = 1561
|
||||||
|
t_varbit = 1562
|
||||||
|
t__varbit = 1563
|
||||||
|
t_numeric = 1700
|
||||||
|
t_refcursor = 1790
|
||||||
|
t__refcursor = 2201
|
||||||
|
t_regprocedure = 2202
|
||||||
|
t_regoper = 2203
|
||||||
|
t_regoperator = 2204
|
||||||
|
t_regclass = 2205
|
||||||
|
t_regtype = 2206
|
||||||
|
t__regprocedure = 2207
|
||||||
|
t__regoper = 2208
|
||||||
|
t__regoperator = 2209
|
||||||
|
t__regclass = 2210
|
||||||
|
t__regtype = 2211
|
||||||
|
t_record = 2249
|
||||||
|
t_cstring = 2275
|
||||||
|
t_any = 2276
|
||||||
|
t_anyarray = 2277
|
||||||
|
t_v = 2278
|
||||||
|
t_trigger = 2279
|
||||||
|
t_language_handler = 2280
|
||||||
|
t_internal = 2281
|
||||||
|
t_opaque = 2282
|
||||||
|
t_anyelement = 2283
|
||||||
|
t__record = 2287
|
||||||
|
t_anynonarray = 2776
|
||||||
|
t_pg_authid = 2842
|
||||||
|
t_pg_auth_members = 2843
|
||||||
|
t__txid_snapshot = 2949
|
||||||
|
t_uuid = 2950
|
||||||
|
t__uuid = 2951
|
||||||
|
t_txid_snapshot = 2970
|
||||||
|
t_fdw_handler = 3115
|
||||||
|
t_pg_lsn = 3220
|
||||||
|
t__pg_lsn = 3221
|
||||||
|
t_tsm_handler = 3310
|
||||||
|
t_anyenum = 3500
|
||||||
|
t_tsvector = 3614
|
||||||
|
t_tsquery = 3615
|
||||||
|
t_gtsvector = 3642
|
||||||
|
t__tsvector = 3643
|
||||||
|
t__gtsvector = 3644
|
||||||
|
t__tsquery = 3645
|
||||||
|
t_regconfig = 3734
|
||||||
|
t__regconfig = 3735
|
||||||
|
t_regdictionary = 3769
|
||||||
|
t__regdictionary = 3770
|
||||||
|
t_jsonb = 3802
|
||||||
|
t__jsonb = 3807
|
||||||
|
t_anyrange = 3831
|
||||||
|
t_event_trigger = 3838
|
||||||
|
t_int4range = 3904
|
||||||
|
t__int4range = 3905
|
||||||
|
t_numrange = 3906
|
||||||
|
t__numrange = 3907
|
||||||
|
t_tsrange = 3908
|
||||||
|
t__tsrange = 3909
|
||||||
|
t_tstzrange = 3910
|
||||||
|
t__tstzrange = 3911
|
||||||
|
t_daterange = 3912
|
||||||
|
t__daterange = 3913
|
||||||
|
t_int8range = 3926
|
||||||
|
t__int8range = 3927
|
||||||
|
t_pg_shseclabel = 4066
|
||||||
|
t_regnamespace = 4089
|
||||||
|
t__regnamespace = 4090
|
||||||
|
t_regrole = 4096
|
||||||
|
t__regrole = 4097
|
||||||
|
}
|
298
vlib/db/pg/orm.v
Normal file
298
vlib/db/pg/orm.v
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
module pg
|
||||||
|
|
||||||
|
import orm
|
||||||
|
import time
|
||||||
|
import net.conv
|
||||||
|
|
||||||
|
// sql expr
|
||||||
|
|
||||||
|
// @select is used internally by V's ORM for processing `SELECT ` queries
|
||||||
|
pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive {
|
||||||
|
query := orm.orm_select_gen(config, '"', true, '$', 1, where)
|
||||||
|
|
||||||
|
res := pg_stmt_worker(db, query, where, data)!
|
||||||
|
|
||||||
|
mut ret := [][]orm.Primitive{}
|
||||||
|
|
||||||
|
if config.is_count {
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in res {
|
||||||
|
mut row_data := []orm.Primitive{}
|
||||||
|
for i, val in row.vals {
|
||||||
|
field := str_to_primitive(val, config.types[i])!
|
||||||
|
row_data << field
|
||||||
|
}
|
||||||
|
ret << row_data
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// sql stmt
|
||||||
|
|
||||||
|
// insert is used internally by V's ORM for processing `INSERT ` queries
|
||||||
|
pub fn (db DB) insert(table string, data orm.QueryData) ! {
|
||||||
|
query, converted_data := orm.orm_stmt_gen(table, '"', .insert, true, '$', 1, data,
|
||||||
|
orm.QueryData{})
|
||||||
|
pg_stmt_worker(db, query, converted_data, orm.QueryData{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// update is used internally by V's ORM for processing `UPDATE ` queries
|
||||||
|
pub fn (db DB) update(table string, data orm.QueryData, where orm.QueryData) ! {
|
||||||
|
query, _ := orm.orm_stmt_gen(table, '"', .update, true, '$', 1, data, where)
|
||||||
|
pg_stmt_worker(db, query, data, where)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete is used internally by V's ORM for processing `DELETE ` queries
|
||||||
|
pub fn (db DB) delete(table string, where orm.QueryData) ! {
|
||||||
|
query, _ := orm.orm_stmt_gen(table, '"', .delete, true, '$', 1, orm.QueryData{}, where)
|
||||||
|
pg_stmt_worker(db, query, orm.QueryData{}, where)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// last_id is used internally by V's ORM for post-processing `INSERT ` queries
|
||||||
|
pub fn (db DB) last_id() orm.Primitive {
|
||||||
|
query := 'SELECT LASTVAL();'
|
||||||
|
id := db.q_int(query) or { 0 }
|
||||||
|
return orm.Primitive(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DDL (table creation/destroying etc)
|
||||||
|
|
||||||
|
// create is used internally by V's ORM for processing table creation queries (DDL)
|
||||||
|
pub fn (db DB) create(table string, fields []orm.TableField) ! {
|
||||||
|
query := orm.orm_table_gen(table, '"', true, 0, fields, pg_type_from_v, false) or { return err }
|
||||||
|
pg_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// drop is used internally by V's ORM for processing table destroying queries (DDL)
|
||||||
|
pub fn (db DB) drop(table string) ! {
|
||||||
|
query := 'DROP TABLE "${table}";'
|
||||||
|
pg_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// utils
|
||||||
|
|
||||||
|
fn pg_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ![]Row {
|
||||||
|
mut param_types := []u32{}
|
||||||
|
mut param_vals := []&char{}
|
||||||
|
mut param_lens := []int{}
|
||||||
|
mut param_formats := []int{}
|
||||||
|
|
||||||
|
pg_stmt_binder(mut param_types, mut param_vals, mut param_lens, mut param_formats,
|
||||||
|
data)
|
||||||
|
pg_stmt_binder(mut param_types, mut param_vals, mut param_lens, mut param_formats,
|
||||||
|
where)
|
||||||
|
|
||||||
|
res := C.PQexecParams(db.conn, &char(query.str), param_vals.len, param_types.data,
|
||||||
|
param_vals.data, param_lens.data, param_formats.data, 0) // here, the last 0 means require text results, 1 - binary results
|
||||||
|
return db.handle_error_or_result(res, 'orm_stmt_worker')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pg_stmt_binder(mut types []u32, mut vals []&char, mut lens []int, mut formats []int, d orm.QueryData) {
|
||||||
|
for data in d.data {
|
||||||
|
pg_stmt_match(mut types, mut vals, mut lens, mut formats, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pg_stmt_match(mut types []u32, mut vals []&char, mut lens []int, mut formats []int, data orm.Primitive) {
|
||||||
|
match data {
|
||||||
|
bool {
|
||||||
|
types << u32(Oid.t_bool)
|
||||||
|
vals << &char(&data)
|
||||||
|
lens << int(sizeof(bool))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
u8 {
|
||||||
|
types << u32(Oid.t_char)
|
||||||
|
vals << &char(&data)
|
||||||
|
lens << int(sizeof(u8))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
u16 {
|
||||||
|
types << u32(Oid.t_int2)
|
||||||
|
num := conv.htn16(data)
|
||||||
|
vals << &char(&num)
|
||||||
|
lens << int(sizeof(u16))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
u32 {
|
||||||
|
types << u32(Oid.t_int4)
|
||||||
|
num := conv.htn32(data)
|
||||||
|
vals << &char(&num)
|
||||||
|
lens << int(sizeof(u32))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
u64 {
|
||||||
|
types << u32(Oid.t_int8)
|
||||||
|
num := conv.htn64(data)
|
||||||
|
vals << &char(&num)
|
||||||
|
lens << int(sizeof(u64))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
i8 {
|
||||||
|
types << u32(Oid.t_char)
|
||||||
|
vals << &char(&data)
|
||||||
|
lens << int(sizeof(i8))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
i16 {
|
||||||
|
types << u32(Oid.t_int2)
|
||||||
|
num := conv.htn16(u16(data))
|
||||||
|
vals << &char(&num)
|
||||||
|
lens << int(sizeof(i16))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
int {
|
||||||
|
types << u32(Oid.t_int4)
|
||||||
|
num := conv.htn32(u32(data))
|
||||||
|
vals << &char(&num)
|
||||||
|
lens << int(sizeof(int))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
i64 {
|
||||||
|
types << u32(Oid.t_int8)
|
||||||
|
num := conv.htn64(u64(data))
|
||||||
|
vals << &char(&num)
|
||||||
|
lens << int(sizeof(i64))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
f32 {
|
||||||
|
types << u32(Oid.t_float4)
|
||||||
|
vals << &char(&data)
|
||||||
|
lens << int(sizeof(f32))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
f64 {
|
||||||
|
types << u32(Oid.t_float8)
|
||||||
|
vals << &char(&data)
|
||||||
|
lens << int(sizeof(f64))
|
||||||
|
formats << 1
|
||||||
|
}
|
||||||
|
string {
|
||||||
|
// If paramTypes is NULL, or any particular element in the array is zero,
|
||||||
|
// the server infers a data type for the parameter symbol in the same way
|
||||||
|
// it would do for an untyped literal string.
|
||||||
|
types << u32(0)
|
||||||
|
vals << &char(data.str)
|
||||||
|
lens << data.len
|
||||||
|
formats << 0
|
||||||
|
}
|
||||||
|
time.Time {
|
||||||
|
datetime := data.format_ss()
|
||||||
|
types << u32(0)
|
||||||
|
vals << &char(datetime.str)
|
||||||
|
lens << datetime.len
|
||||||
|
formats << 0
|
||||||
|
}
|
||||||
|
orm.InfixType {
|
||||||
|
pg_stmt_match(mut types, mut vals, mut lens, mut formats, data.right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pg_type_from_v(typ int) !string {
|
||||||
|
str := match typ {
|
||||||
|
orm.type_idx['i8'], orm.type_idx['i16'], orm.type_idx['u8'], orm.type_idx['u16'] {
|
||||||
|
'SMALLINT'
|
||||||
|
}
|
||||||
|
orm.type_idx['bool'] {
|
||||||
|
'BOOLEAN'
|
||||||
|
}
|
||||||
|
orm.type_idx['int'], orm.type_idx['u32'] {
|
||||||
|
'INT'
|
||||||
|
}
|
||||||
|
orm.time {
|
||||||
|
'TIMESTAMP'
|
||||||
|
}
|
||||||
|
orm.type_idx['i64'], orm.type_idx['u64'] {
|
||||||
|
'BIGINT'
|
||||||
|
}
|
||||||
|
orm.float[0] {
|
||||||
|
'REAL'
|
||||||
|
}
|
||||||
|
orm.float[1] {
|
||||||
|
'DOUBLE PRECISION'
|
||||||
|
}
|
||||||
|
orm.type_string {
|
||||||
|
'TEXT'
|
||||||
|
}
|
||||||
|
orm.serial {
|
||||||
|
'SERIAL'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if str == '' {
|
||||||
|
return error('Unknown type ${typ}')
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str_to_primitive(str string, typ int) !orm.Primitive {
|
||||||
|
match typ {
|
||||||
|
// bool
|
||||||
|
orm.type_idx['bool'] {
|
||||||
|
return orm.Primitive(str == 't')
|
||||||
|
}
|
||||||
|
// i8
|
||||||
|
orm.type_idx['i8'] {
|
||||||
|
return orm.Primitive(str.i8())
|
||||||
|
}
|
||||||
|
// i16
|
||||||
|
orm.type_idx['i16'] {
|
||||||
|
return orm.Primitive(str.i16())
|
||||||
|
}
|
||||||
|
// int
|
||||||
|
orm.type_idx['int'] {
|
||||||
|
return orm.Primitive(str.int())
|
||||||
|
}
|
||||||
|
// i64
|
||||||
|
orm.type_idx['i64'] {
|
||||||
|
return orm.Primitive(str.i64())
|
||||||
|
}
|
||||||
|
// u8
|
||||||
|
orm.type_idx['u8'] {
|
||||||
|
data := str.i8()
|
||||||
|
return orm.Primitive(*unsafe { &u8(&data) })
|
||||||
|
}
|
||||||
|
// u16
|
||||||
|
orm.type_idx['u16'] {
|
||||||
|
data := str.i16()
|
||||||
|
return orm.Primitive(*unsafe { &u16(&data) })
|
||||||
|
}
|
||||||
|
// u32
|
||||||
|
orm.type_idx['u32'] {
|
||||||
|
data := str.int()
|
||||||
|
return orm.Primitive(*unsafe { &u32(&data) })
|
||||||
|
}
|
||||||
|
// u64
|
||||||
|
orm.type_idx['u64'] {
|
||||||
|
data := str.i64()
|
||||||
|
return orm.Primitive(*unsafe { &u64(&data) })
|
||||||
|
}
|
||||||
|
// f32
|
||||||
|
orm.type_idx['f32'] {
|
||||||
|
return orm.Primitive(str.f32())
|
||||||
|
}
|
||||||
|
// f64
|
||||||
|
orm.type_idx['f64'] {
|
||||||
|
return orm.Primitive(str.f64())
|
||||||
|
}
|
||||||
|
orm.type_string {
|
||||||
|
return orm.Primitive(str)
|
||||||
|
}
|
||||||
|
orm.time {
|
||||||
|
if str.contains_any(' /:-') {
|
||||||
|
date_time_str := time.parse(str)!
|
||||||
|
return orm.Primitive(date_time_str)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := str.int()
|
||||||
|
return orm.Primitive(time.unix(timestamp))
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
return error('Unknown field type ${typ}')
|
||||||
|
}
|
348
vlib/db/pg/pg.v
Normal file
348
vlib/db/pg/pg.v
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
module pg
|
||||||
|
|
||||||
|
import io
|
||||||
|
|
||||||
|
$if $pkgconfig('libpq') {
|
||||||
|
#pkgconfig --cflags --libs libpq
|
||||||
|
} $else {
|
||||||
|
#flag -lpq
|
||||||
|
#flag linux -I/usr/include/postgresql
|
||||||
|
|
||||||
|
#flag darwin -I/opt/local/include/postgresql11
|
||||||
|
#flag darwin -L/opt/local/lib/postgresql11
|
||||||
|
|
||||||
|
#flag darwin -I/usr/local/opt/libpq/include
|
||||||
|
#flag darwin -L/usr/local/opt/libpq/lib
|
||||||
|
|
||||||
|
#flag darwin -I/opt/homebrew/include
|
||||||
|
#flag darwin -L/opt/homebrew/lib
|
||||||
|
|
||||||
|
#flag darwin -I/opt/homebrew/opt/libpq/include
|
||||||
|
#flag darwin -L/opt/homebrew/opt/libpq/lib
|
||||||
|
|
||||||
|
#flag windows -I @VEXEROOT/thirdparty/pg/include
|
||||||
|
#flag windows -L @VEXEROOT/thirdparty/pg/win64
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostgreSQL Source Code
|
||||||
|
// https://doxygen.postgresql.org/libpq-fe_8h.html
|
||||||
|
#include <libpq-fe.h>
|
||||||
|
|
||||||
|
// for PG_VERSION_NUM, which is defined everywhere at least since PG 9.5
|
||||||
|
#include <pg_config.h>
|
||||||
|
|
||||||
|
// for orm
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#include "@VMODROOT/vlib/db/pg/compatibility.h"
|
||||||
|
|
||||||
|
pub struct DB {
|
||||||
|
mut:
|
||||||
|
conn voidptr = unsafe { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Row {
|
||||||
|
pub mut:
|
||||||
|
vals []string
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub:
|
||||||
|
host string = 'localhost'
|
||||||
|
port int = 5432
|
||||||
|
user string
|
||||||
|
password string
|
||||||
|
dbname string
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
struct C.pg_result {}
|
||||||
|
|
||||||
|
struct C.pg_conn {}
|
||||||
|
|
||||||
|
[typedef]
|
||||||
|
pub struct C.PGresult {}
|
||||||
|
|
||||||
|
[typedef]
|
||||||
|
pub struct C.PGconn {}
|
||||||
|
|
||||||
|
pub enum ConnStatusType {
|
||||||
|
ok = C.CONNECTION_OK
|
||||||
|
bad = C.CONNECTION_BAD
|
||||||
|
// Non-blocking mode only below here
|
||||||
|
// The existence of these should never be relied upon - they should only be used for user feedback or similar purposes.
|
||||||
|
started = C.CONNECTION_STARTED // Waiting for connection to be made.
|
||||||
|
made = C.CONNECTION_MADE // Connection OK; waiting to send.
|
||||||
|
awaiting_response = C.CONNECTION_AWAITING_RESPONSE // Waiting for a response from the postmaster.
|
||||||
|
auth_ok = C.CONNECTION_AUTH_OK // Received authentication; waiting for backend startup.
|
||||||
|
setenv = C.CONNECTION_SETENV // Negotiating environment.
|
||||||
|
ssl_startup = C.CONNECTION_SSL_STARTUP // Negotiating SSL.
|
||||||
|
needed = C.CONNECTION_NEEDED // Internal state: connect() needed . Available in PG 8
|
||||||
|
check_writable = C.CONNECTION_CHECK_WRITABLE // Check if we could make a writable connection. Available since PG 10
|
||||||
|
consume = C.CONNECTION_CONSUME // Wait for any pending message and consume them. Available since PG 10
|
||||||
|
gss_startup = C.CONNECTION_GSS_STARTUP // Negotiating GSSAPI; available since PG 12
|
||||||
|
}
|
||||||
|
|
||||||
|
[typedef]
|
||||||
|
pub enum ExecStatusType {
|
||||||
|
empty_query = C.PGRES_EMPTY_QUERY // empty query string was executed
|
||||||
|
command_ok = C.PGRES_COMMAND_OK // a query command that doesn't return anything was executed properly by the backend
|
||||||
|
tuples_ok = C.PGRES_TUPLES_OK // a query command that returns tuples was executed properly by the backend, PGresult contains the result tuples
|
||||||
|
copy_out = C.PGRES_COPY_OUT // Copy Out data transfer in progress
|
||||||
|
copy_in = C.PGRES_COPY_IN // Copy In data transfer in progress
|
||||||
|
bad_response = C.PGRES_BAD_RESPONSE // an unexpected response was recv'd from the backend
|
||||||
|
nonfatal_error = C.PGRES_NONFATAL_ERROR // notice or warning message
|
||||||
|
fatal_error = C.PGRES_FATAL_ERROR // query failed
|
||||||
|
copy_both = C.PGRES_COPY_BOTH // Copy In/Out data transfer in progress
|
||||||
|
single_tuple = C.PGRES_SINGLE_TUPLE // single tuple from larger resultset
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
fn C.PQconnectdb(const_conninfo &char) &C.PGconn
|
||||||
|
|
||||||
|
fn C.PQstatus(const_conn &C.PGconn) int
|
||||||
|
|
||||||
|
fn C.PQerrorMessage(const_conn &C.PGconn) &char
|
||||||
|
|
||||||
|
fn C.PQexec(res &C.PGconn, const_query &char) &C.PGresult
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
fn C.PQgetvalue(const_res &C.PGresult, int, int) &char
|
||||||
|
|
||||||
|
fn C.PQresultStatus(const_res &C.PGresult) int
|
||||||
|
|
||||||
|
fn C.PQntuples(const_res &C.PGresult) int
|
||||||
|
|
||||||
|
fn C.PQnfields(const_res &C.PGresult) int
|
||||||
|
|
||||||
|
// Params:
|
||||||
|
// const Oid *paramTypes
|
||||||
|
// const char *const *paramValues
|
||||||
|
// const int *paramLengths
|
||||||
|
// const int *paramFormats
|
||||||
|
fn C.PQexecParams(conn &C.PGconn, const_command &char, nParams int, const_paramTypes &int, const_paramValues &char, const_paramLengths &int, const_paramFormats &int, resultFormat int) &C.PGresult
|
||||||
|
|
||||||
|
fn C.PQputCopyData(conn &C.PGconn, const_buffer &char, nbytes int) int
|
||||||
|
|
||||||
|
fn C.PQputCopyEnd(conn &C.PGconn, const_errmsg &char) int
|
||||||
|
|
||||||
|
fn C.PQgetCopyData(conn &C.PGconn, buffer &&char, async int) int
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
|
||||||
|
fn C.PQclear(res &C.PGresult)
|
||||||
|
|
||||||
|
fn C.PQfreemem(ptr voidptr)
|
||||||
|
|
||||||
|
fn C.PQfinish(conn &C.PGconn)
|
||||||
|
|
||||||
|
// connect makes a new connection to the database server using
|
||||||
|
// the parameters from the `Config` structure, returning
|
||||||
|
// a connection error when something goes wrong
|
||||||
|
pub fn connect(config Config) !DB {
|
||||||
|
conninfo := 'host=${config.host} port=${config.port} user=${config.user} dbname=${config.dbname} password=${config.password}'
|
||||||
|
conn := C.PQconnectdb(&char(conninfo.str))
|
||||||
|
if conn == 0 {
|
||||||
|
return error('libpq memory allocation error')
|
||||||
|
}
|
||||||
|
status := unsafe { ConnStatusType(C.PQstatus(conn)) }
|
||||||
|
if status != .ok {
|
||||||
|
// We force the construction of a new string as the
|
||||||
|
// error message will be freed by the next `PQfinish`
|
||||||
|
// call
|
||||||
|
c_error_msg := unsafe { C.PQerrorMessage(conn).vstring() }
|
||||||
|
error_msg := '${c_error_msg}'
|
||||||
|
C.PQfinish(conn)
|
||||||
|
return error('Connection to a PG database failed: ${error_msg}')
|
||||||
|
}
|
||||||
|
return DB{
|
||||||
|
conn: conn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn res_to_rows(res voidptr) []Row {
|
||||||
|
nr_rows := C.PQntuples(res)
|
||||||
|
nr_cols := C.PQnfields(res)
|
||||||
|
|
||||||
|
mut rows := []Row{}
|
||||||
|
for i in 0 .. nr_rows {
|
||||||
|
mut row := Row{}
|
||||||
|
for j in 0 .. nr_cols {
|
||||||
|
val := C.PQgetvalue(res, i, j)
|
||||||
|
sval := unsafe { val.vstring() }
|
||||||
|
row.vals << sval
|
||||||
|
}
|
||||||
|
rows << row
|
||||||
|
}
|
||||||
|
|
||||||
|
C.PQclear(res)
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
// close frees the underlying resource allocated by the database connection
|
||||||
|
pub fn (db DB) close() {
|
||||||
|
C.PQfinish(db.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// q_int submit a command to the database server and
|
||||||
|
// returns an the first field in the first tuple
|
||||||
|
// converted to an int. If no row is found or on
|
||||||
|
// command failure, an error is returned
|
||||||
|
pub fn (db DB) q_int(query string) !int {
|
||||||
|
rows := db.exec(query)!
|
||||||
|
if rows.len == 0 {
|
||||||
|
return error('q_int "${query}" not found')
|
||||||
|
}
|
||||||
|
row := rows[0]
|
||||||
|
if row.vals.len == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val := row.vals[0]
|
||||||
|
return val.int()
|
||||||
|
}
|
||||||
|
|
||||||
|
// q_string submit a command to the database server and
|
||||||
|
// returns an the first field in the first tuple
|
||||||
|
// as a string. If no row is found or on
|
||||||
|
// command failure, an error is returned
|
||||||
|
pub fn (db DB) q_string(query string) !string {
|
||||||
|
rows := db.exec(query)!
|
||||||
|
if rows.len == 0 {
|
||||||
|
return error('q_string "${query}" not found')
|
||||||
|
}
|
||||||
|
row := rows[0]
|
||||||
|
if row.vals.len == 0 {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
val := row.vals[0]
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// q_strings submit a command to the database server and
|
||||||
|
// returns the resulting row set. Alias of `exec`
|
||||||
|
pub fn (db DB) q_strings(query string) ![]Row {
|
||||||
|
return db.exec(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec submits a command to the database server and wait for the result, returning an error on failure and a row set on success
|
||||||
|
pub fn (db DB) exec(query string) ![]Row {
|
||||||
|
res := C.PQexec(db.conn, &char(query.str))
|
||||||
|
return db.handle_error_or_result(res, 'exec')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rows_first_or_empty(rows []Row) !Row {
|
||||||
|
if rows.len == 0 {
|
||||||
|
return error('no row')
|
||||||
|
}
|
||||||
|
return rows[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec_one executes a query and returns its first row as a result, or an error on failure
|
||||||
|
pub fn (db DB) exec_one(query string) !Row {
|
||||||
|
res := C.PQexec(db.conn, &char(query.str))
|
||||||
|
e := unsafe { C.PQerrorMessage(db.conn).vstring() }
|
||||||
|
if e != '' {
|
||||||
|
return error('pg exec error: "${e}"')
|
||||||
|
}
|
||||||
|
row := rows_first_or_empty(res_to_rows(res))!
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec_param_many executes a query with the provided parameters
|
||||||
|
pub fn (db DB) exec_param_many(query string, params []string) ![]Row {
|
||||||
|
unsafe {
|
||||||
|
mut param_vals := []&char{len: params.len}
|
||||||
|
for i in 0 .. params.len {
|
||||||
|
param_vals[i] = &char(params[i].str)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := C.PQexecParams(db.conn, &char(query.str), params.len, 0, param_vals.data,
|
||||||
|
0, 0, 0)
|
||||||
|
return db.handle_error_or_result(res, 'exec_param_many')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec_param2 executes a query with 1 parameter, and returns either an error on failure, or the full result set on success
|
||||||
|
pub fn (db DB) exec_param(query string, param string) ![]Row {
|
||||||
|
return db.exec_param_many(query, [param])
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec_param2 executes a query with 2 parameters, and returns either an error on failure, or the full result set on success
|
||||||
|
pub fn (db DB) exec_param2(query string, param string, param2 string) ![]Row {
|
||||||
|
return db.exec_param_many(query, [param, param2])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db DB) handle_error_or_result(res voidptr, elabel string) ![]Row {
|
||||||
|
e := unsafe { C.PQerrorMessage(db.conn).vstring() }
|
||||||
|
if e != '' {
|
||||||
|
C.PQclear(res)
|
||||||
|
return error('pg ${elabel} error:\n${e}')
|
||||||
|
}
|
||||||
|
return res_to_rows(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy_expert executes COPY command
|
||||||
|
// https://www.postgresql.org/docs/9.5/libpq-copy.html
|
||||||
|
pub fn (db DB) copy_expert(query string, mut file io.ReaderWriter) !int {
|
||||||
|
mut res := C.PQexec(db.conn, &char(query.str))
|
||||||
|
status := unsafe { ExecStatusType(C.PQresultStatus(res)) }
|
||||||
|
defer {
|
||||||
|
C.PQclear(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
e := unsafe { C.PQerrorMessage(db.conn).vstring() }
|
||||||
|
if e != '' {
|
||||||
|
return error('pg copy error:\n${e}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == .copy_in {
|
||||||
|
mut buf := []u8{len: 4 * 1024}
|
||||||
|
for {
|
||||||
|
n := file.read(mut buf) or {
|
||||||
|
msg := 'pg copy error: Failed to read from input'
|
||||||
|
C.PQputCopyEnd(db.conn, &char(msg.str))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
code := C.PQputCopyData(db.conn, buf.data, n)
|
||||||
|
if code == -1 {
|
||||||
|
return error('pg copy error: Failed to send data, code=${code}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code := C.PQputCopyEnd(db.conn, &char(0))
|
||||||
|
|
||||||
|
if code != 1 {
|
||||||
|
return error('pg copy error: Failed to finish copy command, code: ${code}')
|
||||||
|
}
|
||||||
|
} else if status == .copy_out {
|
||||||
|
for {
|
||||||
|
address := &char(0)
|
||||||
|
n_bytes := C.PQgetCopyData(db.conn, &address, 0)
|
||||||
|
if n_bytes > 0 {
|
||||||
|
mut local_buf := []u8{len: n_bytes}
|
||||||
|
unsafe { C.memcpy(&u8(local_buf.data), address, n_bytes) }
|
||||||
|
file.write(local_buf) or {
|
||||||
|
C.PQfreemem(address)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if n_bytes == -1 {
|
||||||
|
break
|
||||||
|
} else if n_bytes == -2 {
|
||||||
|
// consult PQerrorMessage for the reason
|
||||||
|
return error('pg copy error: read error')
|
||||||
|
}
|
||||||
|
if address != 0 {
|
||||||
|
C.PQfreemem(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
module main
|
module main
|
||||||
|
|
||||||
import orm
|
import orm
|
||||||
import pg
|
import db.pg
|
||||||
import time
|
import time
|
||||||
|
|
||||||
struct TestCustomSqlType {
|
struct TestCustomSqlType {
|
39
vlib/db/sqlite/README.md
Normal file
39
vlib/db/sqlite/README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
## Description
|
||||||
|
|
||||||
|
`sqlite` is a thin wrapper for [the SQLite library](https://sqlite.org/), which in turn is
|
||||||
|
"a C-language library that implements a small, fast, self-contained,
|
||||||
|
high-reliability, full-featured, SQL database engine."
|
||||||
|
|
||||||
|
# Install SQLite Dependency
|
||||||
|
|
||||||
|
Before you can use this module, you must first have the SQLite development
|
||||||
|
library installed on your system.
|
||||||
|
|
||||||
|
**Fedora 31**:
|
||||||
|
|
||||||
|
`sudo dnf -y install sqlite-devel`
|
||||||
|
|
||||||
|
|
||||||
|
**Ubuntu 20.04**:
|
||||||
|
|
||||||
|
`sudo apt install -y libsqlite3-dev`
|
||||||
|
|
||||||
|
|
||||||
|
**Windows**:
|
||||||
|
- Download the source zip from [SQLite Downloads](https://sqlite.org/download.html)
|
||||||
|
- Create a new `sqlite` subfolder inside `v/thirdparty`
|
||||||
|
- Extract the zip into that folder
|
||||||
|
|
||||||
|
# Performance Tips
|
||||||
|
|
||||||
|
When performing a large amount of database calls (i.e. INSERTS), significant
|
||||||
|
performance increase can be obtained by controlling the synchronization and journal modes.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
```v
|
||||||
|
import db.sqlite
|
||||||
|
|
||||||
|
db := sqlite.connect('foo.db') or { panic(err) }
|
||||||
|
db.synchronization_mode(sqlite.SyncMode.off)
|
||||||
|
db.journal_mode(sqlite.JournalMode.memory)
|
||||||
|
```
|
184
vlib/db/sqlite/orm.v
Normal file
184
vlib/db/sqlite/orm.v
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
module sqlite
|
||||||
|
|
||||||
|
import orm
|
||||||
|
import time
|
||||||
|
|
||||||
|
// @select is used internally by V's ORM for processing `SELECT ` queries
|
||||||
|
pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive {
|
||||||
|
// 1. Create query and bind necessary data
|
||||||
|
query := orm.orm_select_gen(config, '`', true, '?', 1, where)
|
||||||
|
$if trace_sqlite ? {
|
||||||
|
eprintln('> @select query: "${query}"')
|
||||||
|
}
|
||||||
|
stmt := db.new_init_stmt(query)!
|
||||||
|
defer {
|
||||||
|
stmt.finalize()
|
||||||
|
}
|
||||||
|
mut c := 1
|
||||||
|
sqlite_stmt_binder(stmt, where, query, mut c)!
|
||||||
|
sqlite_stmt_binder(stmt, data, query, mut c)!
|
||||||
|
|
||||||
|
mut ret := [][]orm.Primitive{}
|
||||||
|
|
||||||
|
if config.is_count {
|
||||||
|
// 2. Get count of returned values & add it to ret array
|
||||||
|
step := stmt.step()
|
||||||
|
if step !in [sqlite_row, sqlite_ok, sqlite_done] {
|
||||||
|
return db.error_message(step, query)
|
||||||
|
}
|
||||||
|
count := stmt.sqlite_select_column(0, 8)!
|
||||||
|
ret << [count]
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
// 2. Parse returned values
|
||||||
|
step := stmt.step()
|
||||||
|
if step == sqlite_done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if step != sqlite_ok && step != sqlite_row {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mut row := []orm.Primitive{}
|
||||||
|
for i, typ in config.types {
|
||||||
|
primitive := stmt.sqlite_select_column(i, typ)!
|
||||||
|
row << primitive
|
||||||
|
}
|
||||||
|
ret << row
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// sql stmt
|
||||||
|
|
||||||
|
// insert is used internally by V's ORM for processing `INSERT ` queries
|
||||||
|
pub fn (db DB) insert(table string, data orm.QueryData) ! {
|
||||||
|
query, converted_data := orm.orm_stmt_gen(table, '`', .insert, true, '?', 1, data,
|
||||||
|
orm.QueryData{})
|
||||||
|
sqlite_stmt_worker(db, query, converted_data, orm.QueryData{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// update is used internally by V's ORM for processing `UPDATE ` queries
|
||||||
|
pub fn (db DB) update(table string, data orm.QueryData, where orm.QueryData) ! {
|
||||||
|
query, _ := orm.orm_stmt_gen(table, '`', .update, true, '?', 1, data, where)
|
||||||
|
sqlite_stmt_worker(db, query, data, where)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete is used internally by V's ORM for processing `DELETE ` queries
|
||||||
|
pub fn (db DB) delete(table string, where orm.QueryData) ! {
|
||||||
|
query, _ := orm.orm_stmt_gen(table, '`', .delete, true, '?', 1, orm.QueryData{}, where)
|
||||||
|
sqlite_stmt_worker(db, query, orm.QueryData{}, where)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// last_id is used internally by V's ORM for post-processing `INSERT ` queries
|
||||||
|
pub fn (db DB) last_id() orm.Primitive {
|
||||||
|
query := 'SELECT last_insert_rowid();'
|
||||||
|
id := db.q_int(query)
|
||||||
|
return orm.Primitive(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DDL (table creation/destroying etc)
|
||||||
|
|
||||||
|
// create is used internally by V's ORM for processing table creation queries (DDL)
|
||||||
|
pub fn (db DB) create(table string, fields []orm.TableField) ! {
|
||||||
|
query := orm.orm_table_gen(table, '`', true, 0, fields, sqlite_type_from_v, false) or {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sqlite_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// drop is used internally by V's ORM for processing table destroying queries (DDL)
|
||||||
|
pub fn (db DB) drop(table string) ! {
|
||||||
|
query := 'DROP TABLE `${table}`;'
|
||||||
|
sqlite_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper
|
||||||
|
|
||||||
|
// Executes query and bind prepared statement data directly
|
||||||
|
fn sqlite_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ! {
|
||||||
|
$if trace_sqlite ? {
|
||||||
|
eprintln('> sqlite_stmt_worker query: "${query}"')
|
||||||
|
}
|
||||||
|
stmt := db.new_init_stmt(query)!
|
||||||
|
defer {
|
||||||
|
stmt.finalize()
|
||||||
|
}
|
||||||
|
mut c := 1
|
||||||
|
sqlite_stmt_binder(stmt, data, query, mut c)!
|
||||||
|
sqlite_stmt_binder(stmt, where, query, mut c)!
|
||||||
|
stmt.orm_step(query)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binds all values of d in the prepared statement
|
||||||
|
fn sqlite_stmt_binder(stmt Stmt, d orm.QueryData, query string, mut c &int) ! {
|
||||||
|
for data in d.data {
|
||||||
|
err := bind(stmt, c, data)
|
||||||
|
|
||||||
|
if err != 0 {
|
||||||
|
return stmt.db.error_message(err, query)
|
||||||
|
}
|
||||||
|
c++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Universal bind function
|
||||||
|
fn bind(stmt Stmt, c &int, data orm.Primitive) int {
|
||||||
|
mut err := 0
|
||||||
|
match data {
|
||||||
|
i8, i16, int, u8, u16, u32, bool {
|
||||||
|
err = stmt.bind_int(c, int(data))
|
||||||
|
}
|
||||||
|
i64, u64 {
|
||||||
|
err = stmt.bind_i64(c, i64(data))
|
||||||
|
}
|
||||||
|
f32, f64 {
|
||||||
|
err = stmt.bind_f64(c, unsafe { *(&f64(&data)) })
|
||||||
|
}
|
||||||
|
string {
|
||||||
|
err = stmt.bind_text(c, data)
|
||||||
|
}
|
||||||
|
time.Time {
|
||||||
|
err = stmt.bind_int(c, int(data.unix))
|
||||||
|
}
|
||||||
|
orm.InfixType {
|
||||||
|
err = bind(stmt, c, data.right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selects column in result and converts it to an orm.Primitive
|
||||||
|
fn (stmt Stmt) sqlite_select_column(idx int, typ int) !orm.Primitive {
|
||||||
|
mut primitive := orm.Primitive(0)
|
||||||
|
|
||||||
|
if typ in orm.nums || typ == -1 {
|
||||||
|
primitive = stmt.get_int(idx)
|
||||||
|
} else if typ in orm.num64 {
|
||||||
|
primitive = stmt.get_i64(idx)
|
||||||
|
} else if typ in orm.float {
|
||||||
|
primitive = stmt.get_f64(idx)
|
||||||
|
} else if typ == orm.type_string {
|
||||||
|
primitive = stmt.get_text(idx).clone()
|
||||||
|
} else if typ == orm.time {
|
||||||
|
d := stmt.get_int(idx)
|
||||||
|
primitive = time.unix(d)
|
||||||
|
} else {
|
||||||
|
return error('Unknown type ${typ}')
|
||||||
|
}
|
||||||
|
|
||||||
|
return primitive
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert type int to sql type string
|
||||||
|
fn sqlite_type_from_v(typ int) !string {
|
||||||
|
return if typ in orm.nums || typ < 0 || typ in orm.num64 || typ == orm.time {
|
||||||
|
'INTEGER'
|
||||||
|
} else if typ in orm.float {
|
||||||
|
'REAL'
|
||||||
|
} else if typ == orm.type_string {
|
||||||
|
'TEXT'
|
||||||
|
} else {
|
||||||
|
error('Unknown type ${typ}')
|
||||||
|
}
|
||||||
|
}
|
118
vlib/db/sqlite/result_code.v
Normal file
118
vlib/db/sqlite/result_code.v
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
module sqlite
|
||||||
|
|
||||||
|
// Result represents Sqlite Result and Error Codes
|
||||||
|
// see https://www.sqlite.org/rescode.html
|
||||||
|
pub enum Result {
|
||||||
|
ok = 0
|
||||||
|
error = 1
|
||||||
|
internal = 2
|
||||||
|
perm = 3
|
||||||
|
abort = 4
|
||||||
|
busy = 5
|
||||||
|
locked = 6
|
||||||
|
nomem = 7
|
||||||
|
readonly = 8
|
||||||
|
interrupt = 9
|
||||||
|
ioerr = 10
|
||||||
|
corrupt = 11
|
||||||
|
notfound = 12
|
||||||
|
full = 13
|
||||||
|
cantopen = 14
|
||||||
|
protocol = 15
|
||||||
|
empty = 16
|
||||||
|
schema = 17
|
||||||
|
toobig = 18
|
||||||
|
constraint = 19
|
||||||
|
mismatch = 20
|
||||||
|
misuse = 21
|
||||||
|
nolfs = 22
|
||||||
|
auth = 23
|
||||||
|
format = 24
|
||||||
|
range = 25
|
||||||
|
notadb = 26
|
||||||
|
notice = 27
|
||||||
|
warning = 28
|
||||||
|
row = 100
|
||||||
|
done = 101
|
||||||
|
ok_load_permanently = 256
|
||||||
|
error_missing_collseq = 257
|
||||||
|
busy_recovery = 261
|
||||||
|
locked_sharedcache = 262
|
||||||
|
readonly_recovery = 264
|
||||||
|
ioerr_read = 266
|
||||||
|
corrupt_vtab = 267
|
||||||
|
cantopen_notempdir = 270
|
||||||
|
constraint_check = 275
|
||||||
|
notice_recover_wal = 283
|
||||||
|
warning_autoindex = 284
|
||||||
|
error_retry = 513
|
||||||
|
abort_rollback = 516
|
||||||
|
busy_snapshot = 517
|
||||||
|
locked_vtab = 518
|
||||||
|
readonly_cantlock = 520
|
||||||
|
ioerr_short_read = 522
|
||||||
|
corrupt_sequence = 523
|
||||||
|
cantopen_isdir = 526
|
||||||
|
constraint_commithook = 531
|
||||||
|
notice_recover_rollback = 539
|
||||||
|
error_snapshot = 769
|
||||||
|
busy_timeout = 773
|
||||||
|
readonly_rollback = 776
|
||||||
|
ioerr_write = 778
|
||||||
|
corrupt_index = 779
|
||||||
|
cantopen_fullpath = 782
|
||||||
|
constraint_foreignkey = 787
|
||||||
|
readonly_dbmoved = 1032
|
||||||
|
ioerr_fsync = 1034
|
||||||
|
cantopen_convpath = 1038
|
||||||
|
constraint_function = 1043
|
||||||
|
readonly_cantinit = 1288
|
||||||
|
ioerr_dir_fsync = 1290
|
||||||
|
cantopen_dirtywal = 1294
|
||||||
|
constraint_notnull = 1299
|
||||||
|
readonly_directory = 1544
|
||||||
|
ioerr_truncate = 1546
|
||||||
|
cantopen_symlink = 1550
|
||||||
|
constraint_primarykey = 1555
|
||||||
|
ioerr_fstat = 1802
|
||||||
|
constraint_trigger = 1811
|
||||||
|
ioerr_unlock = 2058
|
||||||
|
constraint_unique = 2067
|
||||||
|
ioerr_rdlock = 2314
|
||||||
|
constraint_vtab = 2323
|
||||||
|
ioerr_delete = 2570
|
||||||
|
constraint_rowid = 2579
|
||||||
|
ioerr_blocked = 2826
|
||||||
|
constraint_pinned = 2835
|
||||||
|
ioerr_nomem = 3082
|
||||||
|
ioerr_access = 3338
|
||||||
|
ioerr_checkreservedlock = 3594
|
||||||
|
ioerr_lock = 3850
|
||||||
|
ioerr_close = 4106
|
||||||
|
ioerr_dir_close = 4362
|
||||||
|
ioerr_shmopen = 4618
|
||||||
|
ioerr_shmsize = 4874
|
||||||
|
ioerr_shmlock = 5130
|
||||||
|
ioerr_shmmap = 5386
|
||||||
|
ioerr_seek = 5642
|
||||||
|
ioerr_delete_noent = 5898
|
||||||
|
ioerr_mmap = 6154
|
||||||
|
ioerr_gettemppath = 6410
|
||||||
|
ioerr_convpath = 6666
|
||||||
|
ioerr_vnode = 6922
|
||||||
|
ioerr_auth = 7178
|
||||||
|
ioerr_begin_atomic = 7434
|
||||||
|
ioerr_commit_atomic = 7690
|
||||||
|
ioerr_rollback_atomic = 7946
|
||||||
|
ioerr_data = 8202
|
||||||
|
}
|
||||||
|
|
||||||
|
// is_error checks if it is an error code.
|
||||||
|
pub fn (r Result) is_error() bool {
|
||||||
|
return r !in [.ok, .row, .done]
|
||||||
|
}
|
||||||
|
|
||||||
|
// is_error checks if `code` is an error code.
|
||||||
|
pub fn is_error(code int) bool {
|
||||||
|
return unsafe { Result(code).is_error() }
|
||||||
|
}
|
340
vlib/db/sqlite/sqlite.v
Normal file
340
vlib/db/sqlite/sqlite.v
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
module sqlite
|
||||||
|
|
||||||
|
$if freebsd || openbsd {
|
||||||
|
#flag -I/usr/local/include
|
||||||
|
#flag -L/usr/local/lib
|
||||||
|
}
|
||||||
|
$if windows {
|
||||||
|
#flag windows -I@VEXEROOT/thirdparty/sqlite
|
||||||
|
#flag windows -L@VEXEROOT/thirdparty/sqlite
|
||||||
|
#flag windows @VEXEROOT/thirdparty/sqlite/sqlite3.o
|
||||||
|
} $else {
|
||||||
|
#flag -lsqlite3
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "sqlite3.h"
|
||||||
|
|
||||||
|
// https://www.sqlite.org/rescode.html
|
||||||
|
pub const (
|
||||||
|
sqlite_ok = 0
|
||||||
|
sqlite_error = 1
|
||||||
|
sqlite_row = 100
|
||||||
|
sqlite_done = 101
|
||||||
|
sqlite_cantopen = 14
|
||||||
|
sqlite_ioerr_read = 266
|
||||||
|
sqlite_ioerr_short_read = 522
|
||||||
|
sqlite_ioerr_write = 778
|
||||||
|
sqlite_ioerr_fsync = 1034
|
||||||
|
sqlite_ioerr_fstat = 1802
|
||||||
|
sqlite_ioerr_delete = 2570
|
||||||
|
|
||||||
|
sqlite_open_main_db = 0x00000100
|
||||||
|
sqlite_open_temp_db = 0x00000200
|
||||||
|
sqlite_open_transient_db = 0x00000400
|
||||||
|
sqlite_open_main_journal = 0x00000800
|
||||||
|
sqlite_open_temp_journal = 0x00001000
|
||||||
|
sqlite_open_subjournal = 0x00002000
|
||||||
|
sqlite_open_super_journal = 0x00004000
|
||||||
|
sqlite_open_wal = 0x00080000
|
||||||
|
)
|
||||||
|
|
||||||
|
pub enum SyncMode {
|
||||||
|
off
|
||||||
|
normal
|
||||||
|
full
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum JournalMode {
|
||||||
|
off
|
||||||
|
delete
|
||||||
|
truncate
|
||||||
|
persist
|
||||||
|
memory
|
||||||
|
}
|
||||||
|
|
||||||
|
struct C.sqlite3 {
|
||||||
|
}
|
||||||
|
|
||||||
|
struct C.sqlite3_stmt {
|
||||||
|
}
|
||||||
|
|
||||||
|
[heap]
|
||||||
|
pub struct Stmt {
|
||||||
|
stmt &C.sqlite3_stmt = unsafe { nil }
|
||||||
|
db &DB = unsafe { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SQLError {
|
||||||
|
MessageError
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
[heap]
|
||||||
|
pub struct DB {
|
||||||
|
pub mut:
|
||||||
|
is_open bool
|
||||||
|
mut:
|
||||||
|
conn &C.sqlite3 = unsafe { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
// str returns a text representation of the DB
|
||||||
|
pub fn (db &DB) str() string {
|
||||||
|
return 'sqlite.DB{ conn: ' + ptr_str(db.conn) + ' }'
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Row {
|
||||||
|
pub mut:
|
||||||
|
vals []string
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
fn C.sqlite3_open(&char, &&C.sqlite3) int
|
||||||
|
|
||||||
|
fn C.sqlite3_close(&C.sqlite3) int
|
||||||
|
|
||||||
|
fn C.sqlite3_busy_timeout(db &C.sqlite3, ms int) int
|
||||||
|
|
||||||
|
fn C.sqlite3_last_insert_rowid(&C.sqlite3) i64
|
||||||
|
|
||||||
|
//
|
||||||
|
fn C.sqlite3_prepare_v2(&C.sqlite3, &char, int, &&C.sqlite3_stmt, &&char) int
|
||||||
|
|
||||||
|
fn C.sqlite3_step(&C.sqlite3_stmt) int
|
||||||
|
|
||||||
|
fn C.sqlite3_finalize(&C.sqlite3_stmt) int
|
||||||
|
|
||||||
|
//
|
||||||
|
fn C.sqlite3_column_name(&C.sqlite3_stmt, int) &char
|
||||||
|
|
||||||
|
fn C.sqlite3_column_text(&C.sqlite3_stmt, int) &u8
|
||||||
|
|
||||||
|
fn C.sqlite3_column_int(&C.sqlite3_stmt, int) int
|
||||||
|
|
||||||
|
fn C.sqlite3_column_int64(&C.sqlite3_stmt, int) i64
|
||||||
|
|
||||||
|
fn C.sqlite3_column_double(&C.sqlite3_stmt, int) f64
|
||||||
|
|
||||||
|
fn C.sqlite3_column_count(&C.sqlite3_stmt) int
|
||||||
|
|
||||||
|
//
|
||||||
|
fn C.sqlite3_errstr(int) &char
|
||||||
|
|
||||||
|
fn C.sqlite3_errmsg(&C.sqlite3) &char
|
||||||
|
|
||||||
|
fn C.sqlite3_free(voidptr)
|
||||||
|
|
||||||
|
fn C.sqlite3_changes(&C.sqlite3) int
|
||||||
|
|
||||||
|
// connect Opens the connection with a database.
|
||||||
|
pub fn connect(path string) !DB {
|
||||||
|
db := &C.sqlite3(0)
|
||||||
|
code := C.sqlite3_open(&char(path.str), &db)
|
||||||
|
if code != 0 {
|
||||||
|
return &SQLError{
|
||||||
|
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
|
||||||
|
code: code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DB{
|
||||||
|
conn: db
|
||||||
|
is_open: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close Closes the DB.
|
||||||
|
// TODO: For all functions, determine whether the connection is
|
||||||
|
// closed first, and determine what to do if it is
|
||||||
|
pub fn (mut db DB) close() !bool {
|
||||||
|
code := C.sqlite3_close(db.conn)
|
||||||
|
if code == 0 {
|
||||||
|
db.is_open = false
|
||||||
|
} else {
|
||||||
|
return &SQLError{
|
||||||
|
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
|
||||||
|
code: code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true // successfully closed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for V ORM
|
||||||
|
fn get_int_from_stmt(stmt &C.sqlite3_stmt) int {
|
||||||
|
x := C.sqlite3_step(stmt)
|
||||||
|
if x != C.SQLITE_OK && x != C.SQLITE_DONE {
|
||||||
|
C.puts(C.sqlite3_errstr(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
res := C.sqlite3_column_int(stmt, 0)
|
||||||
|
C.sqlite3_finalize(stmt)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// last_insert_rowid returns last inserted rowid
|
||||||
|
// https://www.sqlite.org/c3ref/last_insert_rowid.html
|
||||||
|
pub fn (db &DB) last_insert_rowid() i64 {
|
||||||
|
return C.sqlite3_last_insert_rowid(db.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_affected_rows_count returns `sqlite changes()` meaning amount of rows affected by most recent sql query
|
||||||
|
pub fn (db &DB) get_affected_rows_count() int {
|
||||||
|
return C.sqlite3_changes(db.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// q_int returns a single integer value, from the first column of the result of executing `query`
|
||||||
|
pub fn (db &DB) q_int(query string) int {
|
||||||
|
stmt := &C.sqlite3_stmt(0)
|
||||||
|
defer {
|
||||||
|
C.sqlite3_finalize(stmt)
|
||||||
|
}
|
||||||
|
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
|
||||||
|
C.sqlite3_step(stmt)
|
||||||
|
|
||||||
|
res := C.sqlite3_column_int(stmt, 0)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// q_string returns a single string value, from the first column of the result of executing `query`
|
||||||
|
pub fn (db &DB) q_string(query string) string {
|
||||||
|
stmt := &C.sqlite3_stmt(0)
|
||||||
|
defer {
|
||||||
|
C.sqlite3_finalize(stmt)
|
||||||
|
}
|
||||||
|
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
|
||||||
|
C.sqlite3_step(stmt)
|
||||||
|
|
||||||
|
val := unsafe { &u8(C.sqlite3_column_text(stmt, 0)) }
|
||||||
|
return if val != &u8(0) { unsafe { tos_clone(val) } } else { '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec executes the query on the given `db`, and returns an array of all the results, alongside any result code.
|
||||||
|
// Result codes: https://www.sqlite.org/rescode.html
|
||||||
|
[manualfree]
|
||||||
|
pub fn (db &DB) exec(query string) ([]Row, int) {
|
||||||
|
stmt := &C.sqlite3_stmt(0)
|
||||||
|
defer {
|
||||||
|
C.sqlite3_finalize(stmt)
|
||||||
|
}
|
||||||
|
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
|
||||||
|
nr_cols := C.sqlite3_column_count(stmt)
|
||||||
|
mut res := 0
|
||||||
|
mut rows := []Row{}
|
||||||
|
for {
|
||||||
|
res = C.sqlite3_step(stmt)
|
||||||
|
// Result Code SQLITE_ROW; Another row is available
|
||||||
|
if res != 100 {
|
||||||
|
// C.puts(C.sqlite3_errstr(res))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mut row := Row{}
|
||||||
|
for i in 0 .. nr_cols {
|
||||||
|
val := unsafe { &u8(C.sqlite3_column_text(stmt, i)) }
|
||||||
|
if val == &u8(0) {
|
||||||
|
row.vals << ''
|
||||||
|
} else {
|
||||||
|
row.vals << unsafe { tos_clone(val) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows << row
|
||||||
|
}
|
||||||
|
return rows, res
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec_one executes a query on the given `db`.
|
||||||
|
// It returns either the first row from the result, if the query was successful, or an error.
|
||||||
|
[manualfree]
|
||||||
|
pub fn (db &DB) exec_one(query string) !Row {
|
||||||
|
rows, code := db.exec(query)
|
||||||
|
defer {
|
||||||
|
unsafe { rows.free() }
|
||||||
|
}
|
||||||
|
if rows.len == 0 {
|
||||||
|
return &SQLError{
|
||||||
|
msg: 'No rows'
|
||||||
|
code: code
|
||||||
|
}
|
||||||
|
} else if code != 101 {
|
||||||
|
return &SQLError{
|
||||||
|
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
|
||||||
|
code: code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res := rows[0]
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// error_message returns a proper V error, given an integer error code received from SQLite, and a query string
|
||||||
|
[manualfree]
|
||||||
|
pub fn (db &DB) error_message(code int, query string) IError {
|
||||||
|
errmsg := unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db.conn))) }
|
||||||
|
msg := '${errmsg} (${code}) (${query})'
|
||||||
|
unsafe { errmsg.free() }
|
||||||
|
return SQLError{
|
||||||
|
msg: msg
|
||||||
|
code: code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec_none executes a query, and returns the integer SQLite result code.
|
||||||
|
// Use it, in case you don't expect any row results, but still want a result code.
|
||||||
|
// e.g. for queries like these: `INSERT INTO ... VALUES (...)`
|
||||||
|
pub fn (db &DB) exec_none(query string) int {
|
||||||
|
stmt := &C.sqlite3_stmt(0)
|
||||||
|
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
|
||||||
|
code := C.sqlite3_step(stmt)
|
||||||
|
C.sqlite3_finalize(stmt)
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO pub fn (db &DB) exec_param(query string, param string) []Row {
|
||||||
|
|
||||||
|
// create_table issues a "create table if not exists" command to the db.
|
||||||
|
// It creates the table named 'table_name', with columns generated from 'columns' array.
|
||||||
|
// The default columns type will be TEXT.
|
||||||
|
pub fn (db &DB) create_table(table_name string, columns []string) {
|
||||||
|
db.exec('create table if not exists ${table_name} (' + columns.join(',\n') + ')')
|
||||||
|
}
|
||||||
|
|
||||||
|
// busy_timeout sets a busy timeout in milliseconds.
|
||||||
|
// Sleeps for a specified amount of time when a table is locked. The handler
|
||||||
|
// will sleep multiple times until at least "ms" milliseconds of sleeping have accumulated.
|
||||||
|
// (see https://www.sqlite.org/c3ref/busy_timeout.html)
|
||||||
|
pub fn (db &DB) busy_timeout(ms int) int {
|
||||||
|
return C.sqlite3_busy_timeout(db.conn, ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// synchronization_mode sets disk synchronization mode, which controls how
|
||||||
|
// aggressively SQLite will write data to physical storage.
|
||||||
|
// .off: No syncs at all. (fastest)
|
||||||
|
// .normal: Sync after each sequence of critical disk operations.
|
||||||
|
// .full: Sync after each critical disk operation (slowest).
|
||||||
|
pub fn (db &DB) synchronization_mode(sync_mode SyncMode) {
|
||||||
|
if sync_mode == .off {
|
||||||
|
db.exec('pragma synchronous = OFF;')
|
||||||
|
} else if sync_mode == .full {
|
||||||
|
db.exec('pragma synchronous = FULL;')
|
||||||
|
} else {
|
||||||
|
db.exec('pragma synchronous = NORMAL;')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// journal_mode controls how the journal file is stored and processed.
|
||||||
|
// .off: No journal record is kept. (fastest)
|
||||||
|
// .memory: Journal record is held in memory, rather than on disk.
|
||||||
|
// .delete: At the conclusion of a transaction, journal file is deleted.
|
||||||
|
// .truncate: Journal file is truncated to a length of zero bytes.
|
||||||
|
// .persist: Journal file is left in place, but the header is overwritten to indicate journal is no longer valid.
|
||||||
|
pub fn (db &DB) journal_mode(journal_mode JournalMode) {
|
||||||
|
if journal_mode == .off {
|
||||||
|
db.exec('pragma journal_mode = OFF;')
|
||||||
|
} else if journal_mode == .delete {
|
||||||
|
db.exec('pragma journal_mode = DELETE;')
|
||||||
|
} else if journal_mode == .truncate {
|
||||||
|
db.exec('pragma journal_mode = TRUNCATE;')
|
||||||
|
} else if journal_mode == .persist {
|
||||||
|
db.exec('pragma journal_mode = PERSIST;')
|
||||||
|
} else if journal_mode == .memory {
|
||||||
|
db.exec('pragma journal_mode = MEMORY;')
|
||||||
|
} else {
|
||||||
|
db.exec('pragma journal_mode = MEMORY;')
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import orm
|
import orm
|
||||||
import sqlite
|
import db.sqlite
|
||||||
import time
|
import time
|
||||||
|
|
||||||
struct TestCustomSqlType {
|
struct TestCustomSqlType {
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
type Connection = sqlite.DB
|
type Connection = sqlite.DB
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
import rand
|
import rand
|
||||||
|
|
||||||
const (
|
const (
|
78
vlib/db/sqlite/stmt.v
Normal file
78
vlib/db/sqlite/stmt.v
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
module sqlite
|
||||||
|
|
||||||
|
fn C.sqlite3_bind_double(&C.sqlite3_stmt, int, f64) int
|
||||||
|
fn C.sqlite3_bind_int(&C.sqlite3_stmt, int, int) int
|
||||||
|
fn C.sqlite3_bind_int64(&C.sqlite3_stmt, int, i64) int
|
||||||
|
fn C.sqlite3_bind_text(&C.sqlite3_stmt, int, &char, int, voidptr) int
|
||||||
|
|
||||||
|
// Only for V ORM
|
||||||
|
fn (db &DB) init_stmt(query string) (&C.sqlite3_stmt, int) {
|
||||||
|
// println('init_stmt("$query")')
|
||||||
|
stmt := &C.sqlite3_stmt(0)
|
||||||
|
err := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
|
||||||
|
return stmt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db &DB) new_init_stmt(query string) !Stmt {
|
||||||
|
stmt, err := db.init_stmt(query)
|
||||||
|
if err != sqlite_ok {
|
||||||
|
return db.error_message(err, query)
|
||||||
|
}
|
||||||
|
return Stmt{stmt, db}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) bind_int(idx int, v int) int {
|
||||||
|
return C.sqlite3_bind_int(stmt.stmt, idx, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) bind_i64(idx int, v i64) int {
|
||||||
|
return C.sqlite3_bind_int64(stmt.stmt, idx, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) bind_f64(idx int, v f64) int {
|
||||||
|
return C.sqlite3_bind_double(stmt.stmt, idx, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) bind_text(idx int, s string) int {
|
||||||
|
return C.sqlite3_bind_text(stmt.stmt, idx, voidptr(s.str), s.len, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) get_int(idx int) int {
|
||||||
|
return C.sqlite3_column_int(stmt.stmt, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) get_i64(idx int) i64 {
|
||||||
|
return C.sqlite3_column_int64(stmt.stmt, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) get_f64(idx int) f64 {
|
||||||
|
return C.sqlite3_column_double(stmt.stmt, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) get_text(idx int) string {
|
||||||
|
b := &char(C.sqlite3_column_text(stmt.stmt, idx))
|
||||||
|
|
||||||
|
if b == &char(0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return unsafe { b.vstring() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) get_count() int {
|
||||||
|
return C.sqlite3_column_count(stmt.stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) step() int {
|
||||||
|
return C.sqlite3_step(stmt.stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) orm_step(query string) ! {
|
||||||
|
res := stmt.step()
|
||||||
|
if res != sqlite_ok && res != sqlite_done && res != sqlite_row {
|
||||||
|
return stmt.db.error_message(res, query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) finalize() {
|
||||||
|
C.sqlite3_finalize(stmt.stmt)
|
||||||
|
}
|
171
vlib/db/sqlite/vfs_lowlevel.v
Normal file
171
vlib/db/sqlite/vfs_lowlevel.v
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
module sqlite
|
||||||
|
|
||||||
|
type Sig1 = fn (&C.sqlite3_file, &i64) int // https://github.com/vlang/v/issues/16291
|
||||||
|
|
||||||
|
type Sig2 = fn (&Sqlite3_file, &int) int // https://github.com/vlang/v/issues/16291
|
||||||
|
|
||||||
|
pub type Sqlite3_file = C.sqlite3_file
|
||||||
|
|
||||||
|
// https://www.sqlite.org/c3ref/file.html
|
||||||
|
struct C.sqlite3_file {
|
||||||
|
pub mut:
|
||||||
|
pMethods &C.sqlite3_io_methods // Methods for an open file
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.sqlite.org/c3ref/io_methods.html
|
||||||
|
[heap]
|
||||||
|
struct C.sqlite3_io_methods {
|
||||||
|
mut:
|
||||||
|
// version 1 and later fields
|
||||||
|
iVersion int
|
||||||
|
|
||||||
|
xClose fn (&Sqlite3_file) int
|
||||||
|
xRead fn (&Sqlite3_file, voidptr, int, i64) int
|
||||||
|
xWrite fn (&Sqlite3_file, voidptr, int, i64) int
|
||||||
|
xTruncate fn (&Sqlite3_file, i64) int
|
||||||
|
xSync fn (&Sqlite3_file, int) int
|
||||||
|
xFileSize Sig1
|
||||||
|
xLock fn (&Sqlite3_file, int) int
|
||||||
|
xUnlock fn (&Sqlite3_file, int) int
|
||||||
|
xCheckReservedLock Sig2
|
||||||
|
xFileControl fn (&Sqlite3_file, int, voidptr) int
|
||||||
|
xSectorSize fn (&Sqlite3_file) int
|
||||||
|
xDeviceCharacteristics fn (&Sqlite3_file) int
|
||||||
|
// version 2 and later fields
|
||||||
|
xShmMap fn (&Sqlite3_file, int, int, int, &voidptr) int
|
||||||
|
xShmLock fn (&Sqlite3_file, int, int, int) int
|
||||||
|
xShmBarrier fn (&Sqlite3_file)
|
||||||
|
xShmUnmap fn (&Sqlite3_file, int) int
|
||||||
|
// version 3 and later fields
|
||||||
|
xFetch fn (&Sqlite3_file, i64, int, &voidptr) int
|
||||||
|
xUnfetch fn (&Sqlite3_file, i64, voidptr) int
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Sqlite3_io_methods = C.sqlite3_io_methods
|
||||||
|
|
||||||
|
// https://www.sqlite.org/c3ref/vfs.html
|
||||||
|
type Fn_sqlite3_syscall_ptr = fn ()
|
||||||
|
|
||||||
|
pub type Sqlite3_vfs = C.sqlite3_vfs
|
||||||
|
|
||||||
|
[heap]
|
||||||
|
pub struct C.sqlite3_vfs {
|
||||||
|
pub mut:
|
||||||
|
// version 1 and later fields
|
||||||
|
iVersion int // Structure version number (currently 3)
|
||||||
|
szOsFile int // Size of subclassed sqlite3_file
|
||||||
|
mxPathname int // Maximum file pathname length
|
||||||
|
pNext &Sqlite3_vfs // Next registered VFS
|
||||||
|
zName &char // Name of this virtual file system
|
||||||
|
pAppData voidptr // Pointer to application-specific data
|
||||||
|
|
||||||
|
xOpen fn (&Sqlite3_vfs, &char, &Sqlite3_file, int, &int) int
|
||||||
|
xDelete fn (&Sqlite3_vfs, &char, int) int
|
||||||
|
|
||||||
|
xAccess fn (&Sqlite3_vfs, &char, int, &int) int
|
||||||
|
xFullPathname fn (&Sqlite3_vfs, &char, int, &char) int
|
||||||
|
xDlOpen fn (&Sqlite3_vfs, &char) voidptr
|
||||||
|
xDlError fn (&Sqlite3_vfs, int, &char)
|
||||||
|
xDlSym fn (&Sqlite3_vfs, voidptr, &char) voidptr // to fn accepting void and returning
|
||||||
|
xDlClose fn (&Sqlite3_vfs, voidptr)
|
||||||
|
xRandomness fn (&Sqlite3_vfs, int, &char) int
|
||||||
|
xSleep fn (&Sqlite3_vfs, int) int
|
||||||
|
xCurrentTime fn (&Sqlite3_vfs, &f64) int
|
||||||
|
xGetLastError fn (&Sqlite3_vfs, int, &char) int
|
||||||
|
// version two and later only fields
|
||||||
|
xCurrentTimeInt64 fn (&Sqlite3_vfs, &i64) int
|
||||||
|
// version three and later only fields
|
||||||
|
xSetSystemCall fn (&Sqlite3_vfs, &char, Fn_sqlite3_syscall_ptr) int
|
||||||
|
xGetSystemCall fn (&Sqlite3_vfs, &char) Fn_sqlite3_syscall_ptr
|
||||||
|
xNextSystemCall fn (&Sqlite3_vfs, &char) &char
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.sqlite.org/c3ref/vfs_find.html
|
||||||
|
fn C.sqlite3_vfs_find(&char) &C.sqlite3_vfs
|
||||||
|
fn C.sqlite3_vfs_register(&C.sqlite3_vfs, int) int
|
||||||
|
fn C.sqlite3_vfs_unregister(&C.sqlite3_vfs) int
|
||||||
|
|
||||||
|
// get_vfs Requests sqlite to return instance of VFS with given name.
|
||||||
|
// when such vfs is not known, `none` is returned
|
||||||
|
pub fn get_vfs(name string) ?&Sqlite3_vfs {
|
||||||
|
res := C.sqlite3_vfs_find(name.str)
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if res == nil {
|
||||||
|
return none
|
||||||
|
} else {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_default_vfs Asks sqlite for default VFS instance
|
||||||
|
pub fn get_default_vfs() ?&Sqlite3_vfs {
|
||||||
|
unsafe {
|
||||||
|
res := C.sqlite3_vfs_find(nil)
|
||||||
|
if res == nil {
|
||||||
|
return none
|
||||||
|
} else {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register_as_nondefault Asks sqlite to register VFS passed in receiver argument as the known VFS.
|
||||||
|
// more info about VFS: https://www.sqlite.org/c3ref/vfs.html
|
||||||
|
// 'not TODOs' to prevent corruption: https://sqlite.org/howtocorrupt.html
|
||||||
|
// example VFS: https://www.sqlite.org/src/doc/trunk/src/test_demovfs.c
|
||||||
|
pub fn (mut v Sqlite3_vfs) register_as_nondefault() ? {
|
||||||
|
res := C.sqlite3_vfs_register(v, 0)
|
||||||
|
|
||||||
|
return if sqlite_ok == res { none } else { error('sqlite3_vfs_register returned ${res}') }
|
||||||
|
}
|
||||||
|
|
||||||
|
// unregister Requests sqlite to stop using VFS as passed in receiver argument
|
||||||
|
pub fn (mut v Sqlite3_vfs) unregister() ? {
|
||||||
|
res := C.sqlite3_vfs_unregister(v)
|
||||||
|
|
||||||
|
return if sqlite_ok == res { none } else { error('sqlite3_vfs_unregister returned ${res}') }
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.sqlite.org/c3ref/open.html
|
||||||
|
fn C.sqlite3_open_v2(&char, &&C.sqlite3, int, &char) int
|
||||||
|
|
||||||
|
// https://www.sqlite.org/c3ref/c_open_autoproxy.html
|
||||||
|
pub enum OpenModeFlag {
|
||||||
|
readonly = 0x00000001
|
||||||
|
readwrite = 0x00000002
|
||||||
|
create = 0x00000004
|
||||||
|
uri = 0x00000040
|
||||||
|
memory = 0x00000080
|
||||||
|
nomutex = 0x00008000
|
||||||
|
fullmutex = 0x00010000
|
||||||
|
sharedcache = 0x00020000
|
||||||
|
privatecache = 0x00040000
|
||||||
|
exrescode = 0x02000000
|
||||||
|
nofollow = 0x01000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect_full Opens connection to sqlite database. It gives more control than `open`.
|
||||||
|
// Flags give control over readonly and create decisions. Specific VFS can be chosen.
|
||||||
|
pub fn connect_full(path string, mode_flags []OpenModeFlag, vfs_name string) !DB {
|
||||||
|
db := &C.sqlite3(0)
|
||||||
|
|
||||||
|
mut flags := 0
|
||||||
|
|
||||||
|
for flag in mode_flags {
|
||||||
|
flags = flags | int(flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
code := C.sqlite3_open_v2(&char(path.str), &db, flags, vfs_name.str)
|
||||||
|
if code != 0 {
|
||||||
|
return &SQLError{
|
||||||
|
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
|
||||||
|
code: code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DB{
|
||||||
|
conn: db
|
||||||
|
is_open: true
|
||||||
|
}
|
||||||
|
}
|
@ -1,69 +1,4 @@
|
|||||||
# SQL Server ODBC
|
# SQL Server ODBC
|
||||||
|
|
||||||
* This is a V wrapper of SQL Server ODBC C/C++ library
|
The `mssql` module has been moved to `db.mssql`.
|
||||||
|
Update your code to do: `import db.mssql` instead.
|
||||||
## 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
3
vlib/mssql/z_deprecated.v
Normal file
3
vlib/mssql/z_deprecated.v
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[deprecated: 'import db.mssql instead']
|
||||||
|
[deprecated_after: '2023-02-01']
|
||||||
|
module mssql
|
@ -1,39 +1,4 @@
|
|||||||
For Linux, you need to install `MySQL development` package and `pkg-config`.
|
## Description:
|
||||||
|
|
||||||
For Windows, install [the installer](https://dev.mysql.com/downloads/installer/) ,
|
The `mysql` module has been moved to `db.mysql`.
|
||||||
then copy the `include` and `lib` folders to `<V install directory>\thirdparty\mysql`.
|
Update your code to do: `import db.mysql` instead.
|
||||||
|
|
||||||
Note: if you encounter weird errors (your program just exits right away, without
|
|
||||||
printing any messages, even though you have `println('hi')` statements in your
|
|
||||||
`fn main()`), when trying to run a program that does `import mysql` on windows, you
|
|
||||||
may need to copy the .dll file: `thirdparty/mysql/lib/libmysql.dll` , into the folder
|
|
||||||
of the executable too (it should be right next to the .exe file).
|
|
||||||
This is a temporary workaround, until we have a more permanent solution, or at least
|
|
||||||
more user friendly errors for that situation.
|
|
||||||
|
|
||||||
## Basic Usage
|
|
||||||
|
|
||||||
```v oksyntax
|
|
||||||
import mysql
|
|
||||||
|
|
||||||
// Create connection
|
|
||||||
mut connection := mysql.Connection{
|
|
||||||
username: 'root'
|
|
||||||
dbname: 'mysql'
|
|
||||||
}
|
|
||||||
// Connect to server
|
|
||||||
connection.connect()?
|
|
||||||
// Change the default database
|
|
||||||
connection.select_db('db_users')?
|
|
||||||
// Do a query
|
|
||||||
get_users_query_result := connection.query('SELECT * FROM users')?
|
|
||||||
// Get the result as maps
|
|
||||||
for user in get_users_query_result.maps() {
|
|
||||||
// Access the name of user
|
|
||||||
println(user['name'])
|
|
||||||
}
|
|
||||||
// Free the query result
|
|
||||||
get_users_query_result.free()
|
|
||||||
// Close the connection if needed
|
|
||||||
connection.close()
|
|
||||||
```
|
|
||||||
|
3
vlib/mysql/z_deprecated.v
Normal file
3
vlib/mysql/z_deprecated.v
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[deprecated: 'import db.mysql instead']
|
||||||
|
[deprecated_after: '2023-02-01']
|
||||||
|
module mysql
|
@ -112,7 +112,7 @@ result := sql db {
|
|||||||
|
|
||||||
### Example
|
### Example
|
||||||
```v ignore
|
```v ignore
|
||||||
import pg
|
import db.pg
|
||||||
|
|
||||||
struct Member {
|
struct Member {
|
||||||
id string [default: 'gen_random_uuid()'; primary; sql_type: 'uuid']
|
id string [default: 'gen_random_uuid()'; primary; sql_type: 'uuid']
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct User {
|
struct User {
|
||||||
id i64 [primary; sql: serial]
|
id i64 [primary; sql: serial]
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
// import os
|
// import os
|
||||||
// import term
|
// import term
|
||||||
// import mysql
|
// import db.mysql
|
||||||
// import pg
|
// import db.pg
|
||||||
import time
|
import time
|
||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct Module {
|
struct Module {
|
||||||
id int [primary; sql: serial]
|
id int [primary; sql: serial]
|
||||||
|
@ -1,52 +1,4 @@
|
|||||||
## Description:
|
## Description:
|
||||||
|
|
||||||
`pg` is a wrapper for the PostgreSQL client library. It provides access to a PostgreSQL
|
The `pg` module has been moved to `db.pg`.
|
||||||
database server.
|
Update your code to do: `import db.pg` instead.
|
||||||
|
|
||||||
Before you can use this module, you must first have PostgreSQL installed on your system.
|
|
||||||
To do this, find your OS and perform the actions listed.
|
|
||||||
|
|
||||||
**NOTE**: These instructions are meant only as a convenience. If your OS is not listed
|
|
||||||
or you need extra help, [go here](https://www.postgresql.org/download/).
|
|
||||||
|
|
||||||
### Fedora 31
|
|
||||||
```
|
|
||||||
sudo dnf install postgresql-server postgresql-contrib
|
|
||||||
sudo systemctl enable postgresql # to autostart on startup
|
|
||||||
sudo systemctl start postgresql
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ubuntu/Debian
|
|
||||||
```
|
|
||||||
sudo apt-get install postgresql postgresql-client
|
|
||||||
sudo systemctl enable postgresql # to autostart on startup
|
|
||||||
sudo systemctl start postgresql
|
|
||||||
```
|
|
||||||
|
|
||||||
### MacOSX (Homebrew)
|
|
||||||
```
|
|
||||||
brew install postgresql
|
|
||||||
brew services start postgresql
|
|
||||||
```
|
|
||||||
|
|
||||||
### MacOSX (MacPorts)
|
|
||||||
```
|
|
||||||
gem install pg -- --with-pg-config=/opt/local/lib/postgresql[version number]/bin/pg_config
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installing libpq-dev or its equivalent for your OS: ##
|
|
||||||
|
|
||||||
**Ubuntu/Debian**: `sudo apt-get install libpq-dev`
|
|
||||||
|
|
||||||
**Red Hat Linux (RHEL)**: `yum install postgresql-devel`
|
|
||||||
|
|
||||||
**OpenSuse**: `zypper in postgresql-devel`
|
|
||||||
|
|
||||||
**ArchLinux**: `pacman -S postgresql-libs`
|
|
||||||
|
|
||||||
##Getting Started with [PostgreSQL](https://www.postgresqltutorial.com/postgresql-getting-started)
|
|
||||||
|
|
||||||
Read this section to learn how to install and connect to PostgreSQL
|
|
||||||
*[Windows](https://www.postgresqltutorial.com/install-postgresql)*;
|
|
||||||
*[Linux](https://www.postgresqltutorial.com/postgresql-getting-started/install-postgresql-linux)*;
|
|
||||||
*[macOS](https://www.postgresqltutorial.com/postgresql-getting-started/install-postgresql-macos)*.
|
|
||||||
|
3
vlib/pg/z_deprecated.v
Normal file
3
vlib/pg/z_deprecated.v
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[deprecated: 'import db.pg instead']
|
||||||
|
[deprecated_after: '2023-02-01']
|
||||||
|
module pg
|
@ -1,39 +1,4 @@
|
|||||||
## Description
|
## Description
|
||||||
|
|
||||||
`sqlite` is a thin wrapper for [the SQLite library](https://sqlite.org/), which in turn is
|
The `sqlite` module has been moved to `db.sqlite`.
|
||||||
"a C-language library that implements a small, fast, self-contained,
|
Update your code to do: `import db.sqlite` instead.
|
||||||
high-reliability, full-featured, SQL database engine."
|
|
||||||
|
|
||||||
# Install SQLite Dependency
|
|
||||||
|
|
||||||
Before you can use this module, you must first have the SQLite development
|
|
||||||
library installed on your system.
|
|
||||||
|
|
||||||
**Fedora 31**:
|
|
||||||
|
|
||||||
`sudo dnf -y install sqlite-devel`
|
|
||||||
|
|
||||||
|
|
||||||
**Ubuntu 20.04**:
|
|
||||||
|
|
||||||
`sudo apt install -y libsqlite3-dev`
|
|
||||||
|
|
||||||
|
|
||||||
**Windows**:
|
|
||||||
- Download the source zip from [SQLite Downloads](https://sqlite.org/download.html)
|
|
||||||
- Create a new `sqlite` subfolder inside `v/thirdparty`
|
|
||||||
- Extract the zip into that folder
|
|
||||||
|
|
||||||
# Performance Tips
|
|
||||||
|
|
||||||
When performing a large amount of database calls (i.e. INSERTS), significant
|
|
||||||
performance increase can be obtained by controlling the synchronization and journal modes.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
```v
|
|
||||||
import sqlite
|
|
||||||
|
|
||||||
db := sqlite.connect('foo.db') or { panic(err) }
|
|
||||||
db.synchronization_mode(sqlite.SyncMode.off)
|
|
||||||
db.journal_mode(sqlite.JournalMode.memory)
|
|
||||||
```
|
|
||||||
|
@ -52,19 +52,19 @@ pub enum JournalMode {
|
|||||||
memory
|
memory
|
||||||
}
|
}
|
||||||
|
|
||||||
struct C.sqlite3 {
|
pub struct C.sqlite3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct C.sqlite3_stmt {
|
pub struct C.sqlite3_stmt {
|
||||||
}
|
}
|
||||||
|
|
||||||
[heap]
|
[heap]
|
||||||
struct Stmt {
|
pub struct Stmt {
|
||||||
stmt &C.sqlite3_stmt = unsafe { nil }
|
stmt &C.sqlite3_stmt = unsafe { nil }
|
||||||
db &DB = unsafe { nil }
|
db &DB = unsafe { nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SQLError {
|
pub struct SQLError {
|
||||||
MessageError
|
MessageError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,14 +7,14 @@ type Sig2 = fn (&Sqlite3_file, &int) int // https://github.com/vlang/v/issues/16
|
|||||||
pub type Sqlite3_file = C.sqlite3_file
|
pub type Sqlite3_file = C.sqlite3_file
|
||||||
|
|
||||||
// https://www.sqlite.org/c3ref/file.html
|
// https://www.sqlite.org/c3ref/file.html
|
||||||
struct C.sqlite3_file {
|
pub struct C.sqlite3_file {
|
||||||
pub mut:
|
pub mut:
|
||||||
pMethods &C.sqlite3_io_methods // Methods for an open file
|
pMethods &C.sqlite3_io_methods // Methods for an open file
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.sqlite.org/c3ref/io_methods.html
|
// https://www.sqlite.org/c3ref/io_methods.html
|
||||||
[heap]
|
[heap]
|
||||||
struct C.sqlite3_io_methods {
|
pub struct C.sqlite3_io_methods {
|
||||||
mut:
|
mut:
|
||||||
// version 1 and later fields
|
// version 1 and later fields
|
||||||
iVersion int
|
iVersion int
|
||||||
@ -49,7 +49,7 @@ type Fn_sqlite3_syscall_ptr = fn ()
|
|||||||
pub type Sqlite3_vfs = C.sqlite3_vfs
|
pub type Sqlite3_vfs = C.sqlite3_vfs
|
||||||
|
|
||||||
[heap]
|
[heap]
|
||||||
struct C.sqlite3_vfs {
|
pub struct C.sqlite3_vfs {
|
||||||
pub mut:
|
pub mut:
|
||||||
// version 1 and later fields
|
// version 1 and later fields
|
||||||
iVersion int // Structure version number (currently 3)
|
iVersion int // Structure version number (currently 3)
|
||||||
|
7
vlib/sqlite/z_deprecated.v
Normal file
7
vlib/sqlite/z_deprecated.v
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[deprecated: 'import db.sqlite instead']
|
||||||
|
[deprecated_after: '2023-02-01']
|
||||||
|
module sqlite
|
||||||
|
|
||||||
|
import db.sqlite
|
||||||
|
|
||||||
|
const use_sqlite_ok = sqlite.sqlite_ok
|
@ -1732,11 +1732,12 @@ pub enum SqlStmtKind {
|
|||||||
|
|
||||||
pub struct SqlStmt {
|
pub struct SqlStmt {
|
||||||
pub:
|
pub:
|
||||||
pos token.Pos
|
|
||||||
db_expr Expr // `db` in `sql db {`
|
db_expr Expr // `db` in `sql db {`
|
||||||
or_expr OrExpr
|
or_expr OrExpr
|
||||||
|
pos token.Pos
|
||||||
pub mut:
|
pub mut:
|
||||||
lines []SqlStmtLine
|
lines []SqlStmtLine
|
||||||
|
db_expr_type Type // the type of the `db` in `sql db {`
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SqlStmtLine {
|
pub struct SqlStmtLine {
|
||||||
|
@ -119,7 +119,7 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type {
|
fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type {
|
||||||
c.expr(node.db_expr)
|
node.db_expr_type = c.table.unaliased_type(c.expr(node.db_expr))
|
||||||
mut typ := ast.void_type
|
mut typ := ast.void_type
|
||||||
for mut line in node.lines {
|
for mut line in node.lines {
|
||||||
a := c.sql_stmt_line(mut line)
|
a := c.sql_stmt_line(mut line)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct Person {
|
struct Person {
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct Person {
|
struct Person {
|
||||||
id int [primary; sql: serial]
|
id int [primary; sql: serial]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
enum Person {
|
enum Person {
|
||||||
test
|
test
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct User {
|
struct User {
|
||||||
id int [primary; sql: serial]
|
id int [primary; sql: serial]
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
vlib/v/checker/tests/struct_type_is_private_err.vv:1:8: warning: module 'sqlite' is imported but never used
|
vlib/v/checker/tests/struct_type_is_private_err.vv:1:8: warning: module 'sqlite (db.sqlite)' is imported but never used
|
||||||
1 | import sqlite
|
1 | import db.sqlite
|
||||||
| ~~~~~~
|
| ~~~~~~~~~
|
||||||
2 |
|
2 |
|
||||||
3 | fn main(){
|
3 | fn main() {
|
||||||
vlib/v/checker/tests/struct_type_is_private_err.vv:4:10: error: struct `C.sqlite3` was declared as private to module `sqlite`, so it can not be used inside module `main`
|
vlib/v/checker/tests/struct_type_is_private_err.vv:4:10: error: struct `C.sqlite3` was declared as private to module `db.sqlite`, so it can not be used inside module `main`
|
||||||
2 |
|
2 |
|
||||||
3 | fn main(){
|
3 | fn main() {
|
||||||
4 | _ := &C.sqlite3{}
|
4 | _ := &C.sqlite3{}
|
||||||
| ~~~~~~~~~
|
| ~~~~~~~~~
|
||||||
5 | }
|
5 | }
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
fn main(){
|
fn main() {
|
||||||
_ := &C.sqlite3{}
|
_ := &C.sqlite3{}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
import mymodule { ModDbStruct }
|
import mymodule { ModDbStruct }
|
||||||
|
|
||||||
struct Customer {
|
struct Customer {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct User {
|
struct User {
|
||||||
id i64 [primary; sql: serial]
|
id i64 [primary; sql: serial]
|
||||||
|
@ -10,38 +10,15 @@ enum SqlExprSide {
|
|||||||
right
|
right
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SqlType {
|
|
||||||
sqlite3
|
|
||||||
mysql
|
|
||||||
psql
|
|
||||||
mssql
|
|
||||||
unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) sql_stmt(node ast.SqlStmt) {
|
fn (mut g Gen) sql_stmt(node ast.SqlStmt) {
|
||||||
conn := g.new_tmp_var()
|
conn := g.new_tmp_var()
|
||||||
g.writeln('')
|
g.writeln('')
|
||||||
g.writeln('// orm')
|
g.writeln('// orm')
|
||||||
g.write('orm__Connection ${conn} = (orm__Connection){._')
|
g.write('orm__Connection ${conn} = (orm__Connection){._')
|
||||||
mut fn_prefix := ''
|
db_expr_ctype_name := g.typ(node.db_expr_type)
|
||||||
typ := g.parse_db_type(node.db_expr)
|
g.write('${db_expr_ctype_name} = &')
|
||||||
match typ {
|
|
||||||
.sqlite3 {
|
|
||||||
fn_prefix = 'sqlite__DB'
|
|
||||||
}
|
|
||||||
.mysql {
|
|
||||||
fn_prefix = 'mysql__Connection'
|
|
||||||
}
|
|
||||||
.psql {
|
|
||||||
fn_prefix = 'pg__DB'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
verror('This database type `${typ}` is not implemented yet in orm') // TODO add better error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g.write('${fn_prefix} = &')
|
|
||||||
g.expr(node.db_expr)
|
g.expr(node.db_expr)
|
||||||
g.writeln(', ._typ = _orm__Connection_${fn_prefix}_index};')
|
g.writeln(', ._typ = _orm__Connection_${db_expr_ctype_name}_index};')
|
||||||
for line in node.lines {
|
for line in node.lines {
|
||||||
g.sql_stmt_line(line, conn, node.or_expr)
|
g.sql_stmt_line(line, conn, node.or_expr)
|
||||||
}
|
}
|
||||||
@ -539,26 +516,13 @@ fn (mut g Gen) sql_select_expr(node ast.SqlExpr) {
|
|||||||
g.writeln('')
|
g.writeln('')
|
||||||
g.writeln('// orm')
|
g.writeln('// orm')
|
||||||
g.write('orm__Connection ${conn} = (orm__Connection){._')
|
g.write('orm__Connection ${conn} = (orm__Connection){._')
|
||||||
mut fn_prefix := ''
|
db_expr_type := g.get_db_type(node.db_expr) or {
|
||||||
typ := g.parse_db_type(node.db_expr)
|
verror('sql orm error - unknown db type for ${node.db_expr}')
|
||||||
match typ {
|
|
||||||
.sqlite3 {
|
|
||||||
fn_prefix = 'sqlite__DB'
|
|
||||||
}
|
|
||||||
.mysql {
|
|
||||||
fn_prefix = 'mysql__Connection'
|
|
||||||
}
|
|
||||||
.psql {
|
|
||||||
fn_prefix = 'pg__DB'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
verror('This database type `${typ}` is not implemented yet in orm') // TODO add better error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
db_expr_ctype_name := g.typ(db_expr_type)
|
||||||
g.write('${fn_prefix} = &')
|
g.write('${db_expr_ctype_name} = &')
|
||||||
g.expr(node.db_expr)
|
g.expr(node.db_expr)
|
||||||
g.writeln(', ._typ = _orm__Connection_${fn_prefix}_index};')
|
g.writeln(', ._typ = _orm__Connection_${db_expr_ctype_name}_index};')
|
||||||
g.sql_select(node, conn, left, node.or_expr)
|
g.sql_select(node, conn, left, node.or_expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -831,41 +795,21 @@ fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string, or_expr as
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut g Gen) parse_db_type(expr ast.Expr) SqlType {
|
fn (mut g Gen) get_db_type(expr ast.Expr) ?ast.Type {
|
||||||
match expr {
|
match expr {
|
||||||
ast.Ident {
|
ast.Ident {
|
||||||
if expr.info is ast.IdentVar {
|
if expr.info is ast.IdentVar {
|
||||||
return g.parse_db_from_type_string(g.table.get_final_type_name(expr.info.typ))
|
return g.table.unaliased_type(expr.info.typ)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast.SelectorExpr {
|
ast.SelectorExpr {
|
||||||
return g.parse_db_from_type_string(g.table.get_final_type_name(expr.typ))
|
return g.table.unaliased_type(expr.typ)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return .unknown
|
return none
|
||||||
}
|
|
||||||
}
|
|
||||||
return .unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) parse_db_from_type_string(name string) SqlType {
|
|
||||||
match name {
|
|
||||||
'sqlite.DB' {
|
|
||||||
return .sqlite3
|
|
||||||
}
|
|
||||||
'mysql.Connection' {
|
|
||||||
return .mysql
|
|
||||||
}
|
|
||||||
'pg.DB' {
|
|
||||||
return .psql
|
|
||||||
}
|
|
||||||
'mssql.Connection' {
|
|
||||||
return .mssql
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return .unknown
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut g Gen) get_table_name(table_expr ast.TypeNode) string {
|
fn (mut g Gen) get_table_name(table_expr ast.TypeNode) string {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
[table: 'Users']
|
[table: 'Users']
|
||||||
struct User {
|
struct User {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct VieterDb {
|
struct VieterDb {
|
||||||
conn sqlite.DB
|
conn sqlite.DB
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct Parent {
|
struct Parent {
|
||||||
id int [primary; sql: serial]
|
id int [primary; sql: serial]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct Upper {
|
struct Upper {
|
||||||
id int [primary; sql: serial]
|
id int [primary; sql: serial]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct Movie {
|
struct Movie {
|
||||||
id int [primary]
|
id int [primary]
|
||||||
|
@ -2,7 +2,7 @@ module main
|
|||||||
|
|
||||||
import vweb
|
import vweb
|
||||||
import time
|
import time
|
||||||
import sqlite
|
import db.sqlite
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
vweb.Context
|
vweb.Context
|
||||||
|
Loading…
Reference in New Issue
Block a user