import Foundation import sqlcipher private struct SqlitePreparedStatement { let statement: COpaquePointer func bind(index: Int, data: UnsafePointer, length: Int) { sqlite3_bind_blob(statement, Int32(index), data, Int32(length), nil) } func bind(index: Int, number: Int32) { sqlite3_bind_int(statement, Int32(index), number) } func reset() { sqlite3_reset(statement) sqlite3_clear_bindings(statement) } func step() -> Bool { let result = sqlite3_step(statement) if result != SQLITE_ROW && result != SQLITE_DONE { assertionFailure("Sqlite error \(result)") } return result == SQLITE_ROW } func valueAt(index: Int) -> ReadBuffer { let valueLength = sqlite3_column_bytes(statement, Int32(index)) let valueData = sqlite3_column_blob(statement, Int32(index)) let valueMemory = malloc(Int(valueLength)) memcpy(valueMemory, valueData, Int(valueLength)) return ReadBuffer(memory: valueMemory, length: Int(valueLength), freeWhenDone: true) } func keyAt(index: Int) -> ValueBoxKey { let valueLength = sqlite3_column_bytes(statement, Int32(index)) let valueData = sqlite3_column_blob(statement, Int32(index)) let key = ValueBoxKey(length: Int(valueLength)) memcpy(key.memory, valueData, Int(valueLength)) return key } func destroy() { sqlite3_finalize(statement) } } public final class SqliteValueBox: ValueBox { private let database: Database private var tables = Set() private var getStatements: [Int32 : SqlitePreparedStatement] = [:] private var rangeKeyAscStatementsLimit: [Int32 : SqlitePreparedStatement] = [:] private var rangeKeyAscStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:] private var rangeKeyDescStatementsLimit: [Int32 : SqlitePreparedStatement] = [:] private var rangeKeyDescStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:] private var rangeValueAscStatementsLimit: [Int32 : SqlitePreparedStatement] = [:] private var rangeValueAscStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:] private var rangeValueDescStatementsLimit: [Int32 : SqlitePreparedStatement] = [:] private var rangeValueDescStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:] private var existsStatements: [Int32 : SqlitePreparedStatement] = [:] private var updateStatements: [Int32 : SqlitePreparedStatement] = [:] private var insertStatements: [Int32 : SqlitePreparedStatement] = [:] private var deleteStatements: [Int32 : SqlitePreparedStatement] = [:] private var readQueryTime: CFAbsoluteTime = 0.0 private var writeQueryTime: CFAbsoluteTime = 0.0 private var commitTime: CFAbsoluteTime = 0.0 public init(basePath: String) { do { try NSFileManager.defaultManager().createDirectoryAtPath(basePath, withIntermediateDirectories: true, attributes: nil) } catch _ { } let path = basePath + "/db_sqlite" self.database = Database(path) self.database.adjustChunkSize() self.database.execute("PRAGMA page_size=1024") self.database.execute("PRAGMA cache_size=-2097152") self.database.execute("PRAGMA synchronous=NORMAL") self.database.execute("PRAGMA journal_mode=truncate") self.database.execute("PRAGMA temp_store=MEMORY") //self.database.execute("PRAGMA wal_autocheckpoint=32") //self.database.execute("PRAGMA journal_size_limit=1536") let result = self.database.scalar("PRAGMA user_version") as! Int64 if result != 1 { self.database.execute("PRAGMA user_version=1") self.database.execute("CREATE TABLE __meta_tables (name INTEGER)") } for row in self.database.prepare("SELECT name FROM __meta_tables").run() { self.tables.insert(Int32(row[0] as! Int64)) } } deinit { self.clearStatements() } public func beginStats() { self.readQueryTime = 0.0 self.writeQueryTime = 0.0 self.commitTime = 0.0 } public func endStats() { print("(SqliteValueBox stats read: \(self.readQueryTime * 1000.0) ms, write: \(self.writeQueryTime * 1000.0) ms, commit: \(self.commitTime * 1000.0) ms") } public func begin() { self.database.transaction() } public func commit() { let startTime = CFAbsoluteTimeGetCurrent() self.database.commit() self.commitTime += CFAbsoluteTimeGetCurrent() - startTime } private func getStatement(table: Int32, key: ValueBoxKey) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.getStatements[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "SELECT value FROM t\(table) WHERE key=?", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.getStatements[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: key.memory, length: key.length) return resultStatement } private func rangeKeyAscStatementLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.rangeKeyAscStatementsLimit[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table) WHERE key > ? AND key < ? ORDER BY key ASC LIMIT ?", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.rangeKeyAscStatementsLimit[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: start.memory, length: start.length) resultStatement.bind(2, data: end.memory, length: end.length) resultStatement.bind(3, number: Int32(limit)) return resultStatement } private func rangeKeyAscStatementNoLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.rangeKeyAscStatementsNoLimit[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table) WHERE key > ? AND key < ? ORDER BY key ASC", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.rangeKeyAscStatementsNoLimit[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: start.memory, length: start.length) resultStatement.bind(2, data: end.memory, length: end.length) return resultStatement } private func rangeKeyDescStatementLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.rangeKeyDescStatementsLimit[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table) WHERE key > ? AND key < ? ORDER BY key DESC LIMIT ?", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.rangeKeyDescStatementsLimit[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: start.memory, length: start.length) resultStatement.bind(2, data: end.memory, length: end.length) resultStatement.bind(3, number: Int32(limit)) return resultStatement } private func rangeKeyDescStatementNoLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.rangeKeyDescStatementsNoLimit[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table) WHERE key > ? AND key < ? ORDER BY key DESC", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.rangeKeyDescStatementsNoLimit[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: start.memory, length: start.length) resultStatement.bind(2, data: end.memory, length: end.length) return resultStatement } private func rangeValueAscStatementLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.rangeValueAscStatementsLimit[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table) WHERE key > ? AND key < ? ORDER BY key ASC LIMIT ?", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.rangeValueAscStatementsLimit[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: start.memory, length: start.length) resultStatement.bind(2, data: end.memory, length: end.length) resultStatement.bind(3, number: Int32(limit)) return resultStatement } private func rangeValueAscStatementNoLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.rangeValueAscStatementsNoLimit[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table) WHERE key > ? AND key < ? ORDER BY key ASC", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.rangeValueAscStatementsNoLimit[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: start.memory, length: start.length) resultStatement.bind(2, data: end.memory, length: end.length) return resultStatement } private func rangeValueDescStatementLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.rangeValueDescStatementsLimit[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table) WHERE key > ? AND key < ? ORDER BY key DESC LIMIT ?", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.rangeValueDescStatementsLimit[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: start.memory, length: start.length) resultStatement.bind(2, data: end.memory, length: end.length) resultStatement.bind(3, number: Int32(limit)) return resultStatement } private func rangeValueDescStatementNoLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.rangeKeyDescStatementsNoLimit[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table) WHERE key > ? AND key < ? ORDER BY key DESC", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.rangeValueDescStatementsNoLimit[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: start.memory, length: start.length) resultStatement.bind(2, data: end.memory, length: end.length) return resultStatement } private func existsStatement(table: Int32, key: ValueBoxKey) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.existsStatements[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table) WHERE key=?", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.existsStatements[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: key.memory, length: key.length) return resultStatement } private func updateStatement(table: Int32, key: ValueBoxKey, value: MemoryBuffer) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.updateStatements[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "UPDATE t\(table) SET value=? WHERE key=?", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.updateStatements[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: value.memory, length: value.length) resultStatement.bind(2, data: key.memory, length: key.length) return resultStatement } private func insertStatement(table: Int32, key: ValueBoxKey, value: MemoryBuffer) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.insertStatements[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "INSERT INTO t\(table) (key, value) VALUES(?, ?)", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.insertStatements[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: key.memory, length: key.length) resultStatement.bind(2, data: value.memory, length: value.length) return resultStatement } private func deleteStatement(table: Int32, key: ValueBoxKey) -> SqlitePreparedStatement { let resultStatement: SqlitePreparedStatement if let statement = self.deleteStatements[table] { resultStatement = statement } else { var statement: COpaquePointer = nil sqlite3_prepare_v2(self.database.handle, "DELETE FROM t\(table) WHERE key=?", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) self.deleteStatements[table] = preparedStatement resultStatement = preparedStatement } resultStatement.reset() resultStatement.bind(1, data: key.memory, length: key.length) return resultStatement } public func get(table: Int32, key: ValueBoxKey) -> ReadBuffer? { let startTime = CFAbsoluteTimeGetCurrent() if self.tables.contains(table) { let statement = self.getStatement(table, key: key) var buffer: ReadBuffer? while statement.step() { buffer = statement.valueAt(0) break } statement.reset() self.readQueryTime += CFAbsoluteTimeGetCurrent() - startTime return buffer } return nil } public func exists(table: Int32, key: ValueBoxKey) -> Bool { if let _ = self.get(table, key: key) { return true } return false } public func range(table: Int32, start: ValueBoxKey, end: ValueBoxKey, @noescape values: (ValueBoxKey, ReadBuffer) -> Bool, limit: Int) { if start == end { return } if self.tables.contains(table) { let statement: SqlitePreparedStatement var startTime = CFAbsoluteTimeGetCurrent() if start < end { if limit <= 0 { statement = self.rangeValueAscStatementNoLimit(table, start: start, end: end) } else { statement = self.rangeValueAscStatementLimit(table, start: start, end: end, limit: limit) } } else { if limit <= 0 { statement = self.rangeValueDescStatementNoLimit(table, start: end, end: start) } else { statement = self.rangeValueDescStatementLimit(table, start: end, end: start, limit: limit) } } var currentTime = CFAbsoluteTimeGetCurrent() self.readQueryTime += currentTime - startTime startTime = currentTime while statement.step() { startTime = CFAbsoluteTimeGetCurrent() let key = statement.keyAt(0) let value = statement.valueAt(1) currentTime = CFAbsoluteTimeGetCurrent() self.readQueryTime += currentTime - startTime if !values(key, value) { break } } statement.reset() } } public func range(table: Int32, start: ValueBoxKey, end: ValueBoxKey, keys: ValueBoxKey -> Bool, limit: Int) { if self.tables.contains(table) { let statement: SqlitePreparedStatement var startTime = CFAbsoluteTimeGetCurrent() if start < end { if limit <= 0 { statement = self.rangeKeyAscStatementNoLimit(table, start: start, end: end) } else { statement = self.rangeKeyAscStatementLimit(table, start: start, end: end, limit: limit) } } else { if limit <= 0 { statement = self.rangeKeyDescStatementNoLimit(table, start: end, end: start) } else { statement = self.rangeKeyDescStatementLimit(table, start: end, end: start, limit: limit) } } var currentTime = CFAbsoluteTimeGetCurrent() self.readQueryTime += currentTime - startTime startTime = currentTime while statement.step() { startTime = CFAbsoluteTimeGetCurrent() let key = statement.keyAt(0) currentTime = CFAbsoluteTimeGetCurrent() self.readQueryTime += currentTime - startTime if !keys(key) { break } } statement.reset() } } public func set(table: Int32, key: ValueBoxKey, value: MemoryBuffer) { if !self.tables.contains(table) { self.database.execute("CREATE TABLE t\(table) (key BLOB, value BLOB)") self.database.execute("CREATE INDEX t\(table)_key ON t\(table) (key)") self.tables.insert(table) self.database.execute("INSERT INTO __meta_tables(name) VALUES (\(table))") } let startTime = CFAbsoluteTimeGetCurrent() var exists = false let existsStatement = self.existsStatement(table, key: key) if existsStatement.step() { exists = true } existsStatement.reset() if exists { let statement = self.updateStatement(table, key: key, value: value) while statement.step() { } statement.reset() } else { let statement = self.insertStatement(table, key: key, value: value) while statement.step() { } statement.reset() } self.writeQueryTime += CFAbsoluteTimeGetCurrent() - startTime } public func remove(table: Int32, key: ValueBoxKey) { if self.tables.contains(table) { let startTime = CFAbsoluteTimeGetCurrent() let statement = self.deleteStatement(table, key: key) while statement.step() { } statement.reset() self.writeQueryTime += CFAbsoluteTimeGetCurrent() - startTime } } private func clearStatements() { for (_, statement) in self.getStatements { statement.destroy() } self.getStatements.removeAll() for (_, statement) in self.rangeKeyAscStatementsLimit { statement.destroy() } self.rangeKeyAscStatementsLimit.removeAll() for (_, statement) in self.rangeKeyAscStatementsNoLimit { statement.destroy() } self.rangeKeyAscStatementsNoLimit.removeAll() for (_, statement) in self.rangeKeyDescStatementsLimit { statement.destroy() } self.rangeKeyDescStatementsLimit.removeAll() for (_, statement) in self.rangeKeyDescStatementsNoLimit { statement.destroy() } self.rangeKeyDescStatementsNoLimit.removeAll() for (_, statement) in self.rangeValueAscStatementsLimit { statement.destroy() } self.rangeValueAscStatementsLimit.removeAll() for (_, statement) in self.rangeValueAscStatementsNoLimit { statement.destroy() } self.rangeValueAscStatementsNoLimit.removeAll() for (_, statement) in self.rangeValueDescStatementsLimit { statement.destroy() } self.rangeValueDescStatementsLimit.removeAll() for (_, statement) in self.rangeValueDescStatementsNoLimit { statement.destroy() } self.rangeValueDescStatementsNoLimit.removeAll() for (_, statement) in self.existsStatements { statement.destroy() } self.existsStatements.removeAll() for (_, statement) in self.updateStatements { statement.destroy() } self.updateStatements.removeAll() for (_, statement) in self.insertStatements { statement.destroy() } self.insertStatements.removeAll() for (_, statement) in self.deleteStatements { statement.destroy() } self.deleteStatements.removeAll() } public func drop() { self.clearStatements() for table in self.tables { self.database.execute("DROP TABLE IF EXISTS t\(table)") } self.database.execute("DELETE FROM __meta_tables") } }