mirror of https://github.com/iSoron/uhabits.git
parent
d96732b588
commit
046a7eab7f
@ -0,0 +1,25 @@
|
|||||||
|
package = sqlite3
|
||||||
|
headers = sqlite3.h
|
||||||
|
headerFilter = sqlite3*.h
|
||||||
|
compilerOpts = -std=c11
|
||||||
|
linkerOpts.ios = -lsqlite3
|
||||||
|
excludedFunctions = sqlite3_mutex_held \
|
||||||
|
sqlite3_mutex_notheld \
|
||||||
|
sqlite3_snapshot_cmp \
|
||||||
|
sqlite3_snapshot_free \
|
||||||
|
sqlite3_snapshot_get \
|
||||||
|
sqlite3_snapshot_open \
|
||||||
|
sqlite3_snapshot_recover \
|
||||||
|
sqlite3_set_last_insert_rowid \
|
||||||
|
sqlite3_stmt_scanstatus \
|
||||||
|
sqlite3_stmt_scanstatus_reset \
|
||||||
|
sqlite3_column_database_name \
|
||||||
|
sqlite3_column_database_name16 \
|
||||||
|
sqlite3_column_origin_name \
|
||||||
|
sqlite3_column_origin_name16 \
|
||||||
|
sqlite3_column_table_name \
|
||||||
|
sqlite3_column_table_name16 \
|
||||||
|
sqlite3_enable_load_extension \
|
||||||
|
sqlite3_load_extension \
|
||||||
|
sqlite3_unlock_notify
|
||||||
|
noStringConversion = sqlite3_prepare_v2 sqlite3_prepare_v3
|
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.io
|
||||||
|
|
||||||
|
import kotlinx.cinterop.*
|
||||||
|
import sqlite3.*
|
||||||
|
|
||||||
|
fun sqlite3_errstr(db: CPointer<sqlite3>): String {
|
||||||
|
return "SQLite3 error: " + sqlite3_errmsg(db).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
class IosDatabaseOpener() : DatabaseOpener {
|
||||||
|
override fun open(file: UserFile): Database = memScoped {
|
||||||
|
val db = alloc<CPointerVar<sqlite3>>()
|
||||||
|
val path = (file as IosFile).path
|
||||||
|
if (sqlite3_open(path, db.ptr) != SQLITE_OK) {
|
||||||
|
throw Exception(sqlite3_errstr(db.value!!))
|
||||||
|
}
|
||||||
|
return IosDatabase(db.value!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IosDatabase(val db: CPointer<sqlite3>) : Database {
|
||||||
|
override fun prepareStatement(sql: String): PreparedStatement = memScoped {
|
||||||
|
if (sql.isEmpty()) throw Exception("empty SQL query")
|
||||||
|
val stmt = alloc<CPointerVar<sqlite3_stmt>>()
|
||||||
|
if (sqlite3_prepare_v2(db, sql.cstr, -1, stmt.ptr, null) != SQLITE_OK) {
|
||||||
|
throw Exception(sqlite3_errstr(db))
|
||||||
|
}
|
||||||
|
return IosPreparedStatement(db, stmt.value!!)
|
||||||
|
}
|
||||||
|
override fun close() {
|
||||||
|
sqlite3_close(db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IosPreparedStatement(val db: CPointer<sqlite3>,
|
||||||
|
val stmt: CPointer<sqlite3_stmt>) : PreparedStatement {
|
||||||
|
override fun step(): StepResult {
|
||||||
|
val result = sqlite3_step(stmt)
|
||||||
|
when (result) {
|
||||||
|
SQLITE_ROW -> return StepResult.ROW
|
||||||
|
SQLITE_DONE -> return StepResult.DONE
|
||||||
|
else -> throw Exception(sqlite3_errstr(db))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun finalize() {
|
||||||
|
sqlite3_finalize(stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getInt(index: Int): Int {
|
||||||
|
return sqlite3_column_int(stmt, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLong(index: Int): Long {
|
||||||
|
return sqlite3_column_int64(stmt, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getText(index: Int): String {
|
||||||
|
return sqlite3_column_text(stmt, index)!!
|
||||||
|
.reinterpret<ByteVar>()
|
||||||
|
.toKString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReal(index: Int): Double {
|
||||||
|
return sqlite3_column_double(stmt, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindInt(index: Int, value: Int) {
|
||||||
|
sqlite3_bind_int(stmt, index + 1, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindLong(index: Int, value: Long) {
|
||||||
|
sqlite3_bind_int64(stmt, index + 1, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindText(index: Int, value: String) {
|
||||||
|
sqlite3_bind_text(stmt, index + 1, value, -1, SQLITE_TRANSIENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindReal(index: Int, value: Double) {
|
||||||
|
sqlite3_bind_double(stmt, index + 1, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() {
|
||||||
|
sqlite3_reset(stmt)
|
||||||
|
}
|
||||||
|
}
|
@ -1,140 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by the
|
|
||||||
* Free Software Foundation, either version 3 of the License, or (at your
|
|
||||||
* option) any later version.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SQLite3
|
|
||||||
|
|
||||||
internal let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self)
|
|
||||||
internal let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
|
||||||
|
|
||||||
class IosPreparedStatement : NSObject, PreparedStatement {
|
|
||||||
|
|
||||||
|
|
||||||
var db: OpaquePointer
|
|
||||||
var statement: OpaquePointer
|
|
||||||
|
|
||||||
init(withStatement statement: OpaquePointer, withDb db: OpaquePointer) {
|
|
||||||
self.statement = statement
|
|
||||||
self.db = db
|
|
||||||
}
|
|
||||||
|
|
||||||
func step() -> StepResult {
|
|
||||||
let result = sqlite3_step(statement)
|
|
||||||
if result == SQLITE_ROW {
|
|
||||||
return StepResult.row
|
|
||||||
} else if result == SQLITE_DONE {
|
|
||||||
return StepResult.done
|
|
||||||
} else {
|
|
||||||
let errMsg = String(cString: sqlite3_errmsg(db)!)
|
|
||||||
fatalError("Database error: \(errMsg) (\(result))")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getInt(index: Int32) -> Int32 {
|
|
||||||
return sqlite3_column_int(statement, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLong(index: Int32) -> Int64 {
|
|
||||||
return sqlite3_column_int64(statement, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getText(index: Int32) -> String {
|
|
||||||
return String(cString: sqlite3_column_text(statement, index))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getReal(index: Int32) -> Double {
|
|
||||||
return sqlite3_column_double(statement, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindInt(index: Int32, value: Int32) {
|
|
||||||
sqlite3_bind_int(statement, index + 1, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindText(index: Int32, value: String) {
|
|
||||||
sqlite3_bind_text(statement, index + 1, value, -1, SQLITE_TRANSIENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindReal(index: Int32, value: Double) {
|
|
||||||
sqlite3_bind_double(statement, index + 1, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reset() {
|
|
||||||
sqlite3_reset(statement)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func finalize() {
|
|
||||||
sqlite3_finalize(statement)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindLong(index: Int32, value: Int64) {
|
|
||||||
sqlite3_bind_int64(statement, index + 1, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IosDatabase : NSObject, Database {
|
|
||||||
var db: OpaquePointer
|
|
||||||
var log: Log
|
|
||||||
|
|
||||||
init(withDb db: OpaquePointer, withLog log: Log) {
|
|
||||||
self.db = db
|
|
||||||
self.log = log
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareStatement(sql: String) -> PreparedStatement {
|
|
||||||
if sql.isEmpty {
|
|
||||||
fatalError("Provided SQL query is empty")
|
|
||||||
}
|
|
||||||
var statement : OpaquePointer?
|
|
||||||
let result = sqlite3_prepare_v2(db, sql, -1, &statement, nil)
|
|
||||||
if result == SQLITE_OK {
|
|
||||||
return IosPreparedStatement(withStatement: statement!, withDb: db)
|
|
||||||
} else {
|
|
||||||
let errMsg = String(cString: sqlite3_errmsg(db)!)
|
|
||||||
fatalError("Database error: \(errMsg)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func close() {
|
|
||||||
sqlite3_close(db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IosDatabaseOpener : NSObject, DatabaseOpener {
|
|
||||||
|
|
||||||
var log: Log
|
|
||||||
|
|
||||||
init(withLog log: Log) {
|
|
||||||
self.log = log
|
|
||||||
}
|
|
||||||
|
|
||||||
func open(file: UserFile) -> Database {
|
|
||||||
let dbPath = (file as! IosFile).path
|
|
||||||
|
|
||||||
let version = String(cString: sqlite3_libversion())
|
|
||||||
log.info(tag: "IosDatabaseOpener", msg: "SQLite \(version)")
|
|
||||||
log.info(tag: "IosDatabaseOpener", msg: "Opening database: \(dbPath)")
|
|
||||||
var db: OpaquePointer?
|
|
||||||
let result = sqlite3_open(dbPath, &db)
|
|
||||||
if result == SQLITE_OK {
|
|
||||||
return IosDatabase(withDb: db!, withLog: log)
|
|
||||||
} else {
|
|
||||||
fatalError("Error opening database (code \(result))")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by the
|
|
||||||
* Free Software Foundation, either version 3 of the License, or (at your
|
|
||||||
* option) any later version.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import XCTest
|
|
||||||
@testable import uhabits
|
|
||||||
|
|
||||||
class IosDatabaseTest: XCTestCase {
|
|
||||||
func testUsage() {
|
|
||||||
let databaseOpener = IosDatabaseOpener(withLog: StandardLog())
|
|
||||||
let fileOpener = IosFileOpener()
|
|
||||||
|
|
||||||
let dbFile = fileOpener.openUserFile(path: "test.sqlite3")
|
|
||||||
let db = databaseOpener.open(file: dbFile)
|
|
||||||
|
|
||||||
var stmt = db.prepareStatement(sql: "drop table if exists demo")
|
|
||||||
stmt.step()
|
|
||||||
stmt.finalize()
|
|
||||||
|
|
||||||
stmt = db.prepareStatement(sql: "begin")
|
|
||||||
stmt.step()
|
|
||||||
stmt.finalize()
|
|
||||||
|
|
||||||
stmt = db.prepareStatement(sql: "create table if not exists demo(key int, value text)")
|
|
||||||
stmt.step()
|
|
||||||
stmt.finalize()
|
|
||||||
|
|
||||||
stmt = db.prepareStatement(sql: "insert into demo(key, value) values (?, ?)")
|
|
||||||
stmt.bindInt(index: 0, value: 42)
|
|
||||||
stmt.bindText(index: 1, value: "Hello World")
|
|
||||||
stmt.step()
|
|
||||||
stmt.finalize()
|
|
||||||
|
|
||||||
stmt = db.prepareStatement(sql: "select * from demo where key > ?")
|
|
||||||
stmt.bindInt(index: 0, value: 10)
|
|
||||||
var result = stmt.step()
|
|
||||||
XCTAssertEqual(result, StepResult.row)
|
|
||||||
XCTAssertEqual(stmt.getInt(index: 0), 42)
|
|
||||||
XCTAssertEqual(stmt.getText(index: 1), "Hello World")
|
|
||||||
result = stmt.step()
|
|
||||||
XCTAssertEqual(result, StepResult.done)
|
|
||||||
stmt.finalize()
|
|
||||||
|
|
||||||
stmt = db.prepareStatement(sql: "drop table demo")
|
|
||||||
stmt.step()
|
|
||||||
stmt.finalize()
|
|
||||||
|
|
||||||
stmt = db.prepareStatement(sql: "commit")
|
|
||||||
stmt.step()
|
|
||||||
stmt.finalize()
|
|
||||||
|
|
||||||
db.close()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in new issue