///|
priv struct ConnectionState {
mut database : @ffi.Sqlite3?
mut executor : @ffi.Sqlite3Executor?
mut jobs_in_flight : Int
}
///|
struct Connection(Ref[ConnectionState])
///|
fn create_executor() -> @ffi.Sqlite3Executor raise SqliteError {
let rescode = Ref(SQLITE_OK)
let executor = @ffi.sqlite3_executor_create(rescode)
if rescode.val != SQLITE_OK {
raise sqlite_error(
rescode.val,
"sqlite3: failed to start the asynchronous executor",
)
}
executor
}
///|
fn Connection::from_open_database(
database : @ffi.Sqlite3,
executor : @ffi.Sqlite3Executor?,
) -> Connection {
Connection(Ref({ database: Some(database), executor, jobs_in_flight: 0, }))
}
///|
fn Connection::handle(self : Connection) -> @ffi.Sqlite3 raise SqliteError {
match self.0.val.database {
Some(db) => db
None => raise sqlite_misuse("sqlite3: connection is closed")
}
}
///|
fn Connection::begin_job(
self : Connection,
) -> (@ffi.Sqlite3, @ffi.Sqlite3Executor) raise SqliteError {
let state = self.0.val
match state.database {
Some(database) => {
let executor = match state.executor {
Some(executor) => executor
None => {
let executor = create_executor()
state.executor = Some(executor)
executor
}
}
state.jobs_in_flight += 1
(database, executor)
}
None => raise sqlite_misuse("sqlite3: connection is closed")
}
}
///|
fn Connection::finish_job(self : Connection) -> Unit {
self.0.val.jobs_in_flight -= 1
}
///|
fn Connection::error(self : Connection, raw_code : Int) -> SqliteError {
match self.0.val.database {
Some(db) => sqlite_error(raw_code, @ffi.sqlite3_errmsg(db))
None => sqlite_error(raw_code, "sqlite3: database connection is closed")
}
}
///|
const SQLITE_OPEN_READWRITE : Int = 0x00000002
///|
const SQLITE_OPEN_CREATE : Int = 0x00000004
///|
const SQLITE_OPEN_FULLMUTEX : Int = 0x00010000
///|
pub fn Connection::open(filename : String) -> Connection raise SqliteError {
if filename.contains_code_unit(0x0000) {
raise sqlite_misuse("sqlite3: filename contains a NUL code unit")
}
let filename = @utf8.encode(filename)
let flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX
let rescode = Ref(SQLITE_OK)
let db = @ffi.sqlite3_open_v2(filename, flags, rescode)
if rescode.val == SQLITE_OK {
// Failed open handles are valid only for error retrieval and close. Keep
// them local so every handle stored by Connection is ready for database
// operations.
return Connection::from_open_database(db, None)
} else {
let error = if @ffi.sqlite3_is_null(db) {
sqlite_error(rescode.val, "sqlite3: failed to allocate a database handle")
} else {
sqlite_error(rescode.val, @ffi.sqlite3_errmsg(db))
}
let _ = @ffi.sqlite3_close(db)
raise error
}
}
///|
/// Return the number of rows changed by the most recently completed `INSERT`,
/// `UPDATE`, or `DELETE` on this connection.
pub fn Connection::changes(self : Connection) -> Int64 raise SqliteError {
let database = self.handle()
@ffi.sqlite3_db_mutex_enter(database)
defer @ffi.sqlite3_db_mutex_leave(database)
@ffi.sqlite3_changes64(database)
}
///|
pub fn Connection::close(self : Connection) -> Unit raise SqliteError {
if self.0.val.jobs_in_flight != 0 {
raise sqlite_misuse("sqlite3: asynchronous operations are running")
}
match self.0.val.database {
None => ()
Some(db) => {
// A successful close destroys the connection mutex, so close cannot use
// the deferred unlock pattern used by statement operations. Callers must
// exclude outstanding worker operations before this lifecycle step.
let rescode = @ffi.sqlite3_close(db)
if rescode == SQLITE_OK {
if self.0.val.executor is Some(executor) {
@ffi.sqlite3_executor_release(executor)
self.0.val.executor = None
}
self.0.val.database = None
} else {
raise self.error(rescode)
}
}
}
}