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:
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;')
|
||||
}
|
||||
}
|
||||
228
vlib/db/sqlite/sqlite_orm_test.v
Normal file
228
vlib/db/sqlite/sqlite_orm_test.v
Normal file
@@ -0,0 +1,228 @@
|
||||
import orm
|
||||
import db.sqlite
|
||||
import time
|
||||
|
||||
struct TestCustomSqlType {
|
||||
id int [primary; sql: serial]
|
||||
custom string [sql_type: 'INTEGER']
|
||||
custom1 string [sql_type: 'TEXT']
|
||||
custom2 string [sql_type: 'REAL']
|
||||
custom3 string [sql_type: 'NUMERIC']
|
||||
custom4 string
|
||||
custom5 int
|
||||
custom6 time.Time
|
||||
}
|
||||
|
||||
struct TestDefaultAtribute {
|
||||
id string [primary; sql: serial]
|
||||
name string
|
||||
created_at string [default: 'CURRENT_TIME']
|
||||
created_at1 string [default: 'CURRENT_DATE']
|
||||
created_at2 string [default: 'CURRENT_TIMESTAMP']
|
||||
}
|
||||
|
||||
struct EntityToTest {
|
||||
id int [notnull; sql_type: 'INTEGER']
|
||||
smth string [notnull; sql_type: 'TEXT']
|
||||
}
|
||||
|
||||
fn test_sqlite_orm() {
|
||||
mut db := sqlite.connect(':memory:') or { panic(err) }
|
||||
defer {
|
||||
db.close() or { panic(err) }
|
||||
}
|
||||
db.create('Test', [
|
||||
orm.TableField{
|
||||
name: 'id'
|
||||
typ: typeof[int]().idx
|
||||
attrs: [
|
||||
StructAttribute{
|
||||
name: 'primary'
|
||||
},
|
||||
StructAttribute{
|
||||
name: 'sql'
|
||||
has_arg: true
|
||||
kind: .plain
|
||||
arg: 'serial'
|
||||
},
|
||||
]
|
||||
},
|
||||
orm.TableField{
|
||||
name: 'name'
|
||||
typ: typeof[string]().idx
|
||||
attrs: []
|
||||
},
|
||||
orm.TableField{
|
||||
name: 'age'
|
||||
typ: typeof[i64]().idx
|
||||
},
|
||||
]) or { panic(err) }
|
||||
|
||||
db.insert('Test', orm.QueryData{
|
||||
fields: ['name', 'age']
|
||||
data: [orm.string_to_primitive('Louis'), orm.i64_to_primitive(100)]
|
||||
}) or { panic(err) }
|
||||
|
||||
res := db.@select(orm.SelectConfig{
|
||||
table: 'Test'
|
||||
has_where: true
|
||||
fields: ['id', 'name', 'age']
|
||||
types: [typeof[int]().idx, typeof[string]().idx, typeof[i64]().idx]
|
||||
}, orm.QueryData{}, orm.QueryData{
|
||||
fields: ['name', 'age']
|
||||
data: [orm.Primitive('Louis'), i64(100)]
|
||||
types: [typeof[string]().idx, typeof[i64]().idx]
|
||||
is_and: [true, true]
|
||||
kinds: [.eq, .eq]
|
||||
}) or { panic(err) }
|
||||
|
||||
id := res[0][0]
|
||||
name := res[0][1]
|
||||
age := res[0][2]
|
||||
|
||||
assert id is int
|
||||
if id is int {
|
||||
assert id == 1
|
||||
}
|
||||
|
||||
assert name is string
|
||||
if name is string {
|
||||
assert name == 'Louis'
|
||||
}
|
||||
|
||||
assert age is i64
|
||||
if age is i64 {
|
||||
assert age == 100
|
||||
}
|
||||
|
||||
/** test orm sql type
|
||||
* - verify if all type create by attribute sql_type has created
|
||||
*/
|
||||
|
||||
sql db {
|
||||
create table TestCustomSqlType
|
||||
}
|
||||
|
||||
mut result_custom_sql, mut exec_custom_code := db.exec('
|
||||
pragma table_info(TestCustomSqlType);
|
||||
')
|
||||
|
||||
assert exec_custom_code == 101
|
||||
mut table_info_types_results := []string{}
|
||||
information_schema_custom_sql := ['INTEGER', 'INTEGER', 'TEXT', 'REAL', 'NUMERIC', 'TEXT',
|
||||
'INTEGER', 'INTEGER']
|
||||
|
||||
for data_type in result_custom_sql {
|
||||
table_info_types_results << data_type.vals[2]
|
||||
}
|
||||
assert table_info_types_results == information_schema_custom_sql
|
||||
|
||||
sql db {
|
||||
drop table TestCustomSqlType
|
||||
}
|
||||
|
||||
/** test default attribute
|
||||
*/
|
||||
|
||||
sql db {
|
||||
create table TestDefaultAtribute
|
||||
}
|
||||
|
||||
mut result_default_sql, mut code := db.exec('
|
||||
pragma table_info(TestDefaultAtribute);
|
||||
')
|
||||
|
||||
assert code == 101
|
||||
mut information_schema_data_types_results := []string{}
|
||||
information_schema_default_sql := ['', '', 'CURRENT_TIME', 'CURRENT_DATE', 'CURRENT_TIMESTAMP']
|
||||
|
||||
for data_type in result_default_sql {
|
||||
information_schema_data_types_results << data_type.vals[4]
|
||||
}
|
||||
assert information_schema_data_types_results == information_schema_default_sql
|
||||
|
||||
test_default_atribute := TestDefaultAtribute{
|
||||
name: 'Hitalo'
|
||||
}
|
||||
|
||||
sql db {
|
||||
insert test_default_atribute into TestDefaultAtribute
|
||||
}
|
||||
|
||||
result_test_default_atribute := sql db {
|
||||
select from TestDefaultAtribute limit 1
|
||||
}
|
||||
|
||||
assert result_test_default_atribute.name == 'Hitalo'
|
||||
assert test_default_atribute.created_at.len == 0
|
||||
assert test_default_atribute.created_at1.len == 0
|
||||
assert test_default_atribute.created_at2.len == 0
|
||||
assert result_test_default_atribute.created_at.len == 8 // HH:MM:SS
|
||||
assert result_test_default_atribute.created_at1.len == 10 // YYYY-MM-DD
|
||||
assert result_test_default_atribute.created_at2.len == 19 // YYYY-MM-DD HH:MM:SS
|
||||
|
||||
sql db {
|
||||
drop table TestDefaultAtribute
|
||||
}
|
||||
}
|
||||
|
||||
fn test_get_affected_rows_count() {
|
||||
mut db := sqlite.connect(':memory:') or { panic(err) }
|
||||
defer {
|
||||
db.close() or { panic(err) }
|
||||
}
|
||||
|
||||
db.exec('create table EntityToTest(
|
||||
id integer not null constraint tbl_pk primary key,
|
||||
smth integer
|
||||
);')
|
||||
|
||||
fst := EntityToTest{
|
||||
id: 1
|
||||
smth: '1'
|
||||
}
|
||||
|
||||
sql db {
|
||||
insert fst into EntityToTest
|
||||
} or { panic('first insert failed') }
|
||||
|
||||
assert db.get_affected_rows_count() == 1
|
||||
|
||||
snd := EntityToTest{
|
||||
id: 1
|
||||
smth: '2'
|
||||
}
|
||||
|
||||
mut sndfailed := false
|
||||
sql db {
|
||||
insert snd into EntityToTest
|
||||
} or { sndfailed = true }
|
||||
|
||||
assert db.get_affected_rows_count() == 0
|
||||
assert sndfailed
|
||||
|
||||
all := sql db {
|
||||
select from EntityToTest
|
||||
}
|
||||
assert 1 == all.len
|
||||
|
||||
sql db {
|
||||
update EntityToTest set smth = '2' where id == 1
|
||||
}
|
||||
assert db.get_affected_rows_count() == 1
|
||||
|
||||
sql db {
|
||||
update EntityToTest set smth = '2' where id == 2
|
||||
}
|
||||
assert db.get_affected_rows_count() == 0
|
||||
|
||||
sql db {
|
||||
delete from EntityToTest where id == 2
|
||||
}
|
||||
assert db.get_affected_rows_count() == 0
|
||||
|
||||
sql db {
|
||||
delete from EntityToTest where id == 1
|
||||
}
|
||||
assert db.get_affected_rows_count() == 1
|
||||
}
|
||||
92
vlib/db/sqlite/sqlite_test.v
Normal file
92
vlib/db/sqlite/sqlite_test.v
Normal file
@@ -0,0 +1,92 @@
|
||||
import db.sqlite
|
||||
|
||||
type Connection = sqlite.DB
|
||||
|
||||
struct User {
|
||||
pub:
|
||||
id int [primary; sql: serial]
|
||||
name string
|
||||
}
|
||||
|
||||
type Content = []u8 | string
|
||||
|
||||
struct Host {
|
||||
pub:
|
||||
db Connection
|
||||
}
|
||||
|
||||
fn (back Host) get_users() []User {
|
||||
return []
|
||||
}
|
||||
|
||||
fn create_host(db Connection) Host {
|
||||
sql db {
|
||||
create table User
|
||||
}
|
||||
|
||||
return Host{
|
||||
db: db
|
||||
}
|
||||
}
|
||||
|
||||
fn test_sqlite() {
|
||||
$if !linux {
|
||||
return
|
||||
}
|
||||
mut db := sqlite.connect(':memory:') or { panic(err) }
|
||||
assert db.is_open
|
||||
db.exec('drop table if exists users')
|
||||
db.exec("create table users (id integer primary key, name text default '');")
|
||||
db.exec("insert into users (name) values ('Sam')")
|
||||
assert db.last_insert_rowid() == 1
|
||||
assert db.get_affected_rows_count() == 1
|
||||
db.exec("insert into users (name) values ('Peter')")
|
||||
assert db.last_insert_rowid() == 2
|
||||
db.exec("insert into users (name) values ('Kate')")
|
||||
assert db.last_insert_rowid() == 3
|
||||
nr_users := db.q_int('select count(*) from users')
|
||||
assert nr_users == 3
|
||||
name := db.q_string('select name from users where id = 1')
|
||||
assert name == 'Sam'
|
||||
|
||||
// this insert will be rejected due to duplicated id
|
||||
db.exec("insert into users (id,name) values (1,'Sam')")
|
||||
assert db.get_affected_rows_count() == 0
|
||||
|
||||
users, mut code := db.exec('select * from users')
|
||||
assert users.len == 3
|
||||
assert code == 101
|
||||
code = db.exec_none('vacuum')
|
||||
assert code == 101
|
||||
user := db.exec_one('select * from users where id = 3') or { panic(err) }
|
||||
println(user)
|
||||
assert user.vals.len == 2
|
||||
|
||||
db.exec("update users set name='zzzz' where name='qqqq'")
|
||||
assert db.get_affected_rows_count() == 0
|
||||
|
||||
db.exec("update users set name='Peter1' where name='Peter'")
|
||||
assert db.get_affected_rows_count() == 1
|
||||
|
||||
db.exec("delete from users where name='qqqq'")
|
||||
assert db.get_affected_rows_count() == 0
|
||||
|
||||
db.exec("delete from users where name='Sam'")
|
||||
assert db.get_affected_rows_count() == 1
|
||||
|
||||
db.close() or { panic(err) }
|
||||
assert !db.is_open
|
||||
}
|
||||
|
||||
fn test_can_access_sqlite_result_consts() {
|
||||
assert sqlite.sqlite_ok == 0
|
||||
assert sqlite.sqlite_error == 1
|
||||
// assert sqlite.misuse == 21
|
||||
assert sqlite.sqlite_row == 100
|
||||
assert sqlite.sqlite_done == 101
|
||||
}
|
||||
|
||||
fn test_alias_db() {
|
||||
create_host(sqlite.connect(':memory:')!)
|
||||
assert true
|
||||
}
|
||||
284
vlib/db/sqlite/sqlite_vfs_lowlevel_test.v
Normal file
284
vlib/db/sqlite/sqlite_vfs_lowlevel_test.v
Normal file
@@ -0,0 +1,284 @@
|
||||
import db.sqlite
|
||||
import rand
|
||||
|
||||
const (
|
||||
max_file_name_len = 256
|
||||
)
|
||||
|
||||
fn test_vfs_register() {
|
||||
org_default_vfs := sqlite.get_default_vfs()?
|
||||
|
||||
assert org_default_vfs.zName != 0
|
||||
|
||||
vfs_name := 'sometest'
|
||||
mut vfs_descr := &sqlite.Sqlite3_vfs{
|
||||
zName: vfs_name.str
|
||||
iVersion: 2
|
||||
}
|
||||
|
||||
if _ := sqlite.get_vfs(vfs_name) {
|
||||
panic('expected that vfs is not known')
|
||||
}
|
||||
|
||||
vfs_descr.register_as_nondefault() or { panic('vfs register failed ${err}') }
|
||||
|
||||
sqlite.get_vfs(vfs_name)?
|
||||
|
||||
now_default_vfs := sqlite.get_default_vfs()?
|
||||
|
||||
assert now_default_vfs.zName == org_default_vfs.zName
|
||||
|
||||
vfs_descr.unregister() or { panic('vfs unregister failed ${err}') }
|
||||
|
||||
if _ := sqlite.get_vfs(vfs_name) {
|
||||
panic('vfs supposedly unregistered yet somehow still foundable')
|
||||
}
|
||||
}
|
||||
|
||||
// minimal vfs based on example https://www.sqlite.org/src/doc/trunk/src/test_demovfs.c
|
||||
fn test_verify_vfs_is_actually_used() {
|
||||
wrapped := sqlite.get_default_vfs()?
|
||||
|
||||
vfs_name := 'sometest'
|
||||
mut vfs_state := &ExampleVfsState{
|
||||
log: []string{cap: 100}
|
||||
}
|
||||
mut vfs_descr := &sqlite.Sqlite3_vfs{
|
||||
iVersion: 2
|
||||
szOsFile: int(sizeof(ExampleVfsOpenedFile))
|
||||
mxPathname: max_file_name_len
|
||||
zName: vfs_name.str
|
||||
pAppData: vfs_state
|
||||
xOpen: example_vfs_open
|
||||
xDelete: example_vfs_delete
|
||||
xAccess: example_vfs_access
|
||||
xFullPathname: example_vfs_fullpathname
|
||||
xDlOpen: wrapped.xDlOpen
|
||||
xDlError: wrapped.xDlError
|
||||
xDlSym: wrapped.xDlSym
|
||||
xDlClose: wrapped.xDlClose
|
||||
xRandomness: wrapped.xRandomness
|
||||
xSleep: wrapped.xSleep
|
||||
xCurrentTime: wrapped.xCurrentTime
|
||||
xGetLastError: example_vfs_getlasterror
|
||||
xCurrentTimeInt64: wrapped.xCurrentTimeInt64
|
||||
}
|
||||
|
||||
vfs_descr.register_as_nondefault()?
|
||||
|
||||
// normally this would be written to disk
|
||||
mut db := sqlite.connect_full('foo.db', [.readwrite, .create], vfs_name)!
|
||||
assert ['fullpathname from=foo.db to=foo.db}', 'open temp?=false name=foo.db', 'read file=foo.db'] == vfs_state.log
|
||||
vfs_state.log.clear()
|
||||
|
||||
db.close()!
|
||||
assert ['close file=foo.db'] == vfs_state.log
|
||||
}
|
||||
|
||||
struct ExampleVfsState {
|
||||
mut:
|
||||
log []string
|
||||
}
|
||||
|
||||
struct ExampleVfsOpenedFile {
|
||||
mut:
|
||||
base sqlite.Sqlite3_file
|
||||
name string
|
||||
vfs_state &ExampleVfsState
|
||||
}
|
||||
|
||||
fn to_vfsstate(t &sqlite.Sqlite3_vfs) &ExampleVfsState {
|
||||
unsafe {
|
||||
p := t.pAppData
|
||||
if p == 0 {
|
||||
assert false, 'p should not be 0'
|
||||
}
|
||||
return &ExampleVfsState(p)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_vfsopenedfile(t &sqlite.Sqlite3_file) &ExampleVfsOpenedFile {
|
||||
unsafe {
|
||||
if t == 0 {
|
||||
assert false, 't should not be 0'
|
||||
}
|
||||
return &ExampleVfsOpenedFile(t)
|
||||
}
|
||||
}
|
||||
|
||||
fn example_vfs_fullpathname(vfs &sqlite.Sqlite3_vfs, input &char, size_of_output int, output &char) int {
|
||||
println('fullpathname called')
|
||||
|
||||
mut vfs_state := to_vfsstate(vfs)
|
||||
|
||||
from := unsafe { cstring_to_vstring(input) }
|
||||
|
||||
unsafe {
|
||||
vmemcpy(output, input, from.len)
|
||||
output[from.len] = u8(0)
|
||||
}
|
||||
result := unsafe { cstring_to_vstring(output) }
|
||||
|
||||
vfs_state.log << 'fullpathname from=${from} to=${result}}'
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfs_access(vfs &sqlite.Sqlite3_vfs, zPath &char, flags int, pResOut &int) int {
|
||||
println('access called')
|
||||
mut vfs_state := &ExampleVfsState{}
|
||||
|
||||
unsafe {
|
||||
assert 0 != vfs.pAppData
|
||||
vfs_state = &ExampleVfsState(vfs.pAppData)
|
||||
}
|
||||
vfs_state.log << 'accessed'
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfs_open(vfs &sqlite.Sqlite3_vfs, file_name_or_null_for_tempfile &char, vfs_opened_file &sqlite.Sqlite3_file, in_flags int, out_flags &int) int {
|
||||
println('open called')
|
||||
|
||||
mut is_temp := false
|
||||
mut file_name := ''
|
||||
|
||||
unsafe {
|
||||
if file_name_or_null_for_tempfile == nil {
|
||||
is_temp = true
|
||||
file_name = rand.uuid_v4()
|
||||
} else {
|
||||
file_name = cstring_to_vstring(file_name_or_null_for_tempfile)
|
||||
}
|
||||
}
|
||||
mut vfs_state := to_vfsstate(vfs)
|
||||
|
||||
unsafe {
|
||||
mut outp := to_vfsopenedfile(vfs_opened_file)
|
||||
outp.base.pMethods = &sqlite.Sqlite3_io_methods{
|
||||
iVersion: 1
|
||||
xClose: example_vfsfile_close
|
||||
xRead: example_vfsfile_read
|
||||
xWrite: example_vfsfile_write
|
||||
xTruncate: example_vfsfile_truncate
|
||||
xSync: example_vfsfile_sync
|
||||
xFileSize: example_vfsfile_size
|
||||
xLock: example_vfsfile_lock
|
||||
xUnlock: example_vfsfile_unlock
|
||||
xCheckReservedLock: example_vfsfile_checkreservedlock
|
||||
xFileControl: example_vfsfile_filecontrol
|
||||
xSectorSize: example_vfsfile_sectorsize
|
||||
xDeviceCharacteristics: example_vfsfile_devicecharacteristics
|
||||
}
|
||||
|
||||
outp.name = file_name.clone()
|
||||
outp.vfs_state = vfs_state
|
||||
}
|
||||
vfs_state.log << 'open temp?=${is_temp} name=${file_name}'
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfsfile_checkreservedlock(file &sqlite.Sqlite3_file, pResOut &int) int {
|
||||
println('file checkreservedlock')
|
||||
|
||||
unsafe {
|
||||
*pResOut = 0
|
||||
}
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfsfile_filecontrol(file &sqlite.Sqlite3_file, op int, arg voidptr) int {
|
||||
println('file filecontrol')
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
fn example_vfsfile_devicecharacteristics(file &sqlite.Sqlite3_file) int {
|
||||
println('file devicecharacteristics')
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
fn example_vfsfile_size(file &sqlite.Sqlite3_file, result &i64) int {
|
||||
println('file size')
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfsfile_read(file &sqlite.Sqlite3_file, output voidptr, amount int, offset i64) int {
|
||||
println('file read')
|
||||
|
||||
assert amount > 0
|
||||
|
||||
mut vfsfile := to_vfsopenedfile(file)
|
||||
|
||||
vfsfile.vfs_state.log << 'read file=${vfsfile.name}'
|
||||
|
||||
unsafe {
|
||||
C.memset(output, 0, amount)
|
||||
}
|
||||
|
||||
return sqlite.sqlite_ioerr_short_read
|
||||
}
|
||||
|
||||
fn example_vfsfile_truncate(file &sqlite.Sqlite3_file, size i64) int {
|
||||
println('file truncate')
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfsfile_sectorsize(file &sqlite.Sqlite3_file) int {
|
||||
println('file sectorsize')
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
fn example_vfsfile_sync(file &sqlite.Sqlite3_file, flags int) int {
|
||||
println('file sync called')
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfsfile_lock(file &sqlite.Sqlite3_file, elock int) int {
|
||||
println('file lock called')
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfsfile_unlock(file &sqlite.Sqlite3_file, elock int) int {
|
||||
println('file unlock called')
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfsfile_write(file &sqlite.Sqlite3_file, buf voidptr, amount int, offset i64) int {
|
||||
println('file write called')
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfsfile_close(file &sqlite.Sqlite3_file) int {
|
||||
println('file close called')
|
||||
|
||||
mut vfsfile := to_vfsopenedfile(file)
|
||||
|
||||
vfsfile.vfs_state.log << 'close file=${vfsfile.name}'
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfs_delete(vfs &sqlite.Sqlite3_vfs, name &char, sync_dir int) int {
|
||||
println('vfs delete called')
|
||||
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
|
||||
fn example_vfs_getlasterror(vfs &sqlite.Sqlite3_vfs, i int, o &char) int {
|
||||
println('vfs getlasterror called')
|
||||
|
||||
unsafe {
|
||||
*o = 0
|
||||
}
|
||||
return sqlite.sqlite_ok
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user