2017-10-20 35 views
4

首先,這些都是我的功能:斯威夫特sqlite3的語法和綁定

  1. 插入功能

    func insert(book : Book) throws -> Bool { 
        var insertPointer: OpaquePointer? = nil 
        let query = "INSERT INTO BOOK (bookName, bookAuthor, bookDesc, bookDate, bookImg, createdBy) VALUES (?, ?, ?, ?, ?, ?)" 
    
        defer{ 
         sqlite3_finalize(insertPointer) 
        } 
    
        if sqlite3_prepare_v2(db, query, -1, &insertPointer, nil) == SQLITE_OK { 
         sqlite3_bind_text(insertPointer, 1, book.bookTitle, -1, nil) 
         sqlite3_bind_text(insertPointer, 2, book.bookAuthor, -1, nil) 
         sqlite3_bind_text(insertPointer, 3, book.bookDesc, -1, nil) 
         //sqlite3_bind_date(insertPointer, 4, book.bookDate,nil) 
         //sqlite3_bind_image(insertPointer, 5, book.bookImg, -1, nil) 
         sqlite3_bind_text(insertPointer, 6, book.createdBy, -1, nil) 
    
         guard sqlite3_step(insertPointer) == SQLITE_DONE else { 
          throw SQLiteError.Step(message: errorMessage) 
         } 
        } else { 
         throw SQLiteError.Prepare(message: errorMessage) 
        } 
    
        return true 
    } 
    
  2. 更新功能

    func update(book : Book) throws -> Bool { 
        var updatePointer: OpaquePointer? = nil 
        var query = "UPDATE Book SET bookName = ?, bookAuthor = ?, bookDesc = ?, bookDate = ?, bookImg = ?, createdBy = ?, WHERE bookId = ?" 
    
        defer{ 
         sqlite3_finalize(updatePointer) 
        } 
    
        if sqlite3_prepare_v2(db, query, -1, &updatePointer, nil) == SQLITE_OK { 
    
         sqlite3_bind_text(updatePointer, 2, book.bookAuthor, -1, nil) 
         sqlite3_bind_text(updatePointer, 3, book.bookDesc, -1, nil) 
         //sqlite3_bind_date(updatePointer, 4, book.bookDate,nil) 
         //sqlite3_bind_image(updatePointer, 5, book.bookImg, -1, nil) 
         sqlite3_bind_text(updatePointer, 6, book.createdBy, -1, nil) 
         sqlite3_bind_text(updatePointer, 7, book.bookId, -1, nil) 
         guard sqlite3_step(updatePointer) == SQLITE_DONE else { 
          throw SQLiteError.Step(message: errorMessage) 
         } 
        } else { 
         throw SQLiteError.Prepare(message: errorMessage) 
        } 
    
        return true 
    } 
    
  3. 刪除功能

    func delete(book : Book) throws -> Bool { 
        var deletePointer: OpaquePointer? = nil 
        var query = "DELETE FROM Book WHERE bookId = ?" 
    
        defer{ 
         sqlite3_finalize(deletePointer) 
        } 
    
        if sqlite3_prepare_v2(db, query, -1, &deletePointer, nil) == SQLITE_OK { 
         sqlite3_bind_text(updatePointer, 1, book.bookId, -1, nil) 
         guard sqlite3_step(deletePointer) == SQLITE_DONE else { 
          throw SQLiteError.Step(message: errorMessage) 
         } 
        } else { 
         throw SQLiteError.Prepare(message: errorMessage) 
        } 
    
        return true 
    } 
    

而且我有一個Book類是這樣的:

class Book{ 
    var bookId : Int 
    var bookImg : Data 
    var bookTitle : String 
    var bookAuthor : String 
    var bookDesc : String 
    var bookDate : Date 
    var createdBy : String 

    init(bookId : Int, bookImg : Data, bookTitle : String, bookAuthor : String, bookDesc : String, bookDate : Date, createdBy : String){ 
     self.bookId = bookId 
     self.bookImg = bookImg 
     self.bookTitle = bookTitle 
     self.bookAuthor = bookAuthor 
     self.bookDesc = bookDesc 
     self.bookDate = bookDate 
     self.createdBy = createdBy 
    } 
} 

我是新來的斯威夫特和SQLite。我的問題是:

  1. 我是否正確地使用參數綁定?

  2. 如何將DataDate類型綁定到SQLite查詢中? (上面代碼中的註釋行)

任何幫助將不勝感激!

+0

SQLiteError類型來自哪裏? errorMessage定義在哪裏? –

+0

SQLiteError是枚舉在同一個文件中定義的。 errorMessage與類中的函數(不是Book類)一起定義。 – Jeff

回答

5

你問:

  1. 我說得對不對與參數綁定做什麼呢?

重要。

  • 當綁定字符串,它可能是謹慎使用SQLITE_TRANSIENT作爲最後一個參數來sqlite3_bind_textsqlite3_bind_blob,這裏定義的:

    internal let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self) 
    internal let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) 
    
  • 當綁定bookId,要使用sqlite3_bind_int64

  • delete你指的是updatePointer。將其更改爲deletePointer

  • 您應該正在檢查這些sqlite3_bind_xxx返回碼,並且如果它們不是SQLITE_OK也會發出錯誤。

你接着問:

  • 如何綁定數據和日期型到SQLite的查詢?(在上面的代碼中的註釋行)
  • 重新日期型,SQLite不具有天然日期類型(見http://sqlite.org/datatype3.html)。或者:

    • 如果你不需要毫秒,使用ISODateFormatter建立一個字符串,並綁定串;
    • 如果您需要毫秒,使用DateFormatteryyyy-MM-dd'T'HH:mm:ss.SSSXdateFormat,一個localeLocale(identifier: "en_US_POSIX"),以及TimeZone(secondsFromGMT: 0)一個timeZone,再存儲和檢索日期爲字符串,並將其轉換;或
    • 使用timeIntervalSince1970Date,並將其插入爲sqlite3_bind_double

    以前的字符串替代方法最容易使用,並且在第三方工具中直觀地檢查數據庫時很容易使用。 timeIntervalSince1970可以說是更有效一些,但它意味着如果查看第三方SQLite工具中的列,則需要使用unixepoch將double轉換爲可理解的日期,這可能有點麻煩。這是效率與可用性的平衡。

    重新編號Data,插入使用sqlite3_bind_blob


    一對夫婦最終未成年人意見:

    • sqlite3_prepare_v2之前,您都推遲sqlite3_finalize。你應該defersqlite3_prepare_v2。如果準備成功,您應該只是最後確定,而不是失敗。

    • 使用WHERE子句進行更新時,可能需要檢查sqlite3_changes以查看是否更改了任何記錄。對於標識符更新,如果沒有更新/刪除,我更改了函數以拋出錯誤。

    • 這些函數中的幾個定義爲拋出錯誤以及返回布爾值。對於沒有意義的更新/刪除函數(因爲我們使用錯誤來知道它是否成功,使布爾返回值成爲冗餘)。所以我刪除了Bool返回類型。對於其他函數(例如SELECT例程),返回值顯然有意義,但不適用於這些通過/失敗更新例程。

    • 對於Book屬性,我刪除了book前綴。在SQL中有這個前綴是有意義的(它使得連接查詢更容易編寫),但是它在Swift類型中是多餘的。您通常僅在需要消除歧義時才使用此類前綴(例如,bookDescription,以避免與CustomStringConvertible財產description混淆)。


    不管怎麼說,拉一起,你喜歡的東西:

    var dateFormatter: DateFormatter = { 
        let _formatter = DateFormatter() 
        _formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX" 
        _formatter.locale = Locale(identifier: "en_US_POSIX") 
        _formatter.timeZone = TimeZone(secondsFromGMT: 0) 
        return _formatter 
    }() 
    
    var errorMessage: String { return String(cString: sqlite3_errmsg(db)) } 
    
    func insert(book: Book) throws { 
        var statement: OpaquePointer? = nil 
        let query = "INSERT INTO book (bookName, bookAuthor, bookDesc, bookDate, bookImg, createdBy) VALUES (?, ?, ?, ?, ?, ?)" 
    
        guard sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK else { 
         throw SQLiteError.prepare(message: errorMessage) 
        } 
    
        defer { sqlite3_finalize(statement) } 
    
        guard sqlite3_bind_text(statement, 1, book.title, -1, SQLITE_TRANSIENT) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_bind_text(statement, 2, book.author, -1, SQLITE_TRANSIENT) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_bind_text(statement, 3, book.bookDescription, -1, SQLITE_TRANSIENT) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_bind_text(statement, 4, dateFormatter.string(from: book.createDate), -1, SQLITE_TRANSIENT) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard book.image.withUnsafeBytes({ (bytes: UnsafePointer<UInt8>) -> Int32 in 
         sqlite3_bind_blob(statement, 5, bytes, Int32(book.image.count), SQLITE_TRANSIENT) 
        }) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_bind_text(statement, 6, book.createdBy, -1, SQLITE_TRANSIENT) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_step(statement) == SQLITE_DONE else { 
         throw SQLiteError.step(message: errorMessage) 
        } 
    } 
    
    func update(book: Book) throws { 
        var statement: OpaquePointer? = nil 
        let query = "UPDATE Book SET bookName = ?, bookAuthor = ?, bookDesc = ?, bookDate = ?, bookImg = ?, createdBy = ?, WHERE bookId = ?" 
    
        guard sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK else { 
         throw SQLiteError.prepare(message: errorMessage) 
        } 
    
        defer { sqlite3_finalize(statement) } 
    
        guard sqlite3_bind_text(statement, 2, book.author, -1, SQLITE_TRANSIENT) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_bind_text(statement, 3, book.bookDescription, -1, SQLITE_TRANSIENT) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_bind_text(statement, 4, dateFormatter.string(from: book.createDate), -1, SQLITE_TRANSIENT) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard book.image.withUnsafeBytes({ (bytes: UnsafePointer<UInt8>) -> Int32 in 
         sqlite3_bind_blob(statement, 5, bytes, Int32(book.image.count), SQLITE_TRANSIENT) 
        }) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_bind_text(statement, 6, book.createdBy, -1, SQLITE_TRANSIENT) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_bind_int64(statement, 7, Int64(book.id)) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_step(statement) == SQLITE_DONE else { 
         throw SQLiteError.step(message: errorMessage) 
        } 
    
        guard sqlite3_changes(db) > 0 else { 
         throw SQLiteError.noDataChanged 
        } 
    } 
    
    func delete(book: Book) throws { 
        var statement: OpaquePointer? = nil 
        let query = "DELETE FROM Book WHERE bookId = ?" 
    
        guard sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK else { 
         throw SQLiteError.prepare(message: errorMessage) 
        } 
    
        defer { sqlite3_finalize(statement) } 
    
        guard sqlite3_bind_int64(statement, 1, Int64(book.id)) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_step(statement) == SQLITE_DONE else { 
         throw SQLiteError.step(message: errorMessage) 
        } 
    
        guard sqlite3_changes(db) > 0 else { 
         throw SQLiteError.noDataChanged 
        } 
    } 
    
    func select(bookId: Int) throws -> Book { 
        var statement: OpaquePointer? = nil 
        let query = "SELECT bookId, bookName, bookAuthor, bookDesc, bookDate, bookImg, createdBy FROM Book WHERE bookId = ?" 
    
        guard sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK else { 
         throw SQLiteError.prepare(message: errorMessage) 
        } 
    
        defer { sqlite3_finalize(statement) } 
    
        guard sqlite3_bind_int64(statement, 1, Int64(bookId)) == SQLITE_OK else { 
         throw SQLiteError.bind(message: errorMessage) 
        } 
    
        guard sqlite3_step(statement) == SQLITE_ROW else { 
         throw SQLiteError.step(message: errorMessage) 
        } 
    
        return try book(for: statement) 
    } 
    
    func selectAll() throws -> [Book] { 
        var statement: OpaquePointer? = nil 
        let query = "SELECT bookId, bookName, bookAuthor, bookDesc, bookDate, bookImg, createdBy FROM Book" 
    
        guard sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK else { 
         throw SQLiteError.prepare(message: errorMessage) 
        } 
    
        defer { sqlite3_finalize(statement) } 
    
        var books = [Book]() 
    
        var rc: Int32 
        repeat { 
         rc = sqlite3_step(statement) 
         guard rc == SQLITE_ROW else { break } 
         books.append(try book(for: statement)) 
        } while rc == SQLITE_ROW 
    
        guard rc == SQLITE_DONE else { 
         throw SQLiteError.step(message: errorMessage) 
        } 
    
        return books 
    } 
    
    func book(for statement: OpaquePointer?) throws -> Book { 
        let bookId = Int(sqlite3_column_int64(statement, 0)) 
    
        guard let bookNameCString = sqlite3_column_text(statement, 1) else { 
         throw SQLiteError.column(message: errorMessage) 
        } 
        let bookName = String(cString: bookNameCString) 
    
        guard let bookAuthorCString = sqlite3_column_text(statement, 2) else { 
         throw SQLiteError.column(message: errorMessage) 
        } 
        let bookAuthor = String(cString: bookAuthorCString) 
    
        guard let bookDescCString = sqlite3_column_text(statement, 3) else { 
         throw SQLiteError.column(message: errorMessage) 
        } 
        let bookDesc = String(cString: bookDescCString) 
    
        guard let bookDateCString = sqlite3_column_text(statement, 4) else { 
         throw SQLiteError.column(message: errorMessage) 
        } 
        guard let bookDate = dateFormatter.date(from: String(cString: bookDateCString)) else { 
         throw SQLiteError.invalidDate 
        } 
    
        let bookImgCount = Int(sqlite3_column_bytes(statement, 5)) 
        guard bookImgCount > 0 else { 
         throw SQLiteError.missingData 
        } 
        guard let bookImgBlog = sqlite3_column_blob(statement, 5) else { 
         throw SQLiteError.column(message: errorMessage) 
        } 
        let bookImg = Data(bytes: bookImgBlog, count: bookImgCount) 
    
        guard let createdByCString = sqlite3_column_text(statement, 6) else { 
         throw SQLiteError.column(message: errorMessage) 
        } 
        let createdBy = String(cString: createdByCString) 
    
        return Book(id: bookId, image: bookImg, title: bookName, author: bookAuthor, bookDescription: bookDesc, createDate: bookDate, createdBy: createdBy) 
    } 
    

    有了這些定義:

    struct Book { 
        var id: Int 
        var image: Data 
        var title: String 
        var author: String 
        var bookDescription: String // this is the only one where I kept the `book` prefix, simply because `description` is a reserved name 
        var createDate: Date 
        var createdBy: String 
    } 
    
    enum SQLiteError: Error { 
        case open(result: Int32) 
        case exec(message: String) 
        case prepare(message: String) 
        case bind(message: String) 
        case step(message: String) 
        case column(message: String) 
        case invalidDate 
        case missingData 
        case noDataChanged 
    } 
    

    由於斯威夫特3,我更喜歡小寫enum值。

    +0

    每個使用Swift的純SQLite的人都應該閱讀https://stackoverflow.com/a/28642293/1187415(現在也正式成爲「偉大的答案」) –

    +0

    感謝Rob!我意識到我錯過了一個問題。我如何在SELECT查詢中解碼它們?特別是當我的數據庫有日期和Blob類型。如何將SQLite.Date轉換爲Swift.Date和SQLite.Blob到Swift.Data中? – Jeff

    +0

    如果使用'dateFormatter'將日期轉換爲字符串,如上所示,當檢索它時,可以使用'dateFormatter.date(from:)'將從'sqlite3_column_text'檢索到的字符串轉換爲日期。爲了檢索'Data',使用'sqlite3_column_bytes'來獲取'Data'的'count',使用'sqlite3_column_blob'獲取'bytes',並使用'Data(bytes:count)'來構建'Data對象。 – Rob