這是SQLite系列文章第三篇。
SQLite的使用一、SQLite的使用二這兩篇文章主要介紹了 SQLite 的基本語句。這篇文章將介紹 Swift 中如何使用 SQLite,包含以下操作:
- 創建數據庫。
- 創建表。
- 插入。
- 查詢。
- 更新。
- 刪除。
- 關閉。
完成後運行如下:
1. 創建數據庫
首先,創建數據庫:
private func openDatabase() -> OpaquePointer? {
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let dbPath = documentsPath?.appendingPathComponent(dbName).path
var db: OpaquePointer?
if sqlite3_open(dbPath, &db) == SQLITE_OK {
return db
} else {
defer {
if db != nil {
sqlite3_close(db)
}
}
let errorMes = String(cString: sqlite3_errmsg(db))
print("Unable to open database \(errorMes)")
return nil
}
}
sqlite3_open(const char *filename, sqlite3 **ppDb)
函數創建數據庫(如果不存在)並建立連接。即使發生了錯誤,database connection 也通過 *ppDb 返回。只有當 SQLite 無法爲對象分配內存時,纔會將NULL寫入 *ppDb,否則統一將 sqlite3 對象寫入 *ppDb。當建立連接成功時,返回SQLITE_OK
;反之,返回錯誤原因。使用sqlite3_errmsg()
函數獲取具體錯誤原因。
OpaquePointer
是 Swift 類型 C pointer。
無論建立連接的過程中是否發生錯誤,使用 database connection 關聯的指針均需使用sqlite3_close()
函數釋放。
很多 SQLite 函數都會返回 Int32 的狀態碼,一般爲常量。例如,SQLITE_OK
值爲0
。文檔中可以查到所有狀態碼。
2. 創建表
這一部分使用代碼創建表。表名稱爲PersonInfo
,包含五列。id 列爲主鍵,integer 類型,值自增;name列爲 char(255),不可爲空。age 列爲 char(255),不可爲空。最後兩列記錄時間,默認記錄當前時間。如下所示:
private func createTable(tbName: String) {
let queryString = """
CREATE TABLE IF NOT EXISTS \(tbName)
( id INTEGER PRIMARY KEY AUTOINCREMENT,
name char(255) NOT NULL,
age char(255) NOT NULL,
createTime timestamp TimeStamp NOT NULL DEFAULT (datetime('now','localtime')),
updateTime timestamp TimeStamp NOT NULL DEFAULT (datetime('now','localtime'))
);
"""
var createTableStatement: OpaquePointer?
if sqlite3_prepare_v2(db, queryString, -1, &createTableStatement, nil) == SQLITE_OK {
if sqlite3_step(createTableStatement) == SQLITE_DONE {
print("\(tableName) table created")
} else {
print("\(tableName) is not created")
}
} else {
print("Create table statement is not prepared")
}
sqlite3_finalize(createTableStatement)
}
要執行的 SQL 語句,必須先使用sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail)
函數編譯爲字節碼程序。也就是,sqlite3_prepare_v2()
函數是語句對象的構造函數。該函數各參數含義如下:
- db:之前調用
sqlite3_open()
函數獲取的 database connection。 - zSql:使用UTF-8編碼的執行語句。
- nByte:如果爲負,則讀取zSql到第一個終止符;如果爲正,則表示要從zSql讀取的字節數;如果爲零,則不生成語句。如果調用者知道zSql字符串以nul終止,直接傳遞包含nul終止符的字節數可以提高性能。
- ppStmt:指向編譯好的 prepared_statement,可以使用
sqlite3_step()
函數執行。 - pzTail:如果pzTail不爲NULL,則 *pzTail 指向zSql中第一個SQL語句後的第一個字節。
sqlite3_prepare_v2()
僅編譯一條語句,因此, *pzTail指向未編譯的內容。
編譯成功後返回SQLITE_OK
;反之,返回錯誤碼。
sqlite3_step()
函數執行編譯後的語句。
int sqlite3_finalize(sqlite3_stmt *pStmp);
函數用於刪除prepared_statement。如果最近一次執行未出現錯誤,或從未執行過,則返回SQLITE_OK
;如果最近執行失敗,則返回相應錯誤碼。sqlite3_finalize()
函數可以在任何時刻調用,即使在NULL指針上調用也不會產生危害。但如果沒有調用,則會產生內存泄漏。調用之後再次使用prepared_statement會產生無法預期的後果。
也可以在外部創建數據庫,導入後使用。
3. 插入數據
之前已經使用sqlite3_prepare_v2()
函數編譯語句,?
符號標記執行時才提供具體值。這樣可以提前編譯、提高性能,也可以複用編譯語句。
如下所示:
func insert(contact: Contact) {
let insertString = """
INSERT INTO \(tableName) (name, age) VALUES(?, ?);
"""
var insertStatement: OpaquePointer?
if sqlite3_prepare_v2(db, insertString, -1, &insertStatement, nil) == SQLITE_OK {
sqlite3_bind_text(insertStatement, 1, strdup(contact.name), -1, free)
sqlite3_bind_text(insertStatement, 2, strdup(contact.age), -1, free)
if sqlite3_step(insertStatement) == SQLITE_DONE {
print("Successfully inserted row.")
} else {
print("Could not insert row.")
}
} else {
print("INSERT statement is not prepared.")
}
sqlite3_finalize(insertStatement)
}
與sqlite3_bind_text()
函數功能類似的還有以下函數:
int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64,
void(*)(void*));
int sqlite3_bind_double(sqlite3_stmt*, int, double);
int sqlite3_bind_int(sqlite3_stmt*, int, int);
int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
int sqlite3_bind_null(sqlite3_stmt*, int);
int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*));
int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*));
int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64,
void(*)(void*), unsigned char encoding);
int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
int sqlite3_bind_pointer(sqlite3_stmt*, int, void*, const char*,void(*)(void*));
int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64);
sqlite3_bind_*()
第一個參數是指向sqlite3_stmt
對象的指針。
第二個參數是要設置的SQL語句參數索引,最左側索引爲一。
第三個參數是要設置的值。如果爲sqlite3_bind_text()
、sqlite3_bind_text16()
和sqlite3_bind_blob()
設置NULL,則自動忽略第四個參數,且和sqlite3_bind_null()
函數效果一致。
第四個參數爲參數字節數,非字符數。sqlite3_bind_text()
和sqlite3_bind_text16()
函數第四個參數爲負時,表示字符長度至第一個終止符。sqlite3_bind_blob()
函數第四個參數爲負時,結果將不可預期。sqlite3_bind_text
、sqlite3_bind_text16()
和sqlite3_bind_text64()
函數第四個參數如果非負,則該參數必須是字符以NUL終止情況下其字節偏移量。如果NUL字符字節偏移量小於第四個參數的值,則結果字符串值將包含嵌入的NUL。涉及帶有NUL字符串的表達式結果是不確定的。
BLOB和字符綁定接口的第五個參數是析構函數。用於在 SQLite 處理完畢 BLOB、字符串後,處理 BLOB、字符串。即使綁定失敗,也會調用析構函數。
4. 查詢
使用以下函數查詢數據庫:
func query(uniqueId: Int32) -> ContactDetail? {
let queryString = """
SELECT id, name, age, createTime, updateTime FROM \(tableName)
WHERE ID = '\(uniqueId)';
"""
var queryStatement: OpaquePointer?
if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK {
if sqlite3_step(queryStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryStatement, 0)
let queryResult2 = sqlite3_column_text(queryStatement, 1)
let queryResult3 = sqlite3_column_text(queryStatement, 2)
let queryResult4 = sqlite3_column_text(queryStatement, 3)
let queryResult5 = sqlite3_column_text(queryStatement, 4)
let name = String(cString: queryResult2!)
let age = String(cString: queryResult3!)
let insertDate = String(cString: queryResult4!)
let updateDate = String(cString: queryResult5!)
let detail = ContactDetail(id: id, name: name, age: age, insertDate: insertDate, updateDate: updateDate)
sqlite3_finalize(queryStatement)
return detail
} else {
print("Could not QUERY")
}
} else {
print("QUERY is not prepared")
}
sqlite3_finalize(queryStatement)
return nil;
}
通過sqlite3_column_int()
和sqlite3_column_text()
函數提取查詢結果中的值。這裏索引從零開始。查詢語句也可以使用SELECT * FROM \(tableName) WHERE ID= '\(uniqueId)'
,但列出要查詢的id、name、age等是一種更好的習慣,這樣表後續發生變化時不會影響查詢結果。
如果要查詢所有記錄,代碼如下:
func queryAllContacts() -> [Contact] {
let queryAllString = """
SELECT id, name, age FROM \(tableName);
"""
let retval = NSMutableArray.init()
var queryAllStatement: OpaquePointer?
if sqlite3_prepare_v2(db, queryAllString, -1, &queryAllStatement, nil) == SQLITE_OK {
while sqlite3_step(queryAllStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryAllStatement, 0)
let queryResult2 = sqlite3_column_text(queryAllStatement, 1)
let queryResult3 = sqlite3_column_text(queryAllStatement, 2)
let name = String(cString: queryResult2!)
let age = String(cString: queryResult3!)
let contact = Contact(id: id, name: name, age: age)
retval.add(contact)
}
} else {
print("SELECT ALL is not prepared.")
}
sqlite3_finalize(queryAllStatement)
return retval as! [Contact]
}
5. 更新
使用以下函數更新數據庫:
func update(contact: Contact) {
let updateString = """
UPDATE \(tableName) SET name = '\(contact.name)', age = '\(contact.age)', updateTime = datetime('now','localtime')
WHERE id = '\(contact.id)';
"""
var updateStatement: OpaquePointer?
if sqlite3_prepare_v2(db, updateString, -1, &updateStatement, nil) == SQLITE_OK {
if sqlite3_step(updateStatement) == SQLITE_DONE {
print("Successfully update row \(contact.name).")
} else {
print("Couldn't update row \(contact.name).")
}
} else {
print("UPDATE is not prepared.")
}
sqlite3_finalize(updateStatement)
}
這裏與前面的流程一致,也是prepare、step、finalize。
這裏更新數據後,會同步更新 updateTime。
6. 刪除
使用以下函數刪除記錄:
func delete(contact: Contact) {
let deleteString = """
DELETE FROM \(tableName) WHERE id = '\(contact.id)';
"""
var deleteStatement: OpaquePointer?
if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK {
if sqlite3_step(deleteStatement) == SQLITE_DONE {
print("Successfully delete row \(contact.name).")
} else {
print("Couldn't delete row \(contact.name).")
}
} else {
print("DELETE is not prepared.")
}
sqlite3_finalize(deleteStatement)
}
7. 關閉
不再需要數據庫連接後,需手動將其關閉。
sqlite3_close()
和sqlite3_close_v2()
是sqlite3對象的析構函數。如果sqlite3對象已成功銷燬,且所有資源都已釋放,則該析構函數返回SQLITE_OK
。
當 database connection 有未 finalize 的 prepare statement,或未完成的sqlite3_backup
對象時,調用sqlite3_close()
將返回SQLITE_BUSY
,且數據庫未關閉;如果調用sqlite3_close_v2()
,則 database connection 將成爲野指針,等最後一個 prepare statement、sqlite3_back
完成後,自動釋放 database connection。sqlite3_close_v2()
用於具有垃圾回收、任意順序調用析構函數的宿主語言。
如果事務正在進行時銷燬了 sqlite3 對象,則該事務將自動回滾。
調用sqlite3_close()
函數如下:
deinit {
sqlite3_close(db)
}
現在,已經介紹了 SQLite 常見語句和在 Swift 中用法,但這裏使用的 C API,SQLite.swift和FMDB封裝了 SQLite,提供了 Ojbective-C、Swift接口。
上一篇:SQLite的使用二
Demo名稱:SQLite
源碼地址:https://github.com/pro648/BasicDemos-iOS/tree/master/SQLite
參考資料:
- SQLite With Swift Tutorial: Getting Started
- Swift 4 + SQLite when inserting
- Transaction Control Language
歡迎更多指針:https://github.com/pro648/tips
本文地址:https://github.com/pro648/tips/blob/master/sources/SQLite的使用三.md