Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -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')
|
||||
}
|
||||
@@ -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()!
|
||||
}
|
||||
@@ -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()!
|
||||
}
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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()!
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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.']
|
||||
}
|
||||
@@ -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')!
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user