SQLite的使用三

這是SQLite系列文章第三篇。

  1. SQLite的使用一
  2. SQLite的使用二
  3. 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_textsqlite3_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.swiftFMDB封裝了 SQLite,提供了 Ojbective-C、Swift接口。

上一篇:SQLite的使用二

Demo名稱:SQLite
源碼地址:https://github.com/pro648/BasicDemos-iOS/tree/master/SQLite

參考資料:

  1. SQLite With Swift Tutorial: Getting Started
  2. Swift 4 + SQLite when inserting
  3. Transaction Control Language

歡迎更多指針:https://github.com/pro648/tips
本文地址:https://github.com/pro648/tips/blob/master/sources/SQLite的使用三.md

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章