mirror of
https://github.com/vlang/v.git
synced 2025-09-15 07:22:27 +03:00

* orm: added is none and !is none handling * orm: added NullType, support option fields and deprecate [nonull] Nullable DB fields are now determined by corresponding option struct field. The [nonull] attribute is deprecated and fields are all NOT NULL now, unless they are option fields. New orm primitive, NullType, added to support passing none values to db backends, which have been updated to support it. Also, empty string and 0 numberic values are no longer skipped during insert (since they may be valid values). * orm: fix [nonull] deprecation warning * orm: add null handling to update and select also, improved formatting for orm cgen, and removed optimised operand handling of orm `is` and `!is` operators * sqlite: read/report NULLs using new orm NullType * postgres: returning data primitives now returns new orm.NullType * orm: initialise NullType Primitives properly * orm: do not smart cast operands inside sql * orm: fix bad setting of option value * orm: improve orm_null_test.v, adding/fixing selects * orm: cleanup: rename NullType->Null, use serial const, cgen output * orm: handle automatically generated fields more explicitly During insert, fields which are * [sql: serial] * [default: whatever] and where the data is a default value (e.g., 0, ""), those fields are not sent to the db, so that the db can generate auto-increment or default values. (This was previously done only for [primary] fields, and not in all circumstances, but that is not correct -- primary and serial/auto-increment fields are differnet.) * orm: udpated README * orm: select cgen fixes: read from uninit res; fail to init res * orm: udpated tests * orm: fix option sub-struct fields * orm: fixed joins to option structs Changed orm.write_orm_select() so that you pass to it the name of a resut variable which it populates with the result (or not) and changed use of it in sql_select_expr() and calls in write_orm_select() to populate substructs. * orm: fix pg driver handling of NULL results * orm: move runtime checks to comptime checker; cache checked tables * orm: vfmt :( * orm: markdown formatting * orm: renamed orm.time_ and orm.enum_; updated db drivers * checker: updated orm tests * orm: fix issue setting up ast option values as orm primitives * checker: ORM use of none/options and operations (added tests) * orm: fixed tests * db: clean code * examples: remove orm nonull attributes * orm: skip test memory santisation for orm_null_test.v * orm: make the type-to-primitive converstion fns not public * orm: mv object var c-code from checker->cgen; fix memory corruption Code in checker/orm.v used the SqlStmtLine object field name to store c-specific referenecs to option and array fields (for arrays of children). I moved this logic to cgen. And fixed an issue introduced with option fields, where an array of children was unpacked into a non-array result which could corrupt memory. * orm: fixed vast error * orm: skip 2 tests on ubuntu-musl which require sqlite3.h * cgen: prevent casting a struct (string) * v fmt orm_fkey_attribute.vv, orm_multidim_array.vv, orm_table_attributes.vv; run `VAUTOFIX=1 ./v vlib/v/compiler_errors_test.v`
395 lines
10 KiB
V
395 lines
10 KiB
V
module sqlite
|
|
|
|
$if freebsd || openbsd {
|
|
#flag -I/usr/local/include
|
|
#flag -L/usr/local/lib
|
|
}
|
|
$if windows {
|
|
#flag windows -I@VEXEROOT/thirdparty/sqlite
|
|
#flag windows -L@VEXEROOT/thirdparty/sqlite
|
|
#flag windows @VEXEROOT/thirdparty/sqlite/sqlite3.o
|
|
} $else {
|
|
#flag -lsqlite3
|
|
}
|
|
|
|
#include "sqlite3.h"
|
|
|
|
// https://www.sqlite.org/rescode.html
|
|
pub const (
|
|
sqlite_ok = 0
|
|
sqlite_error = 1
|
|
sqlite_row = 100
|
|
sqlite_done = 101
|
|
sqlite_cantopen = 14
|
|
sqlite_ioerr_read = 266
|
|
sqlite_ioerr_short_read = 522
|
|
sqlite_ioerr_write = 778
|
|
sqlite_ioerr_fsync = 1034
|
|
sqlite_ioerr_fstat = 1802
|
|
sqlite_ioerr_delete = 2570
|
|
|
|
sqlite_open_main_db = 0x00000100
|
|
sqlite_open_temp_db = 0x00000200
|
|
sqlite_open_transient_db = 0x00000400
|
|
sqlite_open_main_journal = 0x00000800
|
|
sqlite_open_temp_journal = 0x00001000
|
|
sqlite_open_subjournal = 0x00002000
|
|
sqlite_open_super_journal = 0x00004000
|
|
sqlite_open_wal = 0x00080000
|
|
)
|
|
|
|
pub enum SyncMode {
|
|
off
|
|
normal
|
|
full
|
|
}
|
|
|
|
pub enum JournalMode {
|
|
off
|
|
delete
|
|
truncate
|
|
persist
|
|
memory
|
|
}
|
|
|
|
struct C.sqlite3 {
|
|
}
|
|
|
|
struct C.sqlite3_stmt {
|
|
}
|
|
|
|
[heap]
|
|
pub struct Stmt {
|
|
stmt &C.sqlite3_stmt = unsafe { nil }
|
|
db &DB = unsafe { nil }
|
|
}
|
|
|
|
struct SQLError {
|
|
MessageError
|
|
}
|
|
|
|
//
|
|
[heap]
|
|
pub struct DB {
|
|
pub mut:
|
|
is_open bool
|
|
mut:
|
|
conn &C.sqlite3 = unsafe { nil }
|
|
}
|
|
|
|
// str returns a text representation of the DB
|
|
pub fn (db &DB) str() string {
|
|
return 'sqlite.DB{ conn: ' + ptr_str(db.conn) + ' }'
|
|
}
|
|
|
|
pub struct Row {
|
|
pub mut:
|
|
vals []string
|
|
}
|
|
|
|
//
|
|
fn C.sqlite3_open(&char, &&C.sqlite3) int
|
|
|
|
fn C.sqlite3_close(&C.sqlite3) int
|
|
|
|
fn C.sqlite3_busy_timeout(db &C.sqlite3, ms int) int
|
|
|
|
fn C.sqlite3_last_insert_rowid(&C.sqlite3) i64
|
|
|
|
//
|
|
fn C.sqlite3_prepare_v2(&C.sqlite3, &char, int, &&C.sqlite3_stmt, &&char) int
|
|
|
|
fn C.sqlite3_step(&C.sqlite3_stmt) int
|
|
|
|
fn C.sqlite3_finalize(&C.sqlite3_stmt) int
|
|
|
|
//
|
|
fn C.sqlite3_column_name(&C.sqlite3_stmt, int) &char
|
|
|
|
fn C.sqlite3_column_text(&C.sqlite3_stmt, int) &u8
|
|
|
|
fn C.sqlite3_column_int(&C.sqlite3_stmt, int) int
|
|
|
|
fn C.sqlite3_column_int64(&C.sqlite3_stmt, int) i64
|
|
|
|
fn C.sqlite3_column_double(&C.sqlite3_stmt, int) f64
|
|
|
|
fn C.sqlite3_column_count(&C.sqlite3_stmt) int
|
|
|
|
fn C.sqlite3_column_type(&C.sqlite3_stmt, int) int
|
|
|
|
//
|
|
fn C.sqlite3_errstr(int) &char
|
|
|
|
fn C.sqlite3_errmsg(&C.sqlite3) &char
|
|
|
|
fn C.sqlite3_free(voidptr)
|
|
|
|
fn C.sqlite3_changes(&C.sqlite3) int
|
|
|
|
// connect Opens the connection with a database.
|
|
pub fn connect(path string) !DB {
|
|
db := &C.sqlite3(unsafe { nil })
|
|
code := C.sqlite3_open(&char(path.str), &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() !bool {
|
|
code := C.sqlite3_close(db.conn)
|
|
if code == 0 {
|
|
db.is_open = false
|
|
} else {
|
|
return &SQLError{
|
|
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db.conn))) }
|
|
code: code
|
|
}
|
|
}
|
|
return true // successfully closed
|
|
}
|
|
|
|
// Only for V ORM
|
|
fn get_int_from_stmt(stmt &C.sqlite3_stmt) int {
|
|
x := C.sqlite3_step(stmt)
|
|
if x != C.SQLITE_OK && x != C.SQLITE_DONE {
|
|
C.puts(C.sqlite3_errstr(x))
|
|
}
|
|
|
|
res := C.sqlite3_column_int(stmt, 0)
|
|
C.sqlite3_finalize(stmt)
|
|
return res
|
|
}
|
|
|
|
// last_insert_rowid returns last inserted rowid
|
|
// https://www.sqlite.org/c3ref/last_insert_rowid.html
|
|
pub fn (db &DB) last_insert_rowid() i64 {
|
|
return C.sqlite3_last_insert_rowid(db.conn)
|
|
}
|
|
|
|
// get_affected_rows_count returns `sqlite changes()` meaning amount of rows affected by most recent sql query
|
|
pub fn (db &DB) get_affected_rows_count() int {
|
|
return C.sqlite3_changes(db.conn)
|
|
}
|
|
|
|
// q_int returns a single integer value, from the first column of the result of executing `query`, or an error on failure
|
|
pub fn (db &DB) q_int(query string) !int {
|
|
stmt := &C.sqlite3_stmt(unsafe { nil })
|
|
defer {
|
|
C.sqlite3_finalize(stmt)
|
|
}
|
|
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
|
|
code := C.sqlite3_step(stmt)
|
|
if code != sqlite.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 {
|
|
stmt := &C.sqlite3_stmt(unsafe { nil })
|
|
defer {
|
|
C.sqlite3_finalize(stmt)
|
|
}
|
|
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
|
|
code := C.sqlite3_step(stmt)
|
|
if code != sqlite.sqlite_row {
|
|
return db.error_message(code, query)
|
|
}
|
|
|
|
val := unsafe { &u8(C.sqlite3_column_text(stmt, 0)) }
|
|
return if val != &u8(0) { unsafe { tos_clone(val) } } else { '' }
|
|
}
|
|
|
|
// exec executes the query on the given `db`, and returns an array of all the results, or an error on failure
|
|
[manualfree]
|
|
pub fn (db &DB) exec(query string) ![]Row {
|
|
stmt := &C.sqlite3_stmt(unsafe { nil })
|
|
defer {
|
|
C.sqlite3_finalize(stmt)
|
|
}
|
|
mut code := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
|
|
if code != sqlite.sqlite_ok {
|
|
return db.error_message(code, query)
|
|
}
|
|
|
|
nr_cols := C.sqlite3_column_count(stmt)
|
|
mut res := 0
|
|
mut rows := []Row{}
|
|
for {
|
|
res = C.sqlite3_step(stmt)
|
|
// Result Code SQLITE_ROW; Another row is available
|
|
if res != 100 {
|
|
// C.puts(C.sqlite3_errstr(res))
|
|
break
|
|
}
|
|
mut row := Row{}
|
|
for i in 0 .. nr_cols {
|
|
val := unsafe { &u8(C.sqlite3_column_text(stmt, i)) }
|
|
if val == &u8(0) {
|
|
row.vals << ''
|
|
} else {
|
|
row.vals << unsafe { tos_clone(val) }
|
|
}
|
|
}
|
|
rows << row
|
|
}
|
|
return rows
|
|
}
|
|
|
|
// 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.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 {
|
|
stmt := &C.sqlite3_stmt(unsafe { nil })
|
|
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
|
|
code := C.sqlite3_step(stmt)
|
|
C.sqlite3_finalize(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 []string) ![]Row {
|
|
mut stmt := &C.sqlite3_stmt(unsafe { nil })
|
|
defer {
|
|
C.sqlite3_finalize(stmt)
|
|
}
|
|
|
|
mut code := C.sqlite3_prepare_v2(db.conn, &char(query.str), -1, &stmt, 0)
|
|
if code != 0 {
|
|
return db.error_message(code, query)
|
|
}
|
|
|
|
for i, param in params {
|
|
code = C.sqlite3_bind_text(stmt, i + 1, voidptr(param.str), param.len, 0)
|
|
if code != 0 {
|
|
return db.error_message(code, query)
|
|
}
|
|
}
|
|
|
|
nr_cols := C.sqlite3_column_count(stmt)
|
|
mut res := 0
|
|
mut rows := []Row{}
|
|
for {
|
|
res = C.sqlite3_step(stmt)
|
|
if res != sqlite.sqlite_row {
|
|
break
|
|
}
|
|
mut row := Row{}
|
|
for i in 0 .. nr_cols {
|
|
val := unsafe { &u8(C.sqlite3_column_text(stmt, i)) }
|
|
if val == &u8(0) {
|
|
row.vals << ''
|
|
} else {
|
|
row.vals << unsafe { tos_clone(val) }
|
|
}
|
|
}
|
|
rows << row
|
|
}
|
|
|
|
return rows
|
|
}
|
|
|
|
// 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])
|
|
}
|
|
|
|
// create_table issues a "create table if not exists" command to the db.
|
|
// It creates the table named 'table_name', with columns generated from 'columns' array.
|
|
// The default columns type will be TEXT.
|
|
pub fn (db &DB) create_table(table_name string, columns []string) ! {
|
|
db.exec('create table if not exists ${table_name} (' + columns.join(',\n') + ')')!
|
|
}
|
|
|
|
// busy_timeout sets a busy timeout in milliseconds.
|
|
// Sleeps for a specified amount of time when a table is locked. The handler
|
|
// will sleep multiple times until at least "ms" milliseconds of sleeping have accumulated.
|
|
// (see https://www.sqlite.org/c3ref/busy_timeout.html)
|
|
pub fn (db &DB) busy_timeout(ms int) int {
|
|
return C.sqlite3_busy_timeout(db.conn, ms)
|
|
}
|
|
|
|
// synchronization_mode sets disk synchronization mode, which controls how
|
|
// aggressively SQLite will write data to physical storage.
|
|
// 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;')!
|
|
}
|
|
}
|