Home telegram - sqliteValueBox
Post
Cancel

telegram - sqliteValueBox

SqliteValueBox

class SqliteValueBox:ValueBox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
private let lock = NSRecursiveLock()

fileprivate let basePath: String
private let isTemporary: Bool
private let isReadOnly: Bool
private let useCaches: Bool
private let inMemory: Bool
private let encryptionParameters: ValueBoxEncryptionParameters?
private let databasePath: String
private let removeDatabaseOnError: Bool
private var database: Database!
private var tables: [Int32: SqliteValueBoxTable] = [:]
private var fullTextTables: [Int32: ValueBoxFullTextTable] = [:]

private var getStatements: [Int32 : SqlitePreparedStatement] = [:]
private var getRowIdStatements: [Int32 : SqlitePreparedStatement] = [:]
private var rangeKeyAscStatementsLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeKeyAscStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeKeyDescStatementsLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeKeyDescStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:]
private var deleteRangeStatements: [Int32 : SqlitePreparedStatement] = [:]
private var rangeValueAscStatementsLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeValueAscStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeValueDescStatementsLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeValueDescStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:]
private var scanStatements: [Int32 : SqlitePreparedStatement] = [:]
private var scanKeysStatements: [Int32 : SqlitePreparedStatement] = [:]
private var existsStatements: [Int32 : SqlitePreparedStatement] = [:]
private var updateStatements: [Int32 : SqlitePreparedStatement] = [:]
private var insertOrReplacePrimaryKeyStatements: [Int32 : SqlitePreparedStatement] = [:]
private var insertOrReplaceIndexKeyStatements: [Int32 : SqlitePreparedStatement] = [:]
private var deleteStatements: [Int32 : SqlitePreparedStatement] = [:]
private var moveStatements: [Int32 : SqlitePreparedStatement] = [:]
private var copyStatements: [TablePairKey : SqlitePreparedStatement] = [:]
private var fullTextInsertStatements: [Int32 : SqlitePreparedStatement] = [:]
private var fullTextDeleteStatements: [Int32 : SqlitePreparedStatement] = [:]
private var fullTextMatchGlobalStatements: [Int32 : SqlitePreparedStatement] = [:]
private var fullTextMatchCollectionStatements: [Int32 : SqlitePreparedStatement] = [:]
private var fullTextMatchCollectionTagsStatements: [Int32 : SqlitePreparedStatement] = [:]

private var secureDeleteEnabled: Bool = false
private let checkpoints = MetaDisposable()
private let queue: Queue


public init?(basePath: String, queue: Queue, isTemporary: Bool, isReadOnly: Bool, useCaches: Bool, removeDatabaseOnError: Bool,
encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void, inMemory: Bool = false) {
    self.basePath = basePath
    self.isTemporary = isTemporary
    self.isReadOnly = isReadOnly
    self.useCaches = useCaches
    self.removeDatabaseOnError = removeDatabaseOnError
    self.inMemory = inMemory
    self.encryptionParameters = encryptionParameters
    self.databasePath = basePath + "/db_sqlite"
    self.queue = queue
    if let database = self.openDatabase(encryptionParameters: encryptionParameters, isTemporary: isTemporary, isReadOnly: isReadOnly,
upgradeProgress: upgradeProgress) {
        self.database = database
    } else {
        return nil
    }
}

deinit {
    precondition(self.queue.isCurrent())
    self.clearStatements()
    checkpoints.dispose()
}

func internalClose() {
    self.database = nil
}

Open Database

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
private func openDatabase(encryptionParameters: ValueBoxEncryptionParameters?, isTemporary: Bool, isReadOnly: Bool, upgradeProgress: (Float) -> Void) -> Database? {
    precondition(self.queue.isCurrent())

    checkpoints.set(nil)
    lock.lock()

    let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
    let path = basePath + "/db_sqlite"

    postboxLog("Instance \(self) opening sqlite at \(path)")

  // dump existing db data in DEBUG mode
    #if DEBUG
    let exists = FileManager.default.fileExists(atPath: path)
    postboxLog("Opening \(path), exists: \(exists)")
    if exists {
        do {
            let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
            postboxLog("\(path) size: \(data.count)")
        } catch let e {
            postboxLog("Couldn't open database: \(e)")
        }
    }
    let walExists = FileManager.default.fileExists(atPath: path + "-wal")
    postboxLog("Opening \(path)-wal, exists: \(walExists)")
    if walExists {
        do {
            let data = try Data(contentsOf: URL(fileURLWithPath: path + "-wal"), options: .mappedIfSafe)
            postboxLog("\(path)-wal size: \(data.count)")
        } catch let e {
            postboxLog("Couldn't open database: \(e)")
        }
    }
    #endif

  // open db in path or memory
    var database: Database
    if let result = Database(self.inMemory ? ":memory:" : path, readOnly: isReadOnly) {
        database = result
    } else {
        postboxLog("Couldn't open DB")

        if isReadOnly {
            postboxLog("Readonly, exiting")
            return nil
        }

        let tempPath = basePath + "_test\(arc4random())"
        enum TempError: Error {
            case generic
        }
        do {
            try FileManager.default.createDirectory(atPath: tempPath, withIntermediateDirectories: true, attributes: nil)
          
          // create a test.db
            let testDatabase = Database(tempPath + "/test_db", readOnly: false)!
          // set journal_mode to WAL
            var resultCode = testDatabase.execute("PRAGMA journal_mode=WAL")
            if !resultCode {
                throw TempError.generic
            }
          // set the user_version header field to 123
            resultCode = testDatabase.execute("PRAGMA user_version=123")
            if !resultCode {
                throw TempError.generic
            }
        } catch {
          // throw has no write access to database folder
            let _ = try? FileManager.default.removeItem(atPath: tempPath)
            postboxLog("Don't have write access to database folder")
            preconditionFailure("Don't have write access to database folder")
        }

      // remove on error
        if self.removeDatabaseOnError {
            let _ = try? FileManager.default.removeItem(atPath: path)
        }
        preconditionFailure("Couldn't open database")
    }

    postboxLog("Did open DB at \(path)")

    sqlite3_busy_timeout(database.handle, 5 * 1000)

    var resultCode: Bool = true

    resultCode = database.execute("PRAGMA cipher_plaintext_header_size=32")
    assert(resultCode)
    resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32")
    assert(resultCode)

    postboxLog("Did set up cipher")

    if self.isEncrypted(database) {
        postboxLog("Database is encrypted")

        if let encryptionParameters = encryptionParameters {// has stored encryption key 
            precondition(encryptionParameters.salt.data.count == 16)
            precondition(encryptionParameters.key.data.count == 32)

            let hexKey = hexString(encryptionParameters.key.data + encryptionParameters.salt.data)

          // provide key and salt
            resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"")
            assert(resultCode)

            postboxLog("Setting encryption key")

          // still encrypted (key is invalid)
            if self.isEncrypted(database) {
                postboxLog("Encryption key is invalid")

                if isTemporary || isReadOnly || !self.removeDatabaseOnError {
                    return nil
                }

                for fileName in dabaseFileNames {
                    let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
                }
              // retyr create database
                database = Database(path, readOnly: false)!

                resultCode = database.execute("PRAGMA cipher_plaintext_header_size=32")
                assert(resultCode)
                resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32")
                assert(resultCode)

              // provide key and salt
                resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"")
                assert(resultCode)
            }
        } else {// encryption key is required
            postboxLog("Encryption key is required")
            if isReadOnly || !self.removeDatabaseOnError {
                return nil
            }

            assert(false)
            for fileName in dabaseFileNames {
                let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
            }
          // create the database
            database = Database(path, readOnly: false)!

            resultCode = database.execute("PRAGMA cipher_plaintext_header_size=32")
            assert(resultCode)
            resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32")
            assert(resultCode)
        }
    } else if let encryptionParameters = encryptionParameters, encryptionParameters.forceEncryptionIfNoSet {// not encrypted
        postboxLog("Not encrypted")

        let hexKey = hexString(encryptionParameters.key.data + encryptionParameters.salt.data)

        if FileManager.default.fileExists(atPath: path) {// db file is exsiting
            if isReadOnly {
                return nil
            }

            postboxLog("Reencrypting database")
          // reencrypting database
            database = self.reencryptInPlace(database: database, encryptionParameters: encryptionParameters)

          // encrypted successfully
            if self.isEncrypted(database) {
                postboxLog("Reencryption failed")

                for fileName in dabaseFileNames {
                    let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
                }
                database = Database(path, readOnly: false)!

                resultCode = database.execute("PRAGMA cipher_plaintext_header_size=32")
                assert(resultCode)
                resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32")
                assert(resultCode)

              // test the encryption key
                resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"")
                assert(resultCode)
            }
        } else {//db file not exsiting
            precondition(encryptionParameters.salt.data.count == 16)
            precondition(encryptionParameters.key.data.count == 32)
          // test the encryption key
            resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"")
            assert(resultCode)

            if self.isEncrypted(database) {
                postboxLog("Encryption setup failed")
                //assert(false)

                if isReadOnly {
                    return nil
                }

                for fileName in dabaseFileNames {
                    let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
                }
              // recreate the db file
                database = Database(path, readOnly: false)!

                resultCode = database.execute("PRAGMA cipher_plaintext_header_size=32")
                assert(resultCode)
                resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32")
                assert(resultCode)

              // test the encryption key
                resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"")
                assert(resultCode)
            }
        }
    }

    postboxLog("Did set up encryption")

    if !self.useCaches {
      // set cache size
        resultCode = database.execute("PRAGMA cache_size=32")
        assert(resultCode)
    }
  // diable memory-map
    resultCode = database.execute("PRAGMA mmap_size=0")
    assert(resultCode)
  // set synchronous mode to `Normal`
    resultCode = database.execute("PRAGMA synchronous=NORMAL")
    assert(resultCode)
  // set temp_store type as `MEMORY`
    resultCode = database.execute("PRAGMA temp_store=MEMORY")
    assert(resultCode)
  // set journal_mode to `WAL`
    resultCode = database.execute("PRAGMA journal_mode=WAL")
    assert(resultCode)
  // disable memory wiping
    resultCode = database.execute("PRAGMA cipher_memory_security = OFF")
    assert(resultCode)

    postboxLog("Did set up pragmas")

    //resultCode = database.execute("PRAGMA wal_autocheckpoint=500")
    //database.execute("PRAGMA journal_size_limit=1536")

    /*#if DEBUG
    var statement: OpaquePointer? = nil
    sqlite3_prepare_v2(database.handle, "PRAGMA integrity_check", -1, &statement, nil)
    let preparedStatement = SqlitePreparedStatement(statement: statement)
    while preparedStatement.step(handle: database.handle, path: self.databasePath) {
        let value = preparedStatement.valueAt(0)
        let text = String(data: Data(bytes: value.memory.assumingMemoryBound(to: UInt8.self), count: value.length), encoding: .utf8)
        print("integrity_check: \(text ?? "")")
        assert(text == "ok")
        //let value = preparedStatement.stringAt(0)
        //print("integrity_check: \(value)")
    }
    preparedStatement.destroy()
    #endif*/

    let _ = self.runPragma(database, "checkpoint_fullfsync = 1")
    assert(self.runPragma(database, "checkpoint_fullfsync") == "1")

    postboxLog("Did set up checkpoint_fullfsync")

    self.beginInternal(database: database)

    postboxLog("Did begin transaction")

    let result = self.getUserVersion(database)

    if result < 3 {
        resultCode = database.execute("CREATE TABLE __meta_fulltext_tables (name INTEGER)")
        assert(resultCode)
    }

    if result < 4 {
        resultCode = database.execute("PRAGMA user_version=4")
        assert(resultCode)
    }

    for table in self.listTables(database) {
        self.tables[table.table.id] = table
    }
    for table in self.listFullTextTables(database) {
        self.fullTextTables[table.id] = table
    }

    postboxLog("Did load tables")

    self.commitInternal(database: database)

    postboxLog("Did commit final")

    lock.unlock()

    return database
}

Query tables in DB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
private func listTables(_ database: Database) -> [SqliteValueBoxTable] {
    precondition(self.queue.isCurrent())
    var statement: OpaquePointer? = nil
    let status = sqlite3_prepare_v2(database.handle, "SELECT name, type, sql FROM sqlite_master", -1, &statement, nil)
    precondition(status == SQLITE_OK)
    let preparedStatement = SqlitePreparedStatement(statement: statement)
    var tables: [SqliteValueBoxTable] = []

    while preparedStatement.step(handle: database.handle, true, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        guard let name = preparedStatement.stringAt(0) else {
            assertionFailure()
            continue
        }
        guard let type = preparedStatement.stringAt(1), type == "table" else {
            continue
        }
        guard let sql = preparedStatement.stringAt(2) else {
            assertionFailure()
            continue
        }

        if name.hasPrefix("t") {
            if let intName = Int(String(name[name.index(after: name.startIndex)...])) {
                let keyType: ValueBoxKeyType
                var hasPrimaryKey = false
                if sql.range(of: "(key INTEGER") != nil {
                    keyType = .int64
                    hasPrimaryKey = true
                } else if sql.range(of: "(key BLOB") != nil {
                    keyType = .binary
                    if sql.range(of: "(key BLOB PRIMARY KEY") != nil {
                        hasPrimaryKey = true
                    }
                } else {
                    assertionFailure()
                    continue
                }
                let isCompact = sql.range(of: "WITHOUT ROWID") != nil
                tables.append(SqliteValueBoxTable(table: ValueBoxTable(id: Int32(intName), keyType: keyType, compactValuesOnCreation: isCompact), hasPrimaryKey: hasPrimaryKey))
            }
        }
    }
    preparedStatement.destroy()

    return tables
}

Query Full Text Tables in DB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private func listFullTextTables(_ database: Database) -> [ValueBoxFullTextTable] {
    precondition(self.queue.isCurrent())
    var statement: OpaquePointer? = nil
    let status = sqlite3_prepare_v2(database.handle, "SELECT name FROM __meta_fulltext_tables", -1, &statement, nil)
    assert(status == SQLITE_OK)
    let preparedStatement = SqlitePreparedStatement(statement: statement)
    var tables: [ValueBoxFullTextTable] = []

    while preparedStatement.step(handle: database.handle, true, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        let value = preparedStatement.int64At(0)
        tables.append(ValueBoxFullTextTable(id: Int32(value)))
    }
    preparedStatement.destroy()
    return tables
}

Ensure Table Existing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private func checkTable(_ table: ValueBoxTable) -> SqliteValueBoxTable {
    precondition(self.queue.isCurrent())
    if let currentTable = self.tables[table.id] {
        precondition(currentTable.table.keyType == table.keyType)
        return currentTable
    } else {
        self.createTable(database: self.database, table: table)
        let resultTable = SqliteValueBoxTable(table: table, hasPrimaryKey: true)
        self.tables[table.id] = resultTable
        return resultTable
    }
}

private func createTable(database: Database, table: ValueBoxTable) {
    switch table.keyType {
        case .binary:
            var resultCode: Bool
            var createStatement = "CREATE TABLE IF NOT EXISTS t\(table.id) (key BLOB PRIMARY KEY, value BLOB)"
            if table.compactValuesOnCreation {
                createStatement += " WITHOUT ROWID"
            }
            resultCode = database.execute(createStatement)
            assert(resultCode)
        case .int64:
            let resultCode = database.execute("CREATE TABLE IF NOT EXISTS t\(table.id) (key INTEGER PRIMARY KEY, value BLOB)")
            assert(resultCode)
    }
}

Ensure Full Text Table Existing

1
2
3
4
5
6
7
8
9
10
11
12
private func checkFullTextTable(_ table: ValueBoxFullTextTable) {
    precondition(self.queue.isCurrent())
    if let _ = self.fullTextTables[table.id] {
    } else {
        var resultCode = self.database.execute("CREATE VIRTUAL TABLE IF NOT EXISTS ft\(table.id) USING fts5(collectionId, itemId, contents,
tags)")
        precondition(resultCode)
        self.fullTextTables[table.id] = table
        resultCode = self.database.execute("INSERT OR IGNORE INTO __meta_fulltext_tables(name) VALUES (\(table.id))")
        precondition(resultCode)
    }
}

Get Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private func getStatement(_ table: ValueBoxTable, key: ValueBoxKey) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, key)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.getStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT value FROM t\(table.id) WHERE key=?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.getStatements[table.id] = preparedStatement
        resultStatement =  preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: key.memory, length: key.length)
        case .int64:
            resultStatement.bind(1, number: key.getInt64(0))
    }
    
    return resultStatement
}

Get RowId Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private func getRowIdStatement(_ table: ValueBoxTable, key: ValueBoxKey) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, key)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.getRowIdStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT rowid FROM t\(table.id) WHERE key=?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.getRowIdStatements[table.id] = preparedStatement
        resultStatement =  preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: key.memory, length: key.length)
        case .int64:
            resultStatement.bind(1, number: key.getInt64(0))
    }
    
    return resultStatement
}

Range Key ASC Limit Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private func rangeKeyAscStatementLimit(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, start)
    checkTableKey(table, end)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.rangeKeyAscStatementsLimit[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC LIMIT
?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.rangeKeyAscStatementsLimit[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: start.memory, length: start.length)
            resultStatement.bind(2, data: end.memory, length: end.length)
        case .int64:
            resultStatement.bind(1, number: start.getInt64(0))
            resultStatement.bind(2, number: end.getInt64(0))
    }
    resultStatement.bind(3, number: Int32(limit))
    
    return resultStatement
}

Range Key ASC No Limit Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private func rangeKeyAscStatementNoLimit(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey) ->
    SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, start)
    checkTableKey(table, end)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.rangeKeyAscStatementsNoLimit[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC", -1,
&statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.rangeKeyAscStatementsNoLimit[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: start.memory, length: start.length)
            resultStatement.bind(2, data: end.memory, length: end.length)
        case .int64:
            resultStatement.bind(1, number: start.getInt64(0))
            resultStatement.bind(2, number: end.getInt64(0))
    }
    
    return resultStatement
}

Range Key DESC Limit Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private func rangeKeyDescStatementLimit(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    let resultStatement: SqlitePreparedStatement
    checkTableKey(table, start)
    checkTableKey(table, end)
    
    if let statement = self.rangeKeyDescStatementsLimit[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key DESC LIMIT
?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.rangeKeyDescStatementsLimit[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: start.memory, length: start.length)
            resultStatement.bind(2, data: end.memory, length: end.length)
        case .int64:
            resultStatement.bind(1, number: start.getInt64(0))
            resultStatement.bind(2, number: end.getInt64(0))
    }
    resultStatement.bind(3, number: Int32(limit))
    
    return resultStatement
}

Range Key DESC No Limit Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private func rangeKeyDescStatementNoLimit(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    let resultStatement: SqlitePreparedStatement
    checkTableKey(table, start)
    checkTableKey(table, end)
    
    if let statement = self.rangeKeyDescStatementsNoLimit[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key DESC", -1,
&statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.rangeKeyDescStatementsNoLimit[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: start.memory, length: start.length)
            resultStatement.bind(2, data: end.memory, length: end.length)
        case .int64:
            resultStatement.bind(1, number: start.getInt64(0))
            resultStatement.bind(2, number: end.getInt64(0))
    }
    
    return resultStatement
}

Range Delete Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private func rangeDeleteStatement(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    let resultStatement: SqlitePreparedStatement
    checkTableKey(table, start)
    checkTableKey(table, end)
    precondition(start <= end)
    
    if let statement = self.deleteRangeStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "DELETE FROM t\(table.id) WHERE key >= ? AND key <= ?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.deleteRangeStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: start.memory, length: start.length)
            resultStatement.bind(2, data: end.memory, length: end.length)
        case .int64:
            resultStatement.bind(1, number: start.getInt64(0))
            resultStatement.bind(2, number: end.getInt64(0))
    }
    
    return resultStatement
}

Range Value ASC Limit Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private func rangeValueAscStatementLimit(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement
{
    precondition(self.queue.isCurrent())
    checkTableKey(table, start)
    checkTableKey(table, end)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.rangeValueAscStatementsLimit[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
            // with some doubt for the where clause here
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC
LIMIT ?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.rangeValueAscStatementsLimit[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: start.memory, length: start.length)
            resultStatement.bind(2, data: end.memory, length: end.length)
        case .int64:
            resultStatement.bind(1, number: start.getInt64(0))
            resultStatement.bind(2, number: end.getInt64(0))
    }
    resultStatement.bind(3, number: Int32(limit))
    
    return resultStatement
}

Range Value ASC No Limit Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private func rangeValueAscStatementNoLimit(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, start)
    checkTableKey(table, end)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.rangeValueAscStatementsNoLimit[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
            // with some doubt for the where clause here
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key
ASC", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.rangeValueAscStatementsNoLimit[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: start.memory, length: start.length)
            resultStatement.bind(2, data: end.memory, length: end.length)
        case .int64:
            resultStatement.bind(1, number: start.getInt64(0))
            resultStatement.bind(2, number: end.getInt64(0))
    }
    
    return resultStatement
}

Range Value DESC Limit Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private func rangeValueDescStatementLimit(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, limit: Int) ->
SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, start)
    checkTableKey(table, end)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.rangeValueDescStatementsLimit[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
            // with some doubt for the where clause here
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key
DESC LIMIT ?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.rangeValueDescStatementsLimit[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: start.memory, length: start.length)
            resultStatement.bind(2, data: end.memory, length: end.length)
        case .int64:
            resultStatement.bind(1, number: start.getInt64(0))
            resultStatement.bind(2, number: end.getInt64(0))
    }
    resultStatement.bind(3, number: Int32(limit))
    
    return resultStatement
}

Range Value DESC No Limit Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private func rangeValueDescStatementNoLimit(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, start)
    checkTableKey(table, end)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.rangeValueDescStatementsNoLimit[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
      // with some doubt for the where clause here
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key
DESC", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.rangeValueDescStatementsNoLimit[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: start.memory, length: start.length)
            resultStatement.bind(2, data: end.memory, length: end.length)
        case .int64:
            resultStatement.bind(1, number: start.getInt64(0))
            resultStatement.bind(2, number: end.getInt64(0))
    }
    
    return resultStatement
}

Scan Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private func scanStatement(_ table: ValueBoxTable) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.scanStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table.id) ORDER BY key ASC", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.scanStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    return resultStatement
}

Scan Keys Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private func scanKeysStatement(_ table: ValueBoxTable) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.scanKeysStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table.id) ORDER BY key ASC", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.scanKeysStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    return resultStatement
}

Exists Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private func existsStatement(_ table: ValueBoxTable, key: ValueBoxKey) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, key)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.existsStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT rowid FROM t\(table.id) WHERE key=?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.existsStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: key.memory, length: key.length)
        case .int64:
            resultStatement.bind(1, number: key.getInt64(0))
    }
    
    return resultStatement
}

Update Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private func updateStatement(_ table: ValueBoxTable, key: ValueBoxKey, value: MemoryBuffer) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, key)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.updateStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "UPDATE t\(table.id) SET value=? WHERE key=?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.updateStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    resultStatement.bind(1, data: value.memory, length: value.length)
    switch table.keyType {
        case .binary:
            resultStatement.bind(2, data: key.memory, length: key.length)
        case .int64:
            resultStatement.bind(2, number: key.getInt64(0))
    }
    
    return resultStatement
}

InsertOrReplaceStatement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
private func insertOrReplaceStatement(_ table: SqliteValueBoxTable, key: ValueBoxKey, value: MemoryBuffer) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table.table, key)

    let resultStatement: SqlitePreparedStatement

    if table.table.keyType == .int64 || table.hasPrimaryKey {
        if let statement = self.insertOrReplacePrimaryKeyStatements[table.table.id] {
            resultStatement = statement
        } else {
            var statement: OpaquePointer? = nil
          // insert or update
            let status = sqlite3_prepare_v2(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", -1, &statement, nil)
            if status != SQLITE_OK {
                let errorText = self.database.currentError() ?? "Unknown error"
                preconditionFailure(errorText)
            }
            let preparedStatement = SqlitePreparedStatement(statement: statement)
            self.insertOrReplacePrimaryKeyStatements[table.table.id] = preparedStatement
            resultStatement = preparedStatement
        }
    } else {
        if let statement = self.insertOrReplaceIndexKeyStatements[table.table.id] {
            resultStatement = statement
        } else {
            var statement: OpaquePointer? = nil
            let status = sqlite3_prepare_v2(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?)", -1, &statement, nil)
            if status != SQLITE_OK {
                let errorText = self.database.currentError() ?? "Unknown error"
                preconditionFailure(errorText)
            }
            let preparedStatement = SqlitePreparedStatement(statement: statement)
            self.insertOrReplacePrimaryKeyStatements[table.table.id] = preparedStatement
            resultStatement = preparedStatement
        }
    }

    resultStatement.reset()

    switch table.table.keyType {
        case .binary:
            resultStatement.bind(1, data: key.memory, length: key.length)
        case .int64:
            resultStatement.bind(1, number: key.getInt64(0))
    }
    if value.length == 0 {
        resultStatement.bindNull(2)
    } else {
        resultStatement.bind(2, data: value.memory, length: value.length)
    }

    return resultStatement
}

Delete Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private func deleteStatement(_ table: ValueBoxTable, key: ValueBoxKey) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, key)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.deleteStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "DELETE FROM t\(table.id) WHERE key=?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.deleteStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: key.memory, length: key.length)
        case .int64:
            resultStatement.bind(1, number: key.getInt64(0))
    }
    
    return resultStatement
}

Move Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private func moveStatement(_ table: ValueBoxTable, from previousKey: ValueBoxKey, to updatedKey: ValueBoxKey) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    checkTableKey(table, previousKey)
    checkTableKey(table, updatedKey)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.moveStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "UPDATE t\(table.id) SET key=? WHERE key=?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.moveStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
  // with some doubt in binding here
    switch table.keyType {
        case .binary:
            resultStatement.bind(1, data: previousKey.memory, length: previousKey.length)
            resultStatement.bind(2, data: updatedKey.memory, length: updatedKey.length)
        case .int64:
            resultStatement.bind(1, number: previousKey.getInt64(0))
            resultStatement.bind(2, number: updatedKey.getInt64(0))
    }
    
    return resultStatement
}

Copy Across Tables Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private func copyStatement(fromTable: ValueBoxTable, fromKey: ValueBoxKey, toTable: ValueBoxTable, toKey: ValueBoxKey) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    let _ = checkTable(fromTable)
    let _ = checkTable(toTable)
    checkTableKey(fromTable, fromKey)
    checkTableKey(toTable, toKey)
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.copyStatements[TablePairKey(table1: fromTable.id, table2: toTable.id)] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "INSERT INTO t\(toTable.id) (key, value) SELECT ?, t\(fromTable.id).value FROM t\(fromTable.id) WHERE
t\(fromTable.id).key=?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.copyStatements[TablePairKey(table1: fromTable.id, table2: toTable.id)] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    switch toTable.keyType {
        case .binary:
            resultStatement.bind(1, data: toKey.memory, length: toKey.length)
        case .int64:
            resultStatement.bind(1, number: toKey.getInt64(0))
    }
    
    switch fromTable.keyType {
        case .binary:
            resultStatement.bind(2, data: fromKey.memory, length: fromKey.length)
        case .int64:
            resultStatement.bind(2, number: fromKey.getInt64(0))
    }
    
    return resultStatement
}

Full Text Insert Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private func fullTextInsertStatement(_ table: ValueBoxFullTextTable, collectionId: Data, itemId: Data, contents: Data, tags: Data) -> SqlitePreparedStatement {
    precondition(self.queue.isCurrent())
    
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.fullTextInsertStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "INSERT INTO ft\(table.id) (collectionId, itemId, contents, tags) VALUES(?, ?, ?, ?)", -1, &statement,
nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.fullTextInsertStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    collectionId.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(1, data: bytes, length: collectionId.count)
    }
    
    itemId.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(2, data: bytes, length: itemId.count)
    }
    
    contents.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(3, data: bytes, length: contents.count)
    }
    
    tags.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(4, data: bytes, length: tags.count)
    }
    return resultStatement
}

Full Text Delete Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private func fullTextDeleteStatement(_ table: ValueBoxFullTextTable, itemId: Data) -> SqlitePreparedStatement {
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.fullTextDeleteStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "DELETE FROM ft\(table.id) WHERE itemId=?", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.fullTextDeleteStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    itemId.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(1, data: bytes, length: itemId.count)
    }
    
    return resultStatement
}

Full Text Match Global Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private func fullTextMatchGlobalStatement(_ table: ValueBoxFullTextTable, contents: Data) -> SqlitePreparedStatement {
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.fullTextMatchGlobalStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
      // the `MATCH` clause
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT collectionId, itemId FROM ft\(table.id) WHERE ft\(table.id) MATCH 'contents:\"' || ? || '\"'",
-1, &statement, nil)
        if status != SQLITE_OK {
            self.printError()
            assertionFailure()
        }
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.fullTextMatchGlobalStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    contents.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(1, data: bytes, length: contents.count)
    }
    
    return resultStatement
}

Full Text Match Collection Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private func fullTextMatchCollectionStatement(_ table: ValueBoxFullTextTable, collectionId: Data, contents: Data) -> SqlitePreparedStatement {
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.fullTextMatchCollectionStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT collectionId, itemId FROM ft\(table.id) WHERE ft\(table.id) MATCH 'contents:\"' || ? || '\" AND
collectionId:\"' || ? || '\"'", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.fullTextMatchCollectionStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    contents.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(1, data: bytes, length: contents.count)
    }
    
    collectionId.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(2, data: bytes, length: collectionId.count)
    }
    
    return resultStatement
}

Full Text Match Collection Tags Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private func fullTextMatchCollectionTagsStatement(_ table: ValueBoxFullTextTable, collectionId: Data, contents: Data, tags: Data) -> SqlitePreparedStatement {
    let resultStatement: SqlitePreparedStatement
    
    if let statement = self.fullTextMatchCollectionTagsStatements[table.id] {
        resultStatement = statement
    } else {
        var statement: OpaquePointer? = nil
        let status = sqlite3_prepare_v2(self.database.handle, "SELECT collectionId, itemId FROM ft\(table.id) WHERE ft\(table.id) MATCH 'contents:\"' || ? || '\" AND
collectionId:\"' || ? || '\" AND tags:\"' || ? || '\"'", -1, &statement, nil)
        precondition(status == SQLITE_OK)
        let preparedStatement = SqlitePreparedStatement(statement: statement)
        self.fullTextMatchCollectionTagsStatements[table.id] = preparedStatement
        resultStatement = preparedStatement
    }
    
    resultStatement.reset()
    
    contents.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(1, data: bytes, length: contents.count)
    }
    
    collectionId.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(2, data: bytes, length: collectionId.count)
    }
    
    tags.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        resultStatement.bindText(3, data: bytes, length: tags.count)
    }
    
    return resultStatement
}

Get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public func get(_ table: ValueBoxTable, key: ValueBoxKey) -> ReadBuffer? {
    precondition(self.queue.isCurrent())
    if let _ = self.tables[table.id] {
        let statement = self.getStatement(table, key: key)
        
        var buffer: ReadBuffer?
        
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
            buffer = statement.valueAt(0)
            break
        }
        
        statement.reset()
        
        return buffer
    }
    
    withExtendedLifetime(key, {})
    
    return nil
}

Read Value Associated with Key Incrementally

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public func read(_ table: ValueBoxTable, key: ValueBoxKey, _ process: (Int, (UnsafeMutableRawPointer, Int, Int) -> Void) -> Void) {
    precondition(self.queue.isCurrent())
    if let _ = self.tables[table.id] {
        let statement = self.getRowIdStatement(table, key: key)
        
        if statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
            let rowId = statement.int64At(0)
            var blobHandle: OpaquePointer?
            sqlite3_blob_open(database.handle, "main", "t\(table.id)", "value", rowId, 0, &blobHandle)
            if let blobHandle = blobHandle {
                let length = sqlite3_blob_bytes(blobHandle)
                process(Int(length), { buffer, offset, length in
                    sqlite3_blob_read(blobHandle, buffer, Int32(length), Int32(offset))
                })
                sqlite3_blob_close(blobHandle)
            }
        }
        statement.reset()
    }
}

Read and Write Value Associated with Key Incrementally

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public func readWrite(_ table: ValueBoxTable, key: ValueBoxKey, _ process: (Int, (UnsafeMutableRawPointer, Int, Int) -> Void, (UnsafeRawPointer, Int, Int) -> Void)
-> Void) {
    if let _ = self.tables[table.id] {
        let statement = self.getRowIdStatement(table, key: key)
        
        if statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
            let rowId = statement.int64At(0)
            var blobHandle: OpaquePointer?
            sqlite3_blob_open(database.handle, "main", "t\(table.id)", "value", rowId, 1, &blobHandle)
            if let blobHandle = blobHandle {
                let length = sqlite3_blob_bytes(blobHandle)
                process(Int(length), { buffer, offset, length in
                    sqlite3_blob_read(blobHandle, buffer, Int32(length), Int32(offset))
                }, { buffer, offset, length in
                    sqlite3_blob_write(blobHandle, buffer, Int32(length), Int32(offset))
                })
                sqlite3_blob_close(blobHandle)
            }
        }
        statement.reset()
    }
}

Exists

1
2
3
4
5
6
7
public func exists(_ table: ValueBoxTable, key: ValueBoxKey) -> Bool {
    precondition(self.queue.isCurrent())
    if let _ = self.get(table, key: key) {
        return true
    }
    return false
}

Evalute the Records with key in start … end range until the Evaluation Expression values return false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> Bool, limit: Int) {
    precondition(self.queue.isCurrent())
    if start == end {
        return
    }

    if let _ = self.tables[table.id] {
        let statement: SqlitePreparedStatement

        switch table.keyType {
            case .binary:
                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)
                    }
                }

                while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
                    let key = statement.keyAt(0)
                    let value = statement.valueAt(1)

                    if !values(key, value) {
                        break
                    }
                }

                statement.reset()
            case .int64:
                if start.reversed < end.reversed {
                    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)
                    }
                }

                while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
                    let key = statement.int64KeyAt(0)
                    let value = statement.valueAt(1)

                    if !values(key, value) {
                        break
                    }
                }

                statement.reset()
        }
    }

    withExtendedLifetime(start, {})
    withExtendedLifetime(end, {})
}

Evalute the Records with key in start … end range depend on the Evaluation Expression values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> ValueBoxFilterResult, limit: Int) {
    var currentStart = start
    var acceptedCount = 0
    while true {
        if limit > 0 && acceptedCount >= limit {
            break
        }
        var hadStop = false
        var lastKey: ValueBoxKey?
        self.range(table, start: currentStart, end: end, values: { key, value in
            lastKey = key
            let result = values(key, value)
            switch result {
            case .accept:
                acceptedCount += 1
                if limit > 0 && acceptedCount >= limit {
                    hadStop = true
                    return false
                } else {
                    return true
                }
            case .skip:
                return true
            case .stop:
                hadStop = true
                return false
            }
        }, limit: limit)
        if let lastKey = lastKey {
            currentStart = lastKey
        } else {
            break
        }
        if hadStop {
            break
        }
    }
}

Evalute the Records with key in start … end range depend on the Evaluation Expression keys

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> ValueBoxFilterResult, limit: Int) {
    var currentStart = start
    var acceptedCount = 0
    while true {
        if limit > 0 && acceptedCount >= limit {
            break
        }
        var hadStop = false
        var lastKey: ValueBoxKey?
        self.range(table, start: currentStart, end: end, keys: { key in
            lastKey = key
            let result = keys(key)
            switch result {
            case .accept:
                acceptedCount += 1
                return true
            case .skip:
                return true
            case .stop:
                hadStop = true
                return false
            }
        }, limit: limit)
        if let lastKey = lastKey {
            currentStart = lastKey
        } else {
            break
        }
        if hadStop {
            break
        }
    }
}

Evalute the Records with key in start … end range until the Evaluation Expression keys return false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> Bool, limit: Int) {
    precondition(self.queue.isCurrent())
    if let _ = self.tables[table.id] {
        let statement: SqlitePreparedStatement

        switch table.keyType {
            case .binary:
                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)
                    }
                }

                while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
                    let key = statement.keyAt(0)

                    if !keys(key) {
                        break
                    }
                }

                statement.reset()
            case .int64:
                if start.reversed < end.reversed {
                    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)
                    }
                }

                while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
                    let key = statement.int64KeyAt(0)

                    if !keys(key) {
                        break
                    }
                }

                statement.reset()
        }
    }

    withExtendedLifetime(start, {})
    withExtendedLifetime(end, {})
}

Evalute the Records until the Evaluation Expression values return false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public func scan(_ table: ValueBoxTable, values: (ValueBoxKey, ReadBuffer) -> Bool) {
    precondition(self.queue.isCurrent())
    
    if let _ = self.tables[table.id] {
        let statement: SqlitePreparedStatement = self.scanStatement(table)
        
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
            let key = statement.keyAt(0)
            let value = statement.valueAt(1)
            
            if !values(key, value) {
                break
            }
        }
        
        statement.reset()
    }
}

Evalute the Records until the Evaluation Expression keys return false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public func scan(_ table: ValueBoxTable, keys: (ValueBoxKey) -> Bool) {
    precondition(self.queue.isCurrent())
    
    if let _ = self.tables[table.id] {
        let statement: SqlitePreparedStatement = self.scanKeysStatement(table)
        
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
            let key = statement.keyAt(0)
            
            if !keys(key) {
                break
            }
        }
        
        statement.reset()
    }
}

Evalute the Records with key represented as Int64 until the Evaluation Expression values return false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public func scanInt64(_ table: ValueBoxTable, values: (Int64, ReadBuffer) -> Bool) {
    precondition(self.queue.isCurrent())
    
    if let _ = self.tables[table.id] {
        let statement: SqlitePreparedStatement = self.scanStatement(table)
        
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
            let key = statement.int64KeyValueAt(0)
            let value = statement.valueAt(1)
            
            if !values(key, value) {
                break
            }
        }
        
        statement.reset()
    }
}

Evalute the Records with key represented as Int64 until the Evaluation Expression keys return false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public func scanInt64(_ table: ValueBoxTable, keys: (Int64) -> Bool) {
    precondition(self.queue.isCurrent())
    
    if let _ = self.tables[table.id] {
        let statement: SqlitePreparedStatement = self.scanKeysStatement(table)
        
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
            let key = statement.int64KeyValueAt(0)
            
            if !keys(key) {
                break
            }
        }
        
        statement.reset()
    }
}

Insert or Update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public func set(_ table: ValueBoxTable, key: ValueBoxKey, value: MemoryBuffer) {
    precondition(self.queue.isCurrent())
    let sqliteTable = self.checkTable(table)
    
    if sqliteTable.hasPrimaryKey {
        let statement = self.insertOrReplaceStatement(sqliteTable, key: key, value: value)
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        }
        statement.reset()
    } else {
        if self.exists(table, key: key) {
            let statement = self.updateStatement(table, key: key, value: value)
            while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
            }
            statement.reset()
        } else {
            let statement = self.insertOrReplaceStatement(sqliteTable, key: key, value: value)
            while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
            }
            statement.reset()
        }
    }
}

Remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public func remove(_ table: ValueBoxTable, key: ValueBoxKey, secure: Bool) {
    precondition(self.queue.isCurrent())
    if let _ = self.tables[table.id] {
        if secure != self.secureDeleteEnabled {
            self.secureDeleteEnabled = secure
            let result = database.execute("PRAGMA secure_delete=\(secure ? 1 : 0)")
            precondition(result)
        }
        
        let statement = self.deleteStatement(table, key: key)
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        }
        statement.reset()
    }
}

Remove Range

1
2
3
4
5
6
7
8
9
public func removeRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey) {
    precondition(self.queue.isCurrent())
    if let _ = self.tables[table.id] {
        let statement = self.rangeDeleteStatement(table, start: min(start, end), end: max(start, end))
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        }
        statement.reset()
    }
}

Move Key from Previous to Updated

1
2
3
4
5
6
7
8
9
public func move(_ table: ValueBoxTable, from previousKey: ValueBoxKey, to updatedKey: ValueBoxKey) {
    precondition(self.queue.isCurrent())
    if let _ = self.tables[table.id] {
        let statement = self.moveStatement(table, from: previousKey, to: updatedKey)
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        }
        statement.reset()
    }
}

Copy Accorss Tables

1
2
3
4
5
6
7
8
9
public func copy(fromTable: ValueBoxTable, fromKey: ValueBoxKey, toTable: ValueBoxTable, toKey: ValueBoxKey) {
    precondition(self.queue.isCurrent())
    if let _ = self.tables[fromTable.id] {
        let statement = self.copyStatement(fromTable: fromTable, fromKey: fromKey, toTable: toTable, toKey: toKey)
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        }
        statement.reset()
    }
}

Rename Table

1
2
3
4
5
6
7
8
public func renameTable(_ table: ValueBoxTable, to toTable: ValueBoxTable) {
    let sqliteTable = self.checkTable(table)
    let resultCode = database.execute("ALTER TABLE t\(table.id) RENAME TO t\(toTable.id)")
    precondition(resultCode)
    self.tables[toTable.id] = SqliteValueBoxTable(table: ValueBoxTable(id: toTable.id, keyType: sqliteTable.table.keyType, compactValuesOnCreation:
sqliteTable.table.compactValuesOnCreation), hasPrimaryKey: sqliteTable.hasPrimaryKey)
    self.tables.removeValue(forKey: table.id)
}

Full Text Match

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public func fullTextMatch(_ table: ValueBoxFullTextTable, collectionId: String?, query: String, tags: String?, values: (String, String) -> Bool) {
    if let _ = self.fullTextTables[table.id] {
        guard let queryData = query.data(using: .utf8) else {
            return
        }

        var statement: SqlitePreparedStatement?
        if let collectionId = collectionId {
            if let collectionIdData = collectionId.data(using: .utf8) {
                if let tags = tags {
                    if let tagsData = tags.data(using: .utf8) {
                        statement = self.fullTextMatchCollectionTagsStatement(table, collectionId: collectionIdData, contents: queryData, tags: tagsData)
                    }
                } else {
                    statement = self.fullTextMatchCollectionStatement(table, collectionId: collectionIdData, contents: queryData)
                }
            }
        } else {
            statement = self.fullTextMatchGlobalStatement(table, contents: queryData)
        }

        if let statement = statement {
            while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
                let resultCollectionId = statement.stringAt(0)
                let resultItemId = statement.stringAt(1)

                if let resultCollectionId = resultCollectionId, let resultItemId = resultItemId {
                    if !values(resultCollectionId, resultItemId) {
                        break
                    }
                } else {
                    assertionFailure()
                }
            }

            statement.reset()
        }
    }
}

Full Text Set

1
2
3
4
5
6
7
8
9
10
11
12
public func fullTextSet(_ table: ValueBoxFullTextTable, collectionId: String, itemId: String, contents: String, tags: String) {
        self.checkFullTextTable(table)
        
        guard let collectionIdData = collectionId.data(using: .utf8), let itemIdData = itemId.data(using: .utf8), let contentsData = contents.data(using: .utf8), let tagsData = tags.data(using: .utf8) else {
            return
        }
        
        let statement = self.fullTextInsertStatement(table, collectionId: collectionIdData, itemId: itemIdData, contents: contentsData, tags: tagsData)
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        }
        statement.reset()
    }

Full Text Remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public func fullTextRemove(_ table: ValueBoxFullTextTable, itemId: String, secure: Bool) {
    if let _ = self.fullTextTables[table.id] {
        if secure != self.secureDeleteEnabled {
            self.secureDeleteEnabled = secure
            let result = database.execute("PRAGMA secure_delete=\(secure ? 1 : 0)")
            precondition(result)
        }
        
        guard let itemIdData = itemId.data(using: .utf8) else {
            return
        }
        
        let statement = self.fullTextDeleteStatement(table, itemId: itemIdData)
        while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        }
        statement.reset()
    }
}

Count Records with key in start … end range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public func count(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey) -> Int {
    let _ = self.checkTable(table)
    
    var statementImpl: OpaquePointer? = nil
    let status =ge sqlite3_prepare_v2(self.database.handle, "SELECT COUNT(*) FROM t\(table.id) WHERE key > ? AND key < ?", -1, &statementImpl, nil)
    precondition(status == SQLITE_OK)
    let statement = SqlitePreparedStatement(statement: statementImpl)
    switch table.keyType {
        case .binary:
            statement.bind(1, data: start.memory, length: start.length)
        case .int64:
            statement.bind(1, number: start.getInt64(0))
    }
    switch table.keyType {
        case .binary:
            statement.bind(2, data: end.memory, length: end.length)
        case .int64:
            statement.bind(2, number: end.getInt64(0))
    }
    
    var result = 0
    while statement.step(handle: database.handle, true, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        let value = statement.int32At(0)
        result = Int(value)
    }
    statement.reset()
    statement.destroy()
    return result
}

Count Records

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public func count(_ table: ValueBoxTable) -> Int {
    let _ = self.checkTable(table)
    
    var statementImpl: OpaquePointer? = nil
    let status = sqlite3_prepare_v2(self.database.handle, "SELECT COUNT(*) FROM t\(table.id)", -1, &statementImpl, nil)
    precondition(status == SQLITE_OK)
    let statement = SqlitePreparedStatement(statement: statementImpl)
    
    var result = 0
    while statement.step(handle: database.handle, true, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
        let value = statement.int32At(0)
        result = Int(value)
    }
    statement.reset()
    statement.destroy()
    return result
}

Clear Statements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    private func clearStatements() {
        precondition(self.queue.isCurrent())
        for (_, statement) in self.getStatements {
            statement.destroy()
        }
        self.getStatements.removeAll()
        
        for (_, statement) in self.getRowIdStatements {
            statement.destroy()
        }
        self.getRowIdStatements.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.deleteRangeStatements {
            statement.destroy()
        }
        self.deleteRangeStatements.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.scanStatements {
            statement.destroy()
        }
        self.scanStatements.removeAll()
        
        for (_, statement) in self.scanKeysStatements {
            statement.destroy()
        }
        self.scanKeysStatements.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.insertOrReplaceIndexKeyStatements {
            statement.destroy()
        }
        self.insertOrReplaceIndexKeyStatements.removeAll()
        
        for (_, statement) in self.insertOrReplacePrimaryKeyStatements {
            statement.destroy()
        }
        self.insertOrReplacePrimaryKeyStatements.removeAll()
        
        for (_, statement) in self.deleteStatements {
            statement.destroy()
        }
        self.deleteStatements.removeAll()
        
        for (_, statement) in self.moveStatements {
            statement.destroy()
        }
        self.moveStatements.removeAll()
        
        for (_, statement) in self.copyStatements {
            statement.destroy()
        }
        self.copyStatements.removeAll()
        
        for (_, statement) in self.fullTextInsertStatements {
            statement.destroy()
        }
        self.fullTextInsertStatements.removeAll()
        
        for (_, statement) in self.fullTextDeleteStatements {
            statement.destroy()
        }
        self.fullTextDeleteStatements.removeAll()
        
        for (_, statement) in self.fullTextMatchGlobalStatements {
            statement.destroy()
        }
        self.fullTextMatchGlobalStatements.removeAll()
        
        for (_, statement) in self.fullTextMatchCollectionStatements {
            statement.destroy()
        }
        self.fullTextMatchCollectionStatements.removeAll()
        
        for (_, statement) in self.fullTextMatchCollectionTagsStatements {
            statement.destroy()
        }
        self.fullTextMatchCollectionTagsStatements.removeAll()
    }

SQLCipher export

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public func exportEncrypted(to exportBasePath: String, encryptionParameters: ValueBoxEncryptionParameters) {
        self.exportEncrypted(database: self.database, to: exportBasePath, encryptionParameters: encryptionParameters)
    }

private func exportEncrypted(database: Database, to exportBasePath: String, encryptionParameters: ValueBoxEncryptionParameters) {
    let _ = try? FileManager.default.createDirectory(atPath: exportBasePath, withIntermediateDirectories: true, attributes: nil)
    let exportFilePath = "\(exportBasePath)/db_sqlite"
    
    let hexKey = hexString(encryptionParameters.key.data + encryptionParameters.salt.data)
    
    precondition(encryptionParameters.salt.data.count == 16)
    precondition(encryptionParameters.key.data.count == 32)
    
    var resultCode = database.execute("ATTACH DATABASE '\(exportFilePath)' AS encrypted KEY \"x'\(hexKey)'\"")
    assert(resultCode)
    resultCode = database.execute("SELECT sqlcipher_export('encrypted')")
    assert(resultCode)
    let userVersion = self.getUserVersion(database)
    resultCode = database.execute("PRAGMA encrypted.user_version=\(userVersion)")
    resultCode = database.execute("DETACH DATABASE encrypted")
    assert(resultCode)
}

Rencrypte in place

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private func reencryptInPlace(database: Database, encryptionParameters: ValueBoxEncryptionParameters) -> Database {
    if self.isReadOnly {
        preconditionFailure()
    }
    
    let targetPath = self.basePath + "/db_export"
    let _ = try? FileManager.default.removeItem(atPath: targetPath)
    
  // export encrypted
    self.exportEncrypted(database: database, to: targetPath, encryptionParameters: encryptionParameters)
    
    for name in dabaseFileNames {
        let _ = try? FileManager.default.removeItem(atPath: self.basePath + "/\(name)")
        let _ = try? FileManager.default.moveItem(atPath: targetPath + "/\(name)", toPath: self.basePath + "/\(name)")
    }
    let _ = try? FileManager.default.removeItem(atPath: targetPath)
    
    let updatedDatabase = Database(self.databasePath, readOnly: false)!
    
    var resultCode = updatedDatabase.execute("PRAGMA cipher_plaintext_header_size=32")
    assert(resultCode)
    resultCode = updatedDatabase.execute("PRAGMA cipher_default_plaintext_header_size=32")
    assert(resultCode)
    
    let hexKey = hexString(encryptionParameters.key.data + encryptionParameters.salt.data)
    
    resultCode = updatedDatabase.execute("PRAGMA key=\"x'\(hexKey)'\"")
    assert(resultCode)
    
    return updatedDatabase
}

data hexString

1
2
3
4
5
6
7
8
9
10
11
private func hexString(_ data: Data) -> String {
    let hexString = NSMutableString()
    data.withUnsafeBytes { rawBytes -> Void in
        let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        for i in 0 ..< data.count {
            hexString.appendFormat("%02x", UInt(bytes.advanced(by: i).pointee))
        }
    }
    
    return hexString as String
}
This post is licensed under CC BY 4.0 by the author.