├── LICENSE ├── README.md ├── sql_helpers.odin └── sqlite3.odin /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Michael Kutowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # odin-sqlite 2 | Simple Sqlite3 bindings for [Odin](https://odin-lang.org/) 3 | 4 | Requires built static lib from the [sqlite3 Source Code](https://sqlite.org/download.html) 5 | 6 | # New helpers 7 | I realized my old helper code was not on par with what other languages deliver, i.e. go / rust 8 | So i made some adjustments: 9 | 1. simple cache system `map[string]^sql.Stmt` that holds prepared statements for reusing 10 | 2. use bind system from `sqlite` itself instead of using `core:fmt` 11 | 3. `SELECT` helper to allow structs to be filled automatically ~ when column names match the struct field name 12 | 4. use `or_return` on `sql.ResultCode` since a lot can fail 13 | 5. bind helper to insert `args: ..any` into increasing `sql.bind_*(stmt, arg_index, arg)` 14 | 15 | ```go 16 | package main 17 | 18 | import "core:fmt" 19 | import "src" // <--- my helpers live in my src, up to you 20 | 21 | run :: proc() -> (err: src.Result_Code) { 22 | using src 23 | db_execute_simple(`DROP TABLE IF EXISTS people`) or_return 24 | 25 | db_execute_simple(`CREATE TABLE IF NOT EXISTS people( 26 | id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 27 | number INTEGER DEFAULT 0, 28 | big_number DECIMAL DEFAULT 0, 29 | name VARCHAR(30) 30 | )`) or_return 31 | 32 | db_insert("people (number, name, big_number)", 4, "test", 1.24) or_return 33 | 34 | // above turns into 35 | // stmt := db_cache_prepare(`INSERT INTO people (number, name, big_number) 36 | // VALUES (?1, ?2, ?3) 37 | // `) or_return 38 | // db_bind(stmt, 4, "test", 1.23) or_return 39 | // db_bind_run(stmt) or_return 40 | 41 | db_execute("UPDATE people SET number = ?1", 3) or_return 42 | 43 | People :: struct { 44 | id: i32, 45 | number: i32, 46 | big_number: f64, 47 | name: string, 48 | } 49 | 50 | p1: People 51 | db_select("FROM people WHERE id = ?1", p1, 1) or_return 52 | fmt.println(p1) 53 | 54 | return 55 | } 56 | 57 | main :: proc() { 58 | using src 59 | 60 | db_check(db_init("testing.db")) 61 | defer db_check(db_destroy()) 62 | 63 | db_cache_cap(64) 64 | defer db_cache_destroy() 65 | 66 | err := run() 67 | fmt.println(err) 68 | } 69 | ``` 70 | 71 | prints 72 | 73 | ```go 74 | People{id = 1, number = 3, big_number = 1.240, name = test} 75 | OK 76 | ``` 77 | 78 | `db_select` & `db_inser` are obviously very opinionated, main call is `db_execute :: proc(cmd: string, args: ..any)` which does cmd preparing and bind argument insertion for you 79 | 80 | still need to test multiple row result usage out 81 | -------------------------------------------------------------------------------- /sql_helpers.odin: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "core:fmt" 4 | import "core:strings" 5 | import "base:runtime" 6 | import "core:reflect" 7 | import "core:mem" 8 | import sql "odin-sqlite" 9 | 10 | Result_Code :: sql.ResultCode 11 | Stmt :: sql.Stmt 12 | db: ^sql.sqlite3 13 | db_cache: map[string]^Stmt 14 | 15 | db_init :: proc(name: cstring) -> (err: Result_Code) { 16 | sql.open(name, &db) or_return 17 | return 18 | } 19 | 20 | db_destroy :: proc() -> (err: Result_Code) { 21 | sql.close(db) or_return 22 | return 23 | } 24 | 25 | db_check :: proc(err: Result_Code, loc := #caller_location) { 26 | if err == .ERROR || err == .CONSTRAINT || err == .MISUSE { 27 | text := fmt.tprintf("%s %v %s", err, sql.errmsg(db), loc) 28 | panic(text) 29 | } 30 | } 31 | 32 | // does not do caching 33 | db_execute_simple :: proc(cmd: string) -> (err: Result_Code) { 34 | data := transmute([^]u8)strings.unsafe_string_to_cstring(cmd) 35 | stmt: ^Stmt 36 | sql.prepare_v2(db, data, i32(len(cmd)), &stmt, nil) or_return 37 | db_run(stmt) or_return 38 | sql.finalize(stmt) or_return 39 | return 40 | } 41 | 42 | // execute cached with args 43 | db_execute :: proc(cmd: string, args: ..any) -> (err: Result_Code) { 44 | stmt := db_cache_prepare(cmd) or_return 45 | db_bind(stmt, ..args) or_return 46 | db_bind_run(stmt) or_return 47 | return 48 | } 49 | 50 | // simple run through statement 51 | db_run :: proc(stmt: ^Stmt) -> (err: Result_Code) { 52 | for { 53 | result := sql.step(stmt) 54 | 55 | if result == .DONE { 56 | break 57 | } else if result != .ROW { 58 | return result 59 | } 60 | } 61 | 62 | return 63 | } 64 | 65 | // set a cap to the cache 66 | db_cache_cap :: proc(cap: int) { 67 | db_cache = make(map[string]^Stmt, cap) 68 | } 69 | 70 | // return cached stmt or create one 71 | db_cache_prepare :: proc(cmd: string) -> (stmt: ^Stmt, err: Result_Code) { 72 | if existing_stmt := db_cache[cmd]; existing_stmt != nil { 73 | stmt = existing_stmt 74 | } else { 75 | data := transmute([^]u8)strings.unsafe_string_to_cstring(cmd) 76 | sql.prepare_v2(db, data, i32(len(cmd)), &stmt, nil); 77 | db_cache[cmd] = stmt 78 | } 79 | 80 | return 81 | } 82 | 83 | // strings are not deleted 84 | db_cache_destroy :: proc() { 85 | for _, stmt in db_cache { 86 | sql.finalize(stmt) 87 | } 88 | clear(&db_cache) 89 | } 90 | 91 | // simple execute -> no cache 92 | // bindings -> maybe cache 93 | // step once -> 94 | // struct getters 95 | 96 | db_bind_run :: proc(stmt: ^Stmt) -> (err: Result_Code) { 97 | db_run(stmt) or_return 98 | sql.reset(stmt) or_return 99 | sql.clear_bindings(stmt) or_return 100 | return 101 | } 102 | 103 | // bind primitive arguments to input statement 104 | db_bind :: proc(stmt: ^Stmt, args: ..any) -> (err: Result_Code) { 105 | for arg, index in args { 106 | // index starts at 1 in binds 107 | index := index + 1 108 | ti := runtime.type_info_base(type_info_of(arg.id)) 109 | 110 | if arg == nil { 111 | sql.bind_null(stmt, i32(index)) or_return 112 | continue 113 | } 114 | 115 | // only allow slice of bytes 116 | if arg.id == []byte { 117 | slice := cast(^mem.Raw_Slice) arg.data 118 | sql.bind_blob( 119 | stmt, 120 | i32(index), 121 | cast(^u8) arg.data, 122 | i32(slice.len), 123 | sql.STATIC, 124 | ) or_return 125 | continue 126 | } 127 | 128 | #partial switch info in ti.variant { 129 | case runtime.Type_Info_Integer: { 130 | // TODO actually use int64 for i64 131 | value, valid := reflect.as_i64(arg) 132 | 133 | if valid { 134 | sql.bind_int(stmt, i32(index), i32(value)) or_return 135 | } else { 136 | return .ERROR 137 | } 138 | } 139 | 140 | case runtime.Type_Info_Float: { 141 | value, valid := reflect.as_f64(arg) 142 | if valid { 143 | sql.bind_double(stmt, i32(index), f64(value)) or_return 144 | } else { 145 | return .ERROR 146 | } 147 | } 148 | 149 | case runtime.Type_Info_String: { 150 | text, valid := reflect.as_string(arg) 151 | 152 | if valid { 153 | data := transmute([^]u8)strings.unsafe_string_to_cstring(text) 154 | sql.bind_text(stmt, i32(index), data, i32(len(text)), sql.STATIC) or_return 155 | } else { 156 | return .ERROR 157 | } 158 | } 159 | } 160 | 161 | // fmt.println(stmt, arg, index) 162 | } 163 | 164 | return 165 | } 166 | 167 | // data from the struct has to match wanted column names 168 | // changes the cmd string to the arg which should be a struct 169 | db_select :: proc(cmd_end: string, struct_arg: any, args: ..any) -> (err: Result_Code) { 170 | b := strings.builder_make_len_cap(0, 128) 171 | defer strings.builder_destroy(&b) 172 | 173 | strings.write_string(&b, "SELECT ") 174 | 175 | ti := runtime.type_info_base(type_info_of(struct_arg.id)) 176 | struct_info := ti.variant.(runtime.Type_Info_Struct) 177 | for name, i in struct_info.names[:struct_info.field_count] { 178 | strings.write_string(&b, name) 179 | 180 | if i != int(struct_info.field_count) - 1 { 181 | strings.write_byte(&b, ',') 182 | } else { 183 | strings.write_byte(&b, ' ') 184 | } 185 | } 186 | 187 | strings.write_string(&b, cmd_end) 188 | 189 | full_cmd := strings.to_string(b) 190 | // fmt.println(full_cmd) 191 | stmt := db_cache_prepare(full_cmd) or_return 192 | db_bind(stmt, ..args) or_return 193 | 194 | for { 195 | result := sql.step(stmt) 196 | 197 | if result == .DONE { 198 | break 199 | } else if result != .ROW { 200 | return result 201 | } 202 | 203 | // get column data per struct field 204 | for i in 0.. (err: Result_Code) { 216 | ti := runtime.type_info_base(type_info_of(arg.id)) 217 | #partial switch info in ti.variant { 218 | case runtime.Type_Info_Integer: { 219 | value := sql.column_int(stmt, column_index) 220 | // TODO proper i64 221 | 222 | switch arg.id { 223 | case i8: (cast(^i8) arg.data)^ = i8(value) 224 | case i16: (cast(^i16) arg.data)^ = i16(value) 225 | case i32: (cast(^i32) arg.data)^ = value 226 | case i64: (cast(^i64) arg.data)^ = i64(value) 227 | } 228 | } 229 | 230 | case runtime.Type_Info_Float: { 231 | value := sql.column_double(stmt, column_index) 232 | 233 | switch arg.id { 234 | case f32: (cast(^f32) arg.data)^ = f32(value) 235 | case f64: (cast(^f64) arg.data)^ = value 236 | } 237 | } 238 | 239 | case runtime.Type_Info_String: { 240 | value := sql.column_text(stmt, column_index) 241 | 242 | switch arg.id { 243 | case string: { 244 | (cast(^string) arg.data)^ = strings.clone( 245 | string(value), 246 | context.temp_allocator, 247 | ) 248 | } 249 | 250 | case cstring: { 251 | (cast(^cstring) arg.data)^ = strings.clone_to_cstring( 252 | string(value), 253 | context.temp_allocator, 254 | ) 255 | } 256 | } 257 | } 258 | } 259 | 260 | return 261 | } 262 | 263 | // auto insert INSERT INTO cmd_names VALUES (...) 264 | db_insert :: proc(cmd_names: string, args: ..any) -> (err: Result_Code) { 265 | b := strings.builder_make_len_cap(0, 128) 266 | defer strings.builder_destroy(&b) 267 | 268 | strings.write_string(&b, "INSERT INTO ") 269 | strings.write_string(&b, cmd_names) 270 | strings.write_string(&b, " VALUES ") 271 | 272 | strings.write_byte(&b, '(') 273 | for arg, i in args { 274 | fmt.sbprintf(&b, "?%d", i + 1) 275 | 276 | if i != len(args) - 1 { 277 | strings.write_byte(&b, ',') 278 | } 279 | } 280 | strings.write_byte(&b, ')') 281 | 282 | full_cmd := strings.to_string(b) 283 | // fmt.println(full_cmd) 284 | 285 | stmt := db_cache_prepare(full_cmd) or_return 286 | db_bind(stmt, ..args) or_return 287 | db_bind_run(stmt) or_return 288 | return 289 | } 290 | -------------------------------------------------------------------------------- /sqlite3.odin: -------------------------------------------------------------------------------- 1 | package sqlite3 2 | 3 | import "core:c" 4 | import "core:os" 5 | 6 | // when os.OS == "windows" do foreign import sqlite { "sqlite3.lib" } 7 | when ODIN_OS == .Linux do foreign import sqlite { "sqlite3.a", "system:pthread", "system:dl" } 8 | when ODIN_OS == .Darwin do foreign import sqlite { "sqlite3.a", "system:pthread", "system:dl" } 9 | 10 | callback :: proc"c"(data: rawptr, a: c.int, b: [^]cstring, c: [^]cstring) -> ResultCode 11 | 12 | @(default_calling_convention="c", link_prefix="sqlite3_") 13 | foreign sqlite { 14 | open :: proc(filename: cstring, ppDb: ^^sqlite3) -> ResultCode --- 15 | close :: proc(db: ^sqlite3) -> ResultCode --- 16 | 17 | prepare_v2 :: proc(db: ^sqlite3, zSql: ^c.char, nByte: c.int, ppStmt: ^^Stmt, pzTail: ^cstring) -> ResultCode --- 18 | 19 | step :: proc(stmt: ^Stmt) -> ResultCode --- 20 | finalize :: proc(stmt: ^Stmt) -> ResultCode --- 21 | 22 | last_insert_rowid :: proc(db: ^sqlite3) -> i64 --- 23 | 24 | column_name :: proc(stmt: ^Stmt, i_col: c.int) -> cstring --- 25 | column_blob :: proc(stmt: ^Stmt, i_col: c.int) -> ^byte --- 26 | column_text :: proc(stmt: ^Stmt, i_col: c.int) -> cstring --- 27 | column_bytes :: proc(stmt: ^Stmt, i_col: c.int) -> c.int --- 28 | 29 | column_int :: proc(stmt: ^Stmt, i_col: c.int) -> c.int --- 30 | column_double :: proc(stmt: ^Stmt, i_col: c.int) -> c.double --- 31 | column_type :: proc(stmt: ^Stmt, i_col: c.int) -> c.int --- 32 | 33 | errcode :: proc(db: ^sqlite3) -> c.int --- 34 | extended_errcode :: proc(db: ^sqlite3) -> c.int --- 35 | errmsg :: proc(db: ^sqlite3) -> cstring --- 36 | // exec :: proc(db: ^sqlite3, sql: cstring, call: callback, arg: rawptr, errmsg: [^]c.char) -> ResultCode ---; 37 | 38 | reset :: proc(stmt: ^Stmt) -> ResultCode --- 39 | clear_bindings :: proc(stmt: ^Stmt) -> ResultCode --- 40 | 41 | bind_int :: proc(stmt: ^Stmt, index: c.int, value: c.int) -> ResultCode --- 42 | bind_null :: proc(stmt: ^Stmt, index: c.int) -> ResultCode --- 43 | bind_int64 :: proc(stmt: ^Stmt, index: c.int, value: i64) -> ResultCode --- 44 | bind_double :: proc(stmt: ^Stmt, index: c.int, value: c.double) -> ResultCode --- 45 | 46 | bind_text :: proc( 47 | stmt: ^Stmt, 48 | index: c.int, 49 | first: ^c.char, 50 | byte_count: c.int, 51 | lifetime: uintptr, 52 | // lifetime: proc "c" (data: rawptr), 53 | ) -> ResultCode --- 54 | 55 | bind_blob :: proc( 56 | stmt: ^Stmt, 57 | index: c.int, 58 | first: ^byte, 59 | byte_count: c.int, 60 | lifetime: uintptr, 61 | ) -> ResultCode --- 62 | 63 | trace_v2 :: proc( 64 | db: ^sqlite3, 65 | mask: TraceFlags, 66 | call: proc "c" (mask: TraceFlag, x, y, z: rawptr) -> c.int, 67 | ctx: rawptr, 68 | ) -> ResultCode --- 69 | 70 | sql :: proc(stmt: ^Stmt) -> cstring --- 71 | expanded_sql :: proc(stmt: ^Stmt) -> cstring --- 72 | } 73 | 74 | STATIC :: uintptr(0) 75 | TRANSIENT :: ~uintptr(0) 76 | 77 | TraceFlag :: enum u8 { 78 | STMT = 0x01, 79 | PROFILE = 0x02, 80 | ROW = 0x04, 81 | CLOSE = 0x08, 82 | } 83 | TraceFlags :: bit_set[TraceFlag] 84 | 85 | // seems to be only a HANDLE 86 | Stmt :: struct {} 87 | 88 | LIMIT_LENGTH :: 0 89 | LIMIT_SQL_LENGTH :: 1 90 | LIMIT_COLUMN :: 2 91 | LIMIT_EXPR_DEPTH :: 3 92 | LIMIT_COMPOUND_SELECT :: 4 93 | LIMIT_VDBE_OP :: 5 94 | LIMIT_FUNCTION_ARG :: 6 95 | LIMIT_ATTACHED :: 7 96 | LIMIT_LIKE_PATTERN_LENGTH :: 8 97 | LIMIT_VARIABLE_NUMBER :: 9 98 | LIMIT_TRIGGER_DEPTH :: 10 99 | LIMIT_WORKER_THREADS :: 11 100 | N_LIMIT :: LIMIT_WORKER_THREADS + 1 101 | 102 | Vfs :: struct { 103 | 104 | } 105 | 106 | Vdbe :: struct { 107 | 108 | } 109 | 110 | CollSeq :: struct { 111 | 112 | } 113 | 114 | Mutex :: struct { 115 | 116 | } 117 | 118 | Db :: struct { 119 | 120 | } 121 | 122 | sqlite3 :: struct { 123 | pVfs: ^Vfs, /* OS Interface */ 124 | pVdbe: ^Vdbe, /* List of active virtual machines */ 125 | pDfltColl: ^CollSeq, /* BINARY collseq for the database encoding */ 126 | mutex: ^Mutex, /* Connection mutex */ 127 | aDb: ^Db, /* All backends */ 128 | nDb: c.int, /* Number of backends currently in use */ 129 | mDbFlags: u32, /* flags recording c.internal state */ 130 | flags: u64, /* flags settable by pragmas. See below */ 131 | lastRowid: i64, /* ROWID of most recent insert (see above) */ 132 | szMmap: i64, /* Default mmap_size setting */ 133 | nSchemaLock: u32, /* Do not reset the schema when non-zero */ 134 | openFlags: c.uint, /* Flags passed to sqlite3_vfs.xOpen() */ 135 | errCode: c.int, /* Most recent error code (SQLITE_*) */ 136 | errMask: c.int, /* & result codes with this before returning */ 137 | iSysErrno: c.int, /* Errno value from last system error */ 138 | dbOptFlags: u32, /* Flags to enable/disable optimizations */ 139 | enc: u8, /* Text encoding */ 140 | autoCommit: u8, /* The auto-commit flag. */ 141 | temp_store: u8, /* 1: file 2: memory 0: default */ 142 | mallocFailed: u8, /* True if we have seen a malloc failure */ 143 | bBenignMalloc: u8, /* Do not require OOMs if true */ 144 | dfltLockMode: u8, /* Default locking-mode for attached dbs */ 145 | nextAutovac: c.char, /* Autovac setting after VACUUM if >=0 */ 146 | suppressErr: u8, /* Do not issue error messages if true */ 147 | vtabOnConflict: u8, /* Value to return for s3_vtab_on_conflict() */ 148 | isTransactionSavepoint: u8, /* True if the outermost savepoc.int is a TS */ 149 | mTrace: u8, /* zero or more SQLITE_TRACE flags */ 150 | noSharedCache: u8, /* True if no shared-cache backends */ 151 | nSqlExec: u8, /* Number of pending OP_SqlExec opcodes */ 152 | nextPagesize: c.int, /* Pagesize after VACUUM if >0 */ 153 | magic: u32, /* Magic number for detect library misuse */ 154 | nChange: c.int, /* Value returned by sqlite3_changes() */ 155 | nTotalChange: c.int, /* Value returned by sqlite3_total_changes() */ 156 | aLimit: [N_LIMIT]c.int, /* Limits */ 157 | nMaxSorterMmap: c.int, /* Maximum size of regions mapped by sorter */ 158 | init: struct { /* Information used during initialization */ 159 | newTnum: Pgno, /* Rootpage of table being initialized */ 160 | iDb: u8, /* Which db file is being initialized */ 161 | busy: u8, /* TRUE if currently initializing */ 162 | orphanTrigger: u8, /* Last statement is orphaned TEMP trigger */ 163 | imposterTable: u8, /* Building an imposter table */ 164 | reopenMemdb: u8, /* ATTACH is really a reopen using MemDB */ 165 | azInit: ^^u8, /* "type", "name", and "tbl_name" columns */ 166 | }, 167 | nVdbeActive: c.int, /* Number of VDBEs currently running */ 168 | nVdbeRead: c.int, /* Number of active VDBEs that read or write */ 169 | nVdbeWrite: c.int, /* Number of active VDBEs that read and write */ 170 | nVdbeExec: c.int, /* Number of nested calls to VdbeExec() */ 171 | nVDestroy: c.int, /* Number of active OP_VDestroy operations */ 172 | nExtension: c.int, /* Number of loaded extensions */ 173 | aExtension: ^^rawptr, /* Array of shared library handles */ 174 | //union { 175 | //void (*xLegacy)(void*,const char*), /* Legacy trace function */ 176 | //c.int (*xV2)(u32,void*,void*,void*), /* V2 Trace function */ 177 | //} trace, 178 | } 179 | 180 | Pgno :: struct { 181 | 182 | } 183 | 184 | ResultCode :: enum c.int { 185 | OK = 0, /* Successful result */ 186 | ERROR = 1, /* Generic error */ 187 | INTERNAL = 2, /* Internal logic error in SQLite */ 188 | PERM = 3, /* Access permission denied */ 189 | ABORT = 4, /* Callback routine requested an abort */ 190 | BUSY = 5, /* The database file is locked */ 191 | LOCKED = 6, /* A table in the database is locked */ 192 | NOMEM = 7, /* A malloc() failed */ 193 | READONLY = 8, /* Attempt to write a readonly database */ 194 | INTERRUPT = 9, /* Operation terminated by sqlite3_interrupt()*/ 195 | IOERR = 10, /* Some kind of disk I/O error occurred */ 196 | CORRUPT = 11, /* The database disk image is malformed */ 197 | NOTFOUND = 12, /* Unknown opcode in sqlite3_file_control() */ 198 | FULL = 13, /* Insertion failed because database is full */ 199 | CANTOPEN = 14, /* Unable to open the database file */ 200 | PROTOCOL = 15, /* Database lock protocol error */ 201 | EMPTY = 16, /* Internal use only */ 202 | SCHEMA = 17, /* The database schema changed */ 203 | TOOBIG = 18, /* String or BLOB exceeds size limit */ 204 | CONSTRAINT = 19, /* Abort due to constraint violation */ 205 | MISMATCH = 20, /* Data type mismatch */ 206 | MISUSE = 21, /* Library used incorrectly */ 207 | NOLFS = 22, /* Uses OS features not supported on host */ 208 | AUTH = 23, /* Authorization denied */ 209 | FORMAT = 24, /* Not used */ 210 | RANGE = 25, /* 2nd parameter to sqlite3_bind out of range */ 211 | NOTADB = 26, /* File opened that is not a database file */ 212 | NOTICE = 27, /* Notifications from sqlite3_log() */ 213 | WARNING = 28, /* Warnings from sqlite3_log() */ 214 | ROW = 100, /* sqlite3_step() has another row ready */ 215 | DONE = 101, /* sqlite3_step() has finished executing */ 216 | } 217 | --------------------------------------------------------------------------------