Initial project import

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

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
## 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
On **any platform** (Windows, Linux, macOS), you can run:
`v vlib/db/sqlite/install_thirdparty_sqlite.vsh`
This downloads the SQLite amalgamation source and places it in
`v/thirdparty/sqlite`. V will then compile it automatically
during your build.
If you need to install SQLite manually, use the SQLite
**amalgamation** package. `v/thirdparty/sqlite` must contain
`sqlite3.c` and `sqlite3.h` directly.
Do not use the autoconf/source package here, and do not rename
`sqlite3.c` to `sqlite3.cpp`.
On **macOS**, V can also fall back to the system `libsqlite3`
when `pkg-config` is not available, so an extra install is often
not needed.
On **Linux**, you can also install the system development package
instead:
- Debian/Ubuntu: `sudo apt install -y libsqlite3-dev`
- Fedora/RHEL: `sudo dnf -y install sqlite-devel`
- Arch: `sudo pacman -S sqlite`
# Convenience Methods
The `DB` struct provides several helper methods for common
introspection queries:
```v
import db.sqlite
db := sqlite.connect('mydb.db') or { panic(err) }
// List all user tables
tables := db.tables()!
// Get column names for a table
cols := db.columns('users')!
// Get CREATE statements (single table or all objects)
s := db.schema('users')!
// Database file size in bytes
size := db.db_size()!
```
# Interactive CLI
V includes a built-in SQLite CLI as a replacement for `sqlite3`:
```sh
v sqlite mydb.db
```
Features include a full readline REPL with history and tab
completion, 9 output modes (`table`, `box`, `markdown`, `csv`,
`json`, `line`, `html`, `insert`, `quote`), `.dump`,
`.import`/`.export`, `.backup`, session control, and schema tools.
Run `.help` inside the REPL for the full command list.
# 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)!
```
@@ -0,0 +1,60 @@
#!/usr/bin/env v
import os
import net.http
import compress.szip
import crypto.sha3
fn should_be_ok(http_status_code int, msg string) {
assert http_status_code == 200, '${msg}. Check your internet connection and try again later.'
}
fn main() {
os.chdir(@VEXEROOT)!
download_page_url := 'https://sqlite.org/download.html'
println('> Getting ${download_page_url} ...')
download_page := http.get(download_page_url)!
should_be_ok(download_page.status_code, 'The download page of SQLite is not available now.')
dlines := download_page.body.split_into_lines()
amalgamation_csv := dlines.filter(|line| line.starts_with('PRODUCT,')
&& line.contains('amalgamation'))[0].split(',')
assert amalgamation_csv.len >= 4
version := amalgamation_csv[1]
zip_name := os.file_name(amalgamation_csv[2])
zip_url := 'https://sqlite.org/${amalgamation_csv[2]}'
url_size := amalgamation_csv[3].int()
url_sha3 := amalgamation_csv[4]
println('> Getting SQLite amalgamation version: ${version}')
println('> from url: ${zip_url}')
println('> expected size: ${url_size}')
println('> expected SHA3: ${url_sha3} ...')
amalgamation_name := zip_name.to_lower().replace('.zip', '')
println('> Downloading from ${zip_url} ...')
zip_content := http.get(zip_url)!
should_be_ok(zip_content.status_code, 'The .zip file URL of SQLite is not available now.')
assert zip_content.body.len == url_size
println('> download size: ${zip_content.body.len} matches expected size: ${url_size} .')
zip_shasum := sha3.sum256(zip_content.body.bytes()).hex()
assert zip_shasum == url_sha3
println('> download sha3: ${zip_shasum} matches too.')
os.write_file(zip_name, zip_content.body)!
assert os.is_file(zip_name)
assert szip.extract_zip_to_dir(zip_name, 'thirdparty')!
os.rmdir_all('thirdparty/sqlite') or {}
os.mv('thirdparty/${amalgamation_name}', 'thirdparty/sqlite')!
os.rm('thirdparty/sqlite/shell.c')!
files := os.walk_ext('thirdparty/sqlite', '')
for f in files {
println('> extracted file: ${f:-40s} | size: ${os.file_size(f):8}')
}
println('> removing ${zip_name} ...')
os.rm(zip_name)!
println('> done')
}
+295
View File
@@ -0,0 +1,295 @@
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 {
where_with_tenant := orm.apply_tenant_filter(config.table, where)
$if trace_orm_where ? {
eprintln('> sqlite.select: where.fields.len = ${where.fields.len}')
eprintln('> sqlite.select: where.kinds.len = ${where.kinds.len}')
}
// 1. Create query and bind necessary data
query := orm.orm_select_gen(config, '`', true, '?', 1, where_with_tenant)
$if trace_sqlite ? {
eprintln('> select query: "${query}"')
}
stmt := db.new_init_stmt(query)!
defer {
stmt.finalize()
}
mut c := 1
sqlite_stmt_binder(stmt, where_with_tenant, query, mut c)!
sqlite_stmt_binder(stmt, data, query, mut c)!
mut ret := [][]orm.Primitive{}
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 orm.Table, data orm.QueryData) ! {
query, converted_data :=
orm.orm_stmt_gen(.sqlite, 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 orm.Table, data orm.QueryData, where orm.QueryData) ! {
where_with_tenant := orm.apply_tenant_filter(table, where)
query, _ := orm.orm_stmt_gen(.sqlite, table, '`', .update, true, '?', 1, data,
where_with_tenant)
sqlite_stmt_worker(db, query, data, where_with_tenant)!
}
// delete is used internally by V's ORM for processing `DELETE ` queries
pub fn (db DB) delete(table orm.Table, where orm.QueryData) ! {
where_with_tenant := orm.apply_tenant_filter(table, where)
query, _ := orm.orm_stmt_gen(.sqlite, table, '`', .delete, true, '?', 1, orm.QueryData{},
where_with_tenant)
sqlite_stmt_worker(db, query, orm.QueryData{}, where_with_tenant)!
}
// last_id is used internally by V's ORM for post-processing `INSERT ` queries
pub fn (db DB) last_id() int {
query := 'SELECT last_insert_rowid();'
return db.q_int(query) or { 0 }
}
// 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 orm.Table, fields []orm.TableField) ! {
query := orm.orm_table_gen(.sqlite, 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 orm.Table) ! {
query := 'DROP TABLE `${table.name}`;'
sqlite_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
}
// orm_begin starts a transaction for ORM helpers.
pub fn (mut db DB) orm_begin() ! {
db.begin()!
}
// orm_commit commits a transaction for ORM helpers.
pub fn (mut db DB) orm_commit() ! {
db.commit()!
}
// orm_rollback rolls back a transaction for ORM helpers.
pub fn (mut db DB) orm_rollback() ! {
db.rollback()!
}
// orm_savepoint creates a savepoint for ORM helpers.
pub fn (mut db DB) orm_savepoint(name string) ! {
db.savepoint(name)!
}
// orm_rollback_to rolls back to a savepoint for ORM helpers.
pub fn (mut db DB) orm_rollback_to(name string) ! {
db.rollback_to(name)!
}
// orm_release_savepoint releases a savepoint for ORM helpers.
pub fn (mut db DB) orm_release_savepoint(name string) ! {
db.release_savepoint(name)!
}
// 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, mut c, data)
if err != 0 {
return stmt.db.error_message(err, query)
}
if !sqlite_primitive_is_array(data) {
c++
}
}
}
fn sqlite_primitive_is_array(data orm.Primitive) bool {
return match data {
[]orm.Primitive, []bool, []f32, []f64, []i16, []i64, []i8, []int, []string, []time.Time,
[]u16, []u32, []u64, []u8, []orm.InfixType {
true
}
else {
false
}
}
}
fn bind_array[T](stmt Stmt, mut c &int, data []T) int {
mut err := 0
for element in data {
tmp_err := bind(stmt, mut c, orm.Primitive(element))
c++
if tmp_err != 0 {
err = tmp_err
break
}
}
return err
}
// Universal bind function
fn bind(stmt Stmt, mut 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, f64(data))
}
string {
err = stmt.bind_text(c, data)
}
time.Time {
err = stmt.bind_int(c, int(data.unix()))
}
orm.InfixType {
err = bind(stmt, mut c, data.right)
}
orm.Null {
err = stmt.bind_null(c)
}
[]orm.Primitive {
err = bind_array(stmt, mut c, data)
}
[]bool {
err = bind_array(stmt, mut c, data)
}
[]f32 {
err = bind_array(stmt, mut c, data)
}
[]f64 {
err = bind_array(stmt, mut c, data)
}
[]i16 {
err = bind_array(stmt, mut c, data)
}
[]i64 {
err = bind_array(stmt, mut c, data)
}
[]i8 {
err = bind_array(stmt, mut c, data)
}
[]int {
err = bind_array(stmt, mut c, data)
}
[]string {
err = bind_array(stmt, mut c, data)
}
[]time.Time {
err = bind_array(stmt, mut c, data)
}
[]u16 {
err = bind_array(stmt, mut c, data)
}
[]u32 {
err = bind_array(stmt, mut c, data)
}
[]u64 {
err = bind_array(stmt, mut c, data)
}
[]u8 {
err = bind_array(stmt, mut c, data)
}
[]orm.InfixType {
err = bind_array(stmt, mut c, data)
}
}
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 {
if typ in orm.nums || typ == -1 {
return stmt.get_int(idx) or { return orm.Null{} }
} else if typ in orm.num64 {
return stmt.get_i64(idx) or { return orm.Null{} }
} else if typ == typeof[f32]().idx {
return f32(stmt.get_f64(idx) or { return orm.Null{} })
} else if typ == typeof[f64]().idx {
return stmt.get_f64(idx) or { return orm.Null{} }
} else if typ == orm.type_string {
if v := stmt.get_text(idx) {
return v.clone()
} else {
return orm.Null{}
}
} else if typ == orm.enum_ {
return stmt.get_i64(idx) or { return orm.Null{} }
} else if typ == orm.time_ {
if v := stmt.get_int(idx) {
return time.unix(v)
} else {
return orm.Null{}
}
} else {
return error('Unknown type ${typ}')
}
}
// Convert type int to sql type string
fn sqlite_type_from_v(typ int) !string {
return if typ in orm.nums || typ in orm.num64 || typ in [orm.serial, orm.time_, orm.enum_] {
'INTEGER'
} else if typ in orm.float {
'REAL'
} else if typ == orm.type_string {
'TEXT'
} else {
error('Unknown type ${typ}')
}
}
@@ -0,0 +1,93 @@
// vtest build: present_sqlite3?
import db.sqlite
struct Parent {
id int @[primary; sql: serial]
name string
children []Child @[fkey: 'parent_id']
}
struct Child {
id int @[primary; sql: serial]
name string
parent_id int
babies []Baby @[fkey: 'child_id']
}
struct Baby {
id int @[primary; sql: serial]
name string
child_id int
}
fn test_main() {
mut db := sqlite.connect(':memory:')!
sql db {
create table Parent
create table Child
create table Baby
} or { panic(err) }
new_parent := Parent{
name: 'first parent'
children: [
Child{
name: 'first child'
},
Child{
name: 'second_child'
},
]
}
sql db {
insert new_parent into Parent
} or { panic(err) }
babies := [
Baby{
name: 'first baby'
child_id: 1
},
Baby{
name: 'second baby'
child_id: 1
},
Baby{
name: 'third baby'
child_id: 2
},
Baby{
name: 'fourth baby'
child_id: 2
},
Baby{
name: 'fifth baby'
child_id: 2
},
]
for v in babies {
sql db {
insert v into Baby
} or { panic(err) }
}
parent := sql db {
select from Parent
} or { panic(err) }
assert parent[0].children[0].id == 1
assert parent[0].children[1].id == 2
assert parent[0].children.len == 2
assert parent[0].children[0].babies.len == 2
assert parent[0].children[1].babies.len == 3
assert parent[0].children[0].babies[0].name == 'first baby'
assert parent[0].children[0].babies[1].name == 'second baby'
assert parent[0].children[1].babies[0].name == 'third baby'
assert parent[0].children[1].babies[1].name == 'fourth baby'
assert parent[0].children[1].babies[2].name == 'fifth baby'
db.close()!
}
+38
View File
@@ -0,0 +1,38 @@
module sqlite
pub struct ConnectionPool {
mut:
connections chan DB
config string
}
// new_connection_pool creates a new connection pool with the given size and configuration.
pub fn new_connection_pool(config string, size int) !ConnectionPool {
mut connections := chan DB{cap: size}
for _ in 0 .. size {
conn := connect(config)!
connections <- conn
}
return ConnectionPool{
connections: connections
config: config
}
}
// acquire gets a connection from the pool
pub fn (mut pool ConnectionPool) acquire() !DB {
return <-pool.connections or { return error('Failed to acquire a connection from the pool') }
}
// release returns a connection back to the pool.
pub fn (mut pool ConnectionPool) release(conn DB) {
pool.connections <- conn
}
// close closes all connections in the pool.
pub fn (mut pool ConnectionPool) close() {
for _ in 0 .. pool.connections.len {
mut conn := <-pool.connections or { break }
conn.close() or { panic('Failed to close connection: ${err}') }
}
}
@@ -0,0 +1,30 @@
// vtest build: present_sqlite3? && gcc && !windows
// vtest vflags: -prod
import db.sqlite
@[table: 'issue_24800_foos']
struct Issue24800Foo {
id int @[primary; sql: serial]
name string
}
struct Issue24800App {
mut:
db sqlite.DB
}
fn issue_24800_memory_app() !Issue24800App {
db := sqlite.connect(':memory:')!
sql db {
create table Issue24800Foo
}!
return Issue24800App{
db: db
}
}
fn test_issue_24800_prod_gcc_orm_create_table() {
mut app := issue_24800_memory_app()!
assert app.db.q_int("select count(*) from sqlite_master where type='table' and name='issue_24800_foos'")! == 1
app.db.close()!
}
+118
View 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() }
}
+672
View File
@@ -0,0 +1,672 @@
module sqlite
$if freebsd || openbsd {
#flag -I/usr/local/include
#flag -L/usr/local/lib
}
$if tinyc {
#flag -DSQLITE_DISABLE_INTRINSIC
}
$if $pkgconfig('sqlite3') {
#pkgconfig sqlite3
#include "sqlite3.h" # The SQLite header file is missing. Please install the corresponding development package.
} $else $if windows {
#flag -I@VEXEROOT/thirdparty/sqlite
#flag @VEXEROOT/thirdparty/sqlite/sqlite3.o
#include "sqlite3.h" # The SQLite header file is missing. Please run vlib/db/sqlite/install_thirdparty_sqlite.vsh to download an SQLite amalgamation.
} $else $if darwin {
// macOS ships libsqlite3, so do not require a separately downloaded amalgamation.
#flag darwin -lsqlite3
} $else {
#flag -I@VEXEROOT/thirdparty/sqlite
#include "sqlite3.h" # The SQLite header file is missing. Please run vlib/db/sqlite/install_thirdparty_sqlite.vsh to download an SQLite amalgamation.
#flag @VEXEROOT/thirdparty/sqlite/sqlite3.c
}
$if linux {
#flag -lm
}
// https://www.sqlite.org/rescode.html
pub const sqlite_ok = 0
pub const sqlite_error = 1
pub const sqlite_row = 100
pub const sqlite_done = 101
pub const sqlite_cantopen = 14
pub const sqlite_ioerr_read = 266
pub const sqlite_ioerr_short_read = 522
pub const sqlite_ioerr_write = 778
pub const sqlite_ioerr_fsync = 1034
pub const sqlite_ioerr_fstat = 1802
pub const sqlite_ioerr_delete = 2570
pub const sqlite_open_main_db = 0x00000100
pub const sqlite_open_temp_db = 0x00000200
pub const sqlite_open_transient_db = 0x00000400
pub const sqlite_open_main_journal = 0x00000800
pub const sqlite_open_temp_journal = 0x00001000
pub const sqlite_open_subjournal = 0x00002000
pub const sqlite_open_super_journal = 0x00004000
pub const sqlite_open_wal = 0x00080000
pub enum SyncMode {
off
normal
full
}
pub enum Sqlite3TransactionLevel {
deferred
immediate
exclusive
}
pub enum JournalMode {
off
delete
truncate
persist
memory
}
pub struct C.sqlite3 {
}
pub 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
names []string
}
// val returns the value at `index`.
pub fn (row Row) val(index int) string {
return row.vals[index]
}
// values returns all row values.
pub fn (row Row) values() []string {
return row.vals.clone()
}
// get_string returns the value for the given column name, or '' if the column is not found
// or if the corresponding value index is out of range.
pub fn (r &Row) get_string(col_name string) string {
for i, name in r.names {
if name == col_name {
if i < r.vals.len {
return r.vals[i]
}
return ''
}
}
return ''
}
// get_int returns the integer value for the given column name, or 0 if the column is not found.
pub fn (r &Row) get_int(col_name string) int {
return r.get_string(col_name).int()
}
pub type Params = []string | [][]string
fn sqlite_cstring(s string) &char {
cstr := unsafe { malloc_noscan(s.len + 1) }
unsafe {
if s.len > 0 {
vmemcpy(cstr, s.str, s.len)
}
cstr[s.len] = 0
}
return &char(cstr)
}
//
fn C.sqlite3_open(&char, &&C.sqlite3) i32
fn C.sqlite3_close(&C.sqlite3) i32
fn C.sqlite3_busy_timeout(db &C.sqlite3, ms i32) i32
fn C.sqlite3_last_insert_rowid(&C.sqlite3) i64
//
fn C.sqlite3_prepare_v2(&C.sqlite3, &char, i32, &&C.sqlite3_stmt, &&char) i32
fn C.sqlite3_step(&C.sqlite3_stmt) i32
fn C.sqlite3_reset(&C.sqlite3_stmt) i32
fn C.sqlite3_finalize(&C.sqlite3_stmt) i32
//
fn C.sqlite3_column_name(&C.sqlite3_stmt, i32) &char
fn C.sqlite3_column_text(&C.sqlite3_stmt, i32) &u8
fn C.sqlite3_column_int(&C.sqlite3_stmt, i32) i32
fn C.sqlite3_column_int64(&C.sqlite3_stmt, i32) i64
fn C.sqlite3_column_double(&C.sqlite3_stmt, i32) f64
fn C.sqlite3_column_count(&C.sqlite3_stmt) i32
fn C.sqlite3_column_type(&C.sqlite3_stmt, i32) i32
fn C.sqlite3_column_bytes(&C.sqlite3_stmt, i32) i32
//
fn C.sqlite3_errstr(i32) &char
fn C.sqlite3_errmsg(&C.sqlite3) &char
fn C.sqlite3_free(voidptr)
fn C.sqlite3_changes(&C.sqlite3) i32
// connect Opens the connection with a database.
pub fn connect(path string) !DB {
db := &C.sqlite3(unsafe { nil })
path_cstr := sqlite_cstring(path)
defer {
unsafe { free(path_cstr) }
}
code := C.sqlite3_open(path_cstr, &db)
if code != 0 {
return &SQLError{
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db))) }
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() ! {
code := C.sqlite3_close(db.conn)
if code == 0 {
db.is_open = false
} else {
return &SQLError{
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db.conn))) }
code: code
}
}
}
// 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`, or an error on failure
pub fn (db &DB) q_int(query string) !int {
$if trace_sqlite ? {
eprintln('> q_int query: "${query}"')
}
stmt := &C.sqlite3_stmt(unsafe { nil })
pres := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
if pres != sqlite_ok {
return db.error_message(pres, query)
}
defer {
C.sqlite3_finalize(stmt)
}
code := C.sqlite3_step(stmt)
if code != sqlite_row {
return db.error_message(code, query)
}
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`, or an error on failure
pub fn (db &DB) q_string(query string) !string {
$if trace_sqlite ? {
eprintln('> q_string query: "${query}"')
}
stmt := &C.sqlite3_stmt(unsafe { nil })
pres := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
if pres != sqlite_ok {
return db.error_message(pres, query)
}
defer {
C.sqlite3_finalize(stmt)
}
code := C.sqlite3_step(stmt)
if code != sqlite_row {
return db.error_message(code, query)
}
val := unsafe { &u8(C.sqlite3_column_text(stmt, 0)) }
return if val != &u8(unsafe { nil }) { unsafe { tos_clone(val) } } else { '' }
}
// exec_map executes the query on the given `db`, and returns an array of maps of strings, or an error on failure
pub fn (db &DB) exec_map(query string) ![]map[string]string {
$if trace_sqlite ? {
eprintln('> exec_map query: "${query}"')
}
stmt := &C.sqlite3_stmt(unsafe { nil })
mut code := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
if code != sqlite_ok {
return db.error_message(code, query)
}
defer {
C.sqlite3_finalize(stmt)
}
nr_cols := C.sqlite3_column_count(stmt)
mut col_names := []string{cap: nr_cols}
for i in 0 .. nr_cols {
col_char := unsafe { &u8(C.sqlite3_column_name(stmt, i)) }
col_names << if col_char != unsafe { nil } { unsafe { tos_clone(col_char) } } else { '' }
}
mut res := 0
mut rows := []map[string]string{}
for {
res = C.sqlite3_step(stmt)
if res != sqlite_row {
break
}
mut row := map[string]string{}
for i in 0 .. nr_cols {
val := unsafe { &u8(C.sqlite3_column_text(stmt, i)) }
row[col_names[i]] = if val != unsafe { nil } { unsafe { tos_clone(val) } } else { '' }
}
rows << row
}
return rows
}
fn C.sqlite3_memory_used() i64
// exec executes the query on the given `db`, and returns an array of all the results, or an error on failure
pub fn (db &DB) exec(query string) ![]Row {
$if trace_sqlite ? {
eprintln('> exec query: "${query}"')
}
stmt := &C.sqlite3_stmt(unsafe { nil })
mut code := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
if code != sqlite_ok {
return db.error_message(code, query)
}
defer {
C.sqlite3_finalize(stmt)
}
nr_cols := C.sqlite3_column_count(stmt)
mut col_names := []string{cap: nr_cols}
for i in 0 .. nr_cols {
col_char := unsafe { &u8(C.sqlite3_column_name(stmt, i)) }
col_names << if col_char != unsafe { nil } { unsafe { tos_clone(col_char) } } else { '' }
}
mut res := 0
mut rows := []Row{}
for {
res = C.sqlite3_step(stmt)
if res != sqlite_row {
break
}
mut row := Row{
names: col_names
}
for i in 0 .. nr_cols {
val := unsafe { &u8(C.sqlite3_column_text(stmt, i)) }
if val == &u8(unsafe { nil }) {
row.vals << ''
} else {
row.vals << unsafe { tos_clone(val) }
}
}
rows << row
}
return rows
}
// 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 := db.exec(query)!
defer {
unsafe { rows.free() }
}
if rows.len == 0 {
return &SQLError{
msg: 'No rows'
code: sqlite_done
}
}
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 {
$if trace_sqlite ? {
eprintln('> exec_none query: "${query}"')
}
stmt := &C.sqlite3_stmt(unsafe { nil })
pres := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
if pres != sqlite_ok {
return -1
}
defer {
C.sqlite3_finalize(stmt)
}
code := C.sqlite3_step(stmt)
return code
}
// exec_param_many executes a query with parameters provided as ?,
// and returns either an error on failure, or the full result set on success
pub fn (db &DB) exec_param_many(query string, params Params) ![]Row {
$if trace_sqlite ? {
eprintln('> exec_param_many query: "${query}", params: ${params}')
}
mut stmt := &C.sqlite3_stmt(unsafe { nil })
mut code := C.sqlite3_prepare_v2(db.conn, &char(query.str), -1, &stmt, 0)
if code != sqlite_ok {
return db.error_message(code, query)
}
defer {
C.sqlite3_finalize(stmt)
}
mut rows := []Row{}
nr_cols := C.sqlite3_column_count(stmt)
mut col_names := []string{cap: nr_cols}
for i in 0 .. nr_cols {
col_char := unsafe { &u8(C.sqlite3_column_name(stmt, i)) }
col_names << if col_char != unsafe { nil } { unsafe { tos_clone(col_char) } } else { '' }
}
if params is []string {
for i, param in params {
code = C.sqlite3_bind_text(stmt, i + 1, voidptr(param.str), param.len, 0)
if code != sqlite_ok {
return db.error_message(code, query)
}
}
for {
mut row := Row{
names: col_names
}
code = C.sqlite3_step(stmt)
if is_error(code) {
return db.error_message(code, query)
}
if code == sqlite_done {
break
}
for i in 0 .. nr_cols {
val := unsafe { &u8(C.sqlite3_column_text(stmt, i)) }
if val == &u8(unsafe { nil }) {
row.vals << ''
} else {
row.vals << unsafe { tos_clone(val) }
}
}
rows << row
}
} else if params is [][]string {
// Rows to process
for params_row in params {
mut row := Row{
names: col_names
}
// Param values to bind
for i, param in params_row {
code = C.sqlite3_bind_text(stmt, i + 1, voidptr(param.str), param.len, 0)
if code != sqlite_ok {
return db.error_message(code, query)
}
}
for {
code = C.sqlite3_step(stmt)
if is_error(code) {
return db.error_message(code, query)
}
if code == sqlite_done {
break
}
for i in 0 .. nr_cols {
val := unsafe { &u8(C.sqlite3_column_text(stmt, i)) }
if val == &u8(unsafe { nil }) {
row.vals << ''
} else {
row.vals << unsafe { tos_clone(val) }
}
}
rows << row
}
C.sqlite3_reset(stmt)
}
}
return rows
}
// exec_param executes a query with one parameter provided as a ?,
// 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 two parameters provided as ? placeholders.
pub fn (db &DB) exec_param2(query string, param string, param2 string) ![]Row {
return db.exec_param_many(query, [param, param2])
}
// 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 (mut 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.
// If the command fails to execute an error is returned
// .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.
// If the command fails to execute an error is returned
// .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;')!
}
}
@[params]
pub struct Sqlite3TransactionParam {
transaction_level Sqlite3TransactionLevel = .deferred
}
// begin begins a new transaction.
pub fn (mut db DB) begin(param Sqlite3TransactionParam) ! {
mut sql_stmt := 'BEGIN '
match param.transaction_level {
.deferred { sql_stmt += 'DEFERRED;' }
.immediate { sql_stmt += 'IMMEDIATE;' }
.exclusive { sql_stmt += 'EXCLUSIVE;' }
}
db.exec(sql_stmt)!
}
// savepoint create a new savepoint.
pub fn (mut db DB) savepoint(savepoint string) ! {
if !savepoint.is_identifier() {
return error('savepoint should be a identifier string')
}
db.exec('SAVEPOINT ${savepoint};')!
}
// commit commits the current transaction.
pub fn (mut db DB) commit() ! {
db.exec('COMMIT;')!
}
// rollback rollbacks the current transaction.
pub fn (mut db DB) rollback() ! {
db.exec('ROLLBACK;')!
}
// rollback_to rollbacks to a specified savepoint.
pub fn (mut db DB) rollback_to(savepoint string) ! {
if !savepoint.is_identifier() {
return error('savepoint should be a identifier string')
}
db.exec('ROLLBACK TO ${savepoint};')!
}
// release_savepoint releases a specified savepoint.
pub fn (mut db DB) release_savepoint(savepoint string) ! {
if !savepoint.is_identifier() {
return error('savepoint should be a identifier string')
}
db.exec('RELEASE SAVEPOINT ${savepoint};')!
}
// tables returns the names of all user tables in the database.
pub fn (db &DB) tables() ![]string {
rows :=
db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name")!
return rows.map(it.vals[0])
}
// columns returns the column names for the given table.
pub fn (db &DB) columns(table string) ![]string {
escaped := table.replace('"', '""')
rows := db.exec('PRAGMA table_info("${escaped}")')!
return rows.map(it.vals[1])
}
// schema returns the CREATE statement(s) for the given table, or for all
// objects if table is empty.
pub fn (db &DB) schema(table string) !string {
filter := if table != '' {
escaped := table.replace("'", "''")
"AND name='${escaped}'"
} else {
''
}
rows :=
db.exec("SELECT sql FROM sqlite_master WHERE type IN ('table','index','view','trigger') ${filter} AND sql IS NOT NULL ORDER BY type, name")!
return rows.map(it.vals[0]).join('\n\n')
}
// db_size returns the database file size in bytes, computed from page_count
// and page_size.
pub fn (db &DB) db_size() !i64 {
pc := db.exec('PRAGMA page_count')!
ps := db.exec('PRAGMA page_size')!
if pc.len == 0 || ps.len == 0 {
return 0
}
return pc[0].vals[0].i64() * ps[0].vals[0].i64()
}
// reset returns the connection to initial state for reuse
pub fn (mut db DB) reset() ! {
}
// validate checks if the connection is still usable
pub fn (mut db DB) validate() !bool {
return db.exec_none('SELECT 1') == 100
}
@@ -0,0 +1,45 @@
// vtest build: present_sqlite3?
module main
import db.sqlite
@[table: 'blog']
pub struct Blog {
id int @[primary; sql: serial]
slug string
language string
}
fn records_by_field[T](db sqlite.DB, fieldname string, value string) ![]T {
$for field in T.fields {
if field.name == fieldname {
entries := sql db {
select from Blog where field.name == value
} or { return err }
return entries
}
}
return error('fieldname not found')
}
fn test_main() {
mut db := sqlite.connect(':memory:')!
sql db {
create table Blog
}!
row := Blog{
slug: 'Test'
language: 'v'
}
sql db {
insert row into Blog
}!
rows := records_by_field[Blog](db, 'language', 'v') or {
println(err)
return
}
assert rows.len == 1
db.close()!
}
+25
View File
@@ -0,0 +1,25 @@
// vtest build: present_sqlite3? && !sanitize-memory-clang
import db.sqlite
struct Test {
value f32
value2 f64
}
fn test_main() {
conn := sqlite.connect(':memory:')!
data := Test{32.32, 64.64}
sql conn {
create table Test
insert data into Test
}!
s := sql conn {
select from Test
}!
assert s[0].value == 32.32
assert s[0].value2 == 64.64
}
@@ -0,0 +1,26 @@
// vtest build: present_sqlite3? && !windows && !sanitize-memory-clang && !docker-ubuntu-musl
import db.sqlite
@[table: 'foos']
struct Foo {
value int
}
struct State {
val ?int
}
fn test_main() {
val := ?int(none)
state := State{val}
db := sqlite.connect(':memory:')!
if state.val != none {
v := sql db {
select from Foo where value < state.val
}!
println(v)
assert false
} else {
assert true
}
}
+317
View File
@@ -0,0 +1,317 @@
// vtest build: present_sqlite3?
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 TestDefaultAttribute {
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 TestDurationAlias {
id int @[primary; sql: serial]
duration time.Duration
}
struct EntityToTest {
id int @[notnull; sql_type: 'INTEGER']
smth string @[notnull; sql_type: 'TEXT']
}
struct TutorialBlogArticle {
id int @[primary; sql: serial]
title string
text string
}
struct TutorialBlogApp {
mut:
db sqlite.DB
}
fn (app &TutorialBlogApp) find_all_tutorial_blog_articles() []TutorialBlogArticle {
return sql app.db {
select from TutorialBlogArticle
} or { panic(err) }
}
fn test_sqlite_orm() {
mut db := sqlite.connect(':memory:') or { panic(err) }
defer {
db.close() or { panic(err) }
}
table := orm.Table{
name: 'Test'
}
db.create(table, [
orm.TableField{
name: 'id'
typ: typeof[int]().idx
attrs: [
VAttribute{
name: 'primary'
},
VAttribute{
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(table, 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: table
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 := db.exec('
pragma table_info(TestCustomSqlType);
')!
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 TestDefaultAttribute
}!
mut result_default_sql := db.exec('
pragma table_info(TestDefaultAttribute);
')!
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_attribute := TestDefaultAttribute{
name: 'Hitalo'
}
sql db {
insert test_default_attribute into TestDefaultAttribute
}!
test_default_attributes := sql db {
select from TestDefaultAttribute limit 1
}!
result_test_default_attribute := test_default_attributes.first()
assert result_test_default_attribute.name == 'Hitalo'
assert test_default_attribute.created_at or { '' } == ''
assert test_default_attribute.created_at1 or { '' } == ''
assert test_default_attribute.created_at2 or { '' } == ''
assert result_test_default_attribute.created_at or { '' }.len == 8 // HH:MM:SS
assert result_test_default_attribute.created_at1 or { '' }.len == 10 // YYYY-MM-DD
assert result_test_default_attribute.created_at2 or { '' }.len == 19 // YYYY-MM-DD HH:MM:SS
sql db {
drop table TestDefaultAttribute
}!
}
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
}
fn test_sqlite_orm_supports_time_duration_alias_fields() {
mut db := sqlite.connect(':memory:') or { panic(err) }
defer {
db.close() or { panic(err) }
}
sql db {
create table TestDurationAlias
}!
result := db.exec('pragma table_info(TestDurationAlias);')!
assert result.len == 2
assert result[1].vals[1] == 'duration'
assert result[1].vals[2] == 'INTEGER'
record := TestDurationAlias{
duration: 3 * time.second
}
sql db {
insert record into TestDurationAlias
}!
rows := sql db {
select from TestDurationAlias limit 1
}!
assert rows.len == 1
assert rows[0].duration == 3 * time.second
}
fn test_sqlite_orm_tutorial_style_select_method() {
mut app := TutorialBlogApp{
db: sqlite.connect(':memory:') or { panic(err) }
}
defer {
app.db.close() or { panic(err) }
}
sql app.db {
create table TutorialBlogArticle
}!
first_article := TutorialBlogArticle{
title: 'Hello, world!'
text: 'V is great.'
}
second_article := TutorialBlogArticle{
title: 'Second post.'
text: 'Hm... what should I write about?'
}
sql app.db {
insert first_article into TutorialBlogArticle
insert second_article into TutorialBlogArticle
}!
articles := app.find_all_tutorial_blog_articles()
assert articles.len == 2
mut titles := articles.map(it.title)
titles.sort()
assert titles == ['Hello, world!', 'Second post.']
}
+204
View File
@@ -0,0 +1,204 @@
// vtest build: present_sqlite3?
import db.sqlite
import orm
import os
type Connection = sqlite.DB
struct User {
pub:
id int @[primary; sql: serial]
name string
last_name ?string
}
type Content = []u8 | string
struct Host {
pub mut:
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() {
mut db := sqlite.connect(':memory:') or { panic(err) }
assert db.is_open
assert db.validate()!
db.exec('drop table if exists users')!
db.exec("create table users (id integer primary key, name text default '', last_name text null default null);")!
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
db.exec_param('insert into users (name) values (?)', 'Tom')!
assert db.last_insert_rowid() == 4
nr_users := db.q_int('select count(*) from users')!
assert nr_users == 4
name := db.q_string('select name from users where id = 1')!
assert name == 'Sam'
username := db.exec_param('select name from users where id = ?', '1')!
assert username[0].vals[0] == 'Sam'
// this insert will be rejected due to duplicated id
db.exec("insert into users (id,name) values (1,'Silly')")!
assert db.get_affected_rows_count() == 0
mut users := db.exec('select * from users')!
// dump(users)
assert users.len == 4
code := db.exec_none('vacuum')
assert code == 101
user := db.exec_one('select * from users where id = 3') or { panic(err) }
// dump(user)
assert user.vals.len == 3
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_param_many('update users set name=? where name=?', ['Peter', 'Peter1'])!
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
// transaction test
db.begin()!
db.exec("insert into users (name) values ('John')")!
assert db.last_insert_rowid() == 5
db.savepoint('new_savepoint')!
db.exec("insert into users (name) values ('Kitty')")!
assert db.last_insert_rowid() == 6
db.rollback_to('new_savepoint')!
db.exec("insert into users (name) values ('Mars')")!
assert db.last_insert_rowid() == 6
db.commit()!
users = db.exec('select * from users')!
// dump(users)
assert users.len == 5
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() {
mut host := create_host(sqlite.connect(':memory:')!)!
host.db.close()!
assert true
}
fn test_exec_param_many() {
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 '' unique);")!
db.exec("insert into users (name) values ('Sam')")!
db.exec_param_many('insert into users (id, name) values (?, ?)', [
'60',
'Sam',
]) or {
assert err.code() == 19 // constraint failure
db.close()!
return
}
db.close()!
assert false
}
fn test_exec_param_many2() {
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 '' unique);")!
db.exec_param_many('insert into users (id, name) values (?, ?)', [
['60', 'Sam'],
['61', 'Foo'],
['62', 'Bar'],
])!
count := db.q_int('select count(*) from users')!
assert count == 3
db.close()!
}
fn test_tables() {
mut db := sqlite.connect(':memory:') or { panic(err) }
db.exec('create table alpha (id integer)')!
db.exec('create table beta (id integer)')!
tbl := db.tables()!
assert tbl == ['alpha', 'beta']
db.close()!
}
fn test_columns() {
mut db := sqlite.connect(':memory:') or { panic(err) }
db.exec('create table items (id integer primary key, name text, price real)')!
cols := db.columns('items')!
assert cols == ['id', 'name', 'price']
db.close()!
}
fn test_schema() {
mut db := sqlite.connect(':memory:') or { panic(err) }
db.exec('create table things (id integer primary key, label text)')!
s := db.schema('things')!
assert s.contains('CREATE TABLE things')
// empty table name returns all objects
all := db.schema('')!
assert all.contains('CREATE TABLE things')
db.close()!
}
fn test_db_size() {
tmp := os.join_path(os.temp_dir(), 'test_db_size.db')
defer {
os.rm(tmp) or {}
}
mut db := sqlite.connect(tmp) or { panic(err) }
db.exec('create table t (id integer)')!
sz := db.db_size()!
assert sz > 0
db.close()!
}
fn test_orm_transaction_interface() {
mut db := sqlite.connect(':memory:') or { panic(err) }
defer {
db.close() or {}
}
mut conn := orm.TransactionalConnection(db)
mut tx := orm.begin(mut conn)!
tx.transaction[int](fn (mut tx orm.Tx) !int {
return 1
})!
tx.commit()!
}
@@ -0,0 +1,319 @@
// vtest build: present_sqlite3?
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
unsafe {
mut vfsfile := to_vfsopenedfile(file)
vfsfile.vfs_state.log << 'read file=${vfsfile.name}'
}
unsafe {
vmemset(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')
unsafe {
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
}
////////////////////////////////////////////////
struct Human {
name string
age f32
}
fn check_connect_full_default_vfs(vfs_name string) ! {
mut db := sqlite.connect_full(':memory:', [.readwrite, .create, .fullmutex], '')!
sql db {
create table Human
}!
h := Human{'Bilbo', 56}
sql db {
insert h into Human
}!
res := sql db {
select from Human
}!
db.close()!
assert res.len == 1
assert res[0] == h
}
fn test_connect_full_default_vfs() {
// passing '' here as vfs_name should work everywhere, and it should be equivalent
// to 'unix' or 'win32', depending on the current system:
check_connect_full_default_vfs('')!
$if windows {
check_connect_full_default_vfs('win32')!
} $else {
check_connect_full_default_vfs('unix')!
}
}
+99
View File
@@ -0,0 +1,99 @@
module sqlite
fn C.sqlite3_bind_null(&C.sqlite3_stmt, i32) i32
fn C.sqlite3_bind_double(&C.sqlite3_stmt, i32, f64) i32
fn C.sqlite3_bind_int(&C.sqlite3_stmt, i32, i32) i32
fn C.sqlite3_bind_int64(&C.sqlite3_stmt, i32, i64) i32
fn C.sqlite3_bind_text(&C.sqlite3_stmt, i32, &char, i32, voidptr) i32
// Only for V ORM
fn (db &DB) init_stmt(query string) (&C.sqlite3_stmt, int) {
// println('init_stmt("${query}")')
stmt := &C.sqlite3_stmt(unsafe { nil })
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_null(idx int) int {
return C.sqlite3_bind_null(stmt.stmt, idx)
}
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 {
if C.sqlite3_column_type(stmt.stmt, idx) == C.SQLITE_NULL {
return none
} else {
return C.sqlite3_column_int(stmt.stmt, idx)
}
}
fn (stmt &Stmt) get_i64(idx int) ?i64 {
if C.sqlite3_column_type(stmt.stmt, idx) == C.SQLITE_NULL {
return none
} else {
return C.sqlite3_column_int64(stmt.stmt, idx)
}
}
fn (stmt &Stmt) get_f64(idx int) ?f64 {
if C.sqlite3_column_type(stmt.stmt, idx) == C.SQLITE_NULL {
return none
} else {
return C.sqlite3_column_double(stmt.stmt, idx)
}
}
fn (stmt &Stmt) get_text(idx int) ?string {
if C.sqlite3_column_type(stmt.stmt, idx) == C.SQLITE_NULL {
return none
} else {
b := &char(C.sqlite3_column_text(stmt.stmt, idx))
if b == &char(unsafe { nil }) {
return ''
}
l := C.sqlite3_column_bytes(stmt.stmt, idx)
return unsafe { b.vstring_with_len(l) }
}
}
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)
}
+234
View File
@@ -0,0 +1,234 @@
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
pub type Sqlite3_vfs = C.sqlite3_vfs
// https://www.sqlite.org/c3ref/vfs.html
type Fn_sqlite3_syscall_ptr = fn ()
// Keep these callback signatures named so v2 does not need to parse qualified
// type names inside inline `fn (...)` struct fields.
type Sqlite3_file_op_fn = fn (&Sqlite3_file) int
type Sqlite3_file_rw_fn = fn (&Sqlite3_file, voidptr, int, i64) int
type Sqlite3_file_truncate_fn = fn (&Sqlite3_file, i64) int
type Sqlite3_file_flag_fn = fn (&Sqlite3_file, int) int
type Sqlite3_file_control_fn = fn (&Sqlite3_file, int, voidptr) int
type Sqlite3_file_shm_map_fn = fn (&Sqlite3_file, int, int, int, &voidptr) int
type Sqlite3_file_shm_lock_fn = fn (&Sqlite3_file, int, int, int) int
type Sqlite3_file_shm_barrier_fn = fn (&Sqlite3_file)
type Sqlite3_file_fetch_fn = fn (&Sqlite3_file, i64, int, &voidptr) int
type Sqlite3_file_unfetch_fn = fn (&Sqlite3_file, i64, voidptr) int
type Sqlite3_vfs_open_fn = fn (&Sqlite3_vfs, &char, &Sqlite3_file, int, &int) int
type Sqlite3_vfs_delete_fn = fn (&Sqlite3_vfs, &char, int) int
type Sqlite3_vfs_access_fn = fn (&Sqlite3_vfs, &char, int, &int) int
type Sqlite3_vfs_fullpathname_fn = fn (&Sqlite3_vfs, &char, int, &char) int
type Sqlite3_vfs_dlopen_fn = fn (&Sqlite3_vfs, &char) voidptr
type Sqlite3_vfs_dlerror_fn = fn (&Sqlite3_vfs, int, &char)
type Sqlite3_vfs_dlsym_fn = fn (&Sqlite3_vfs, voidptr, &char) voidptr
type Sqlite3_vfs_dlclose_fn = fn (&Sqlite3_vfs, voidptr)
type Sqlite3_vfs_randomness_fn = fn (&Sqlite3_vfs, int, &char) int
type Sqlite3_vfs_sleep_fn = fn (&Sqlite3_vfs, int) int
type Sqlite3_vfs_current_time_fn = fn (&Sqlite3_vfs, &f64) int
type Sqlite3_vfs_get_last_error_fn = fn (&Sqlite3_vfs, int, &char) int
type Sqlite3_vfs_current_time_i64_fn = fn (&Sqlite3_vfs, &i64) int
type Sqlite3_vfs_set_system_call_fn = fn (&Sqlite3_vfs, &char, Fn_sqlite3_syscall_ptr) int
type Sqlite3_vfs_get_system_call_fn = fn (&Sqlite3_vfs, &char) Fn_sqlite3_syscall_ptr
type Sqlite3_vfs_next_system_call_fn = fn (&Sqlite3_vfs, &char) &char
// https://www.sqlite.org/c3ref/file.html
pub 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]
pub struct C.sqlite3_io_methods {
mut:
// version 1 and later fields
iVersion int
xClose Sqlite3_file_op_fn
xRead Sqlite3_file_rw_fn
xWrite Sqlite3_file_rw_fn
xTruncate Sqlite3_file_truncate_fn
xSync Sqlite3_file_flag_fn
xFileSize Sig1
xLock Sqlite3_file_flag_fn
xUnlock Sqlite3_file_flag_fn
xCheckReservedLock Sig2
xFileControl Sqlite3_file_control_fn
xSectorSize Sqlite3_file_op_fn
xDeviceCharacteristics Sqlite3_file_op_fn
// version 2 and later fields
xShmMap Sqlite3_file_shm_map_fn
xShmLock Sqlite3_file_shm_lock_fn
xShmBarrier Sqlite3_file_shm_barrier_fn
xShmUnmap Sqlite3_file_flag_fn
// version 3 and later fields
xFetch Sqlite3_file_fetch_fn
xUnfetch Sqlite3_file_unfetch_fn
}
pub type Sqlite3_io_methods = C.sqlite3_io_methods
@[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 Sqlite3_vfs_open_fn
xDelete Sqlite3_vfs_delete_fn
xAccess Sqlite3_vfs_access_fn
xFullPathname Sqlite3_vfs_fullpathname_fn
xDlOpen Sqlite3_vfs_dlopen_fn
xDlError Sqlite3_vfs_dlerror_fn
xDlSym Sqlite3_vfs_dlsym_fn // to fn accepting void and returning
xDlClose Sqlite3_vfs_dlclose_fn
xRandomness Sqlite3_vfs_randomness_fn
xSleep Sqlite3_vfs_sleep_fn
xCurrentTime Sqlite3_vfs_current_time_fn
xGetLastError Sqlite3_vfs_get_last_error_fn
// version two and later only fields
xCurrentTimeInt64 Sqlite3_vfs_current_time_i64_fn
// version three and later only fields
xSetSystemCall Sqlite3_vfs_set_system_call_fn
xGetSystemCall Sqlite3_vfs_get_system_call_fn
xNextSystemCall Sqlite3_vfs_next_system_call_fn
}
// 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, i32) i32
fn C.sqlite3_vfs_unregister(&C.sqlite3_vfs) i32
// 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)
if res != unsafe { nil } {
return res
}
return error('sqlite3_vfs_find returned 0')
}
// get_default_vfs Asks sqlite for default VFS instance
pub fn get_default_vfs() !&Sqlite3_vfs {
res := C.sqlite3_vfs_find(unsafe { nil })
if res != unsafe { nil } {
return res
}
return error('sqlite3_vfs_find(0) returned 0')
}
// 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)
if sqlite_ok != res {
return 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)
if sqlite_ok != res {
return error('sqlite3_vfs_unregister returned ${res}')
}
}
// https://www.sqlite.org/c3ref/open.html
fn C.sqlite3_open_v2(&char, &&C.sqlite3, i32, &char) i32
// 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.
// Pass '' for vfs_name, if you want to use the default VFS for the current platform,
// (which is equivalent to 'win32' on Windows, and 'unix' on !Windows systems).
pub fn connect_full(path string, mode_flags []OpenModeFlag, vfs_name string) !DB {
db := &C.sqlite3(unsafe { nil })
path_cstr := sqlite_cstring(path)
defer {
unsafe { free(path_cstr) }
}
mut flags := 0
for flag in mode_flags {
flags = flags | int(flag)
}
mut pstr := unsafe { &char(0) }
if vfs_name != '' {
pstr = sqlite_cstring(vfs_name)
}
defer {
if vfs_name != '' {
unsafe { free(pstr) }
}
}
code := C.sqlite3_open_v2(path_cstr, &db, flags, pstr)
if code != 0 {
return &SQLError{
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
code: code
}
}
return DB{
conn: db
is_open: true
}
}