From 633fb2c28d9d91adb49e3c2beca7885e1caf18f4 Mon Sep 17 00:00:00 2001 From: Matthias Zenger Date: Fri, 10 Apr 2020 13:29:17 +0200 Subject: [PATCH] Included more comments. Added a statement finalizer that throws errors. --- CHANGELOG.md | 5 ++ SQLiteExpress.xcodeproj/project.pbxproj | 18 ++++++-- .../xcschemes/SQLiteExpress iOS.xcscheme | 4 +- Sources/SQLiteExpress iOS/Info.plist | 4 +- Sources/SQLiteExpress/Info.plist | 2 +- Sources/SQLiteExpress/SQLiteError.swift | 9 +++- Sources/SQLiteExpress/SQLiteStatement.swift | 46 ++++++++++++++++++- Sources/SQLiteExpress/SQLiteType.swift | 17 ++++++- Tests/SQLiteExpressTests/Info.plist | 6 ++- .../SQLiteExpressTests.swift | 8 ++-- 10 files changed, 100 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c95e12..61ccd24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Changelog +## 1.0.1 (2020-04-10) +- Fixed project setup +- Included more comments +- Added a statement finalizer that throws errors + ## 1.0 (2020-04-04) - Initial version diff --git a/SQLiteExpress.xcodeproj/project.pbxproj b/SQLiteExpress.xcodeproj/project.pbxproj index 7a90c3f..03f4289 100644 --- a/SQLiteExpress.xcodeproj/project.pbxproj +++ b/SQLiteExpress.xcodeproj/project.pbxproj @@ -43,7 +43,7 @@ CC3625E724393037003A96DB /* SQLiteDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteDatabase.swift; sourceTree = ""; }; CC3625EC24394912003A96DB /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; CC3625ED24394943003A96DB /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; - CCC43D2A24394AFE005FFD1D /* SQLiteExpress_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLiteExpress_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CCC43D2A24394AFE005FFD1D /* SQLiteExpress iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = "SQLiteExpress iOS.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; CCC43D2C24394AFE005FFD1D /* SQLiteExpress_iOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLiteExpress_iOS.h; sourceTree = ""; }; CCC43D2D24394AFE005FFD1D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ @@ -94,7 +94,7 @@ children = ( CC3625C724392F76003A96DB /* SQLiteExpress.framework */, CC3625D024392F76003A96DB /* SQLiteExpressTests.xctest */, - CCC43D2A24394AFE005FFD1D /* SQLiteExpress_iOS.framework */, + CCC43D2A24394AFE005FFD1D /* SQLiteExpress iOS.framework */, ); name = Products; sourceTree = ""; @@ -206,7 +206,7 @@ ); name = "SQLiteExpress iOS"; productName = "SQLiteExpress iOS"; - productReference = CCC43D2A24394AFE005FFD1D /* SQLiteExpress_iOS.framework */; + productReference = CCC43D2A24394AFE005FFD1D /* SQLiteExpress iOS.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ @@ -440,6 +440,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = C72Z63N8M5; DYLIB_COMPATIBILITY_VERSION = 1; @@ -452,6 +453,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = net.objecthub.SQLiteExpress; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -466,6 +468,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = C72Z63N8M5; DYLIB_COMPATIBILITY_VERSION = 1; @@ -478,6 +481,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = net.objecthub.SQLiteExpress; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -527,6 +531,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = C72Z63N8M5; DYLIB_COMPATIBILITY_VERSION = 1; @@ -540,8 +545,9 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = net.objecthub.SQLiteExpress; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "SQLiteExpress iOS"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; @@ -553,6 +559,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = C72Z63N8M5; DYLIB_COMPATIBILITY_VERSION = 1; @@ -566,8 +573,9 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = net.objecthub.SQLiteExpress; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "SQLiteExpress iOS"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; diff --git a/SQLiteExpress.xcodeproj/xcshareddata/xcschemes/SQLiteExpress iOS.xcscheme b/SQLiteExpress.xcodeproj/xcshareddata/xcschemes/SQLiteExpress iOS.xcscheme index 5a90457..542c32a 100644 --- a/SQLiteExpress.xcodeproj/xcshareddata/xcschemes/SQLiteExpress iOS.xcscheme +++ b/SQLiteExpress.xcodeproj/xcshareddata/xcschemes/SQLiteExpress iOS.xcscheme @@ -15,7 +15,7 @@ @@ -75,7 +75,7 @@ diff --git a/Sources/SQLiteExpress iOS/Info.plist b/Sources/SQLiteExpress iOS/Info.plist index 9bcb244..14642d5 100644 --- a/Sources/SQLiteExpress iOS/Info.plist +++ b/Sources/SQLiteExpress iOS/Info.plist @@ -15,8 +15,10 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Google LLC. All rights reserved. diff --git a/Sources/SQLiteExpress/Info.plist b/Sources/SQLiteExpress/Info.plist index bb5c5a3..14642d5 100644 --- a/Sources/SQLiteExpress/Info.plist +++ b/Sources/SQLiteExpress/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright diff --git a/Sources/SQLiteExpress/SQLiteError.swift b/Sources/SQLiteExpress/SQLiteError.swift index 0b26727..9b63c9e 100644 --- a/Sources/SQLiteExpress/SQLiteError.swift +++ b/Sources/SQLiteExpress/SQLiteError.swift @@ -24,7 +24,7 @@ import SQLite3 /// `SQLiteError` enumerates all errors that can be thrown by methods of the SQLiteExpress framework. /// Both primary as well as extended result/error codes from SQLite are supported. -public enum SQLiteError: Error, Equatable { +public enum SQLiteError: Error, Hashable, CustomStringConvertible { case ok case error case `internal` @@ -320,9 +320,14 @@ public enum SQLiteError: Error, Equatable { /// Returns a human readable error message for this `SQLiteError` object. public var errorMessage: String { if let cstr = sqlite3_errstr(self.errorCode) { - return String(cString: cstr) + return "\(String(cString: cstr)) \(self.errorCode)" } else { return "unknown sqlite3 error \(self.errorCode)" } } + + /// Returns a description of this error as a string. + public var description: String { + return "SQLite3 error: \(self.errorMessage)" + } } diff --git a/Sources/SQLiteExpress/SQLiteStatement.swift b/Sources/SQLiteExpress/SQLiteStatement.swift index 736b460..409dc06 100644 --- a/Sources/SQLiteExpress/SQLiteStatement.swift +++ b/Sources/SQLiteExpress/SQLiteStatement.swift @@ -22,6 +22,8 @@ import Foundation import SQLite3 +/// `SQLiteStatement` represents a compiled SQL statement, typically generated by +/// `SQLiteDatabase.prepare`. public class SQLiteStatement { private static let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self) @@ -46,7 +48,8 @@ public class SQLiteStatement { sqlite3_finalize(self.stmt) } - /// Returns true if the result is done + /// Executes this statement and returns `true` if the result is done. When `false` is returned, + /// `step()` can be called again to retrieve the next row of the result. @discardableResult public func step() throws -> Bool { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -63,6 +66,8 @@ public class SQLiteStatement { } } + /// Resets this statement. This is typically called by clients when an error is encountered, or + /// `step()` is not invoked until all rows have been retrieved. public func reset() { guard self.stmt != nil else { return @@ -70,6 +75,7 @@ public class SQLiteStatement { sqlite3_reset(self.stmt) } + /// This will delete the statement, ignoring errors that are encountered. public func finalize() { guard self.stmt != nil else { return @@ -78,6 +84,23 @@ public class SQLiteStatement { self.stmt = nil } + /// This will delete the statement and throw errors that are encountered if the `throwingError` + /// argument is set to `true`. + public func finalize(throwingError: Bool) throws { + guard self.stmt != nil else { + return + } + let res = sqlite3_finalize(self.stmt) + if throwingError { + if res != SQLITE_OK && res != SQLITE_DONE { + throw SQLiteError(res) + } + } + self.stmt = nil + } + + /// Returns the number of columns of the current result row. Columns are indexed started + /// with 0 (i.e. column 0 is the first column). public var columnCount: Int { guard self.stmt != nil else { return 0 @@ -85,6 +108,7 @@ public class SQLiteStatement { return Int(sqlite3_column_count(self.stmt)) } + /// Returns the name of column `col` of the current result row. public func name(ofColumn col: Int) throws -> String? { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -95,6 +119,7 @@ public class SQLiteStatement { return String(cString: cstr) } + /// Returns the database name of column `col` of the current result row. public func databaseName(ofColumn col: Int) throws -> String? { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -105,6 +130,7 @@ public class SQLiteStatement { return String(cString: cstr) } + /// Returns the table name of column `col` of the current result row. public func tableName(ofColumn col: Int) throws -> String? { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -115,6 +141,7 @@ public class SQLiteStatement { return String(cString: cstr) } + /// Returns the original name of column `col` of the current result row. public func originalName(ofColumn col: Int) throws -> String? { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -125,6 +152,7 @@ public class SQLiteStatement { return String(cString: cstr) } + /// Returns the type of column `col` of the current result row. public func type(ofColumn col: Int) throws -> SQLiteType? { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -132,6 +160,7 @@ public class SQLiteStatement { return SQLiteType(rawValue: sqlite3_column_type(self.stmt, Int32(col))) } + /// Returns the declared type of column `col` of the current result row. public func declaredType(ofColumn col: Int) throws -> String? { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -142,6 +171,7 @@ public class SQLiteStatement { return String(cString: cstr) } + /// Returns the integer value at column `col` of the current result row. public func int(atColumn col: Int) throws -> Int64 { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -149,6 +179,7 @@ public class SQLiteStatement { return sqlite3_column_int64(self.stmt, Int32(col)) } + /// Returns the floating-point value at column `col` of the current result row. public func float(atColumn col: Int) throws -> Double { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -156,6 +187,7 @@ public class SQLiteStatement { return sqlite3_column_double(self.stmt, Int32(col)) } + /// Returns the text value at column `col` of the current result row. public func text(atColumn col: Int) throws -> String? { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -166,6 +198,7 @@ public class SQLiteStatement { return String(cString: cstr) } + /// Returns the blob value at column `col` of the current result row. public func blob(atColumn col: Int) throws -> Data? { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -176,6 +209,7 @@ public class SQLiteStatement { return Data(bytes: blob, count: Int(sqlite3_column_bytes(self.stmt, Int32(col)))) } + /// Returns the date value at column `col` of the current result row. public func date(atColumn col: Int) throws -> Date? { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -189,6 +223,7 @@ public class SQLiteStatement { return date } + /// Returns true, if column `col` of the current result row contains a _null_ value. public func isNull(atColumn col: Int) throws -> Bool { guard self.stmt != nil else { throw SQLiteError.misuse @@ -196,6 +231,7 @@ public class SQLiteStatement { return sqlite3_column_type(self.stmt, Int32(col)) == SQLITE_NULL } + /// Returns the number of parameters of this statement. Parameters are indexed starting at 1. public var paramCount: Int { guard self.stmt != nil else { return 0 @@ -203,6 +239,7 @@ public class SQLiteStatement { return Int(sqlite3_bind_parameter_count(self.stmt)) } + /// Returns the parameter index for the parameter with name `name`. public func paramIndex(_ name: String) throws -> Int { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -210,6 +247,7 @@ public class SQLiteStatement { return Int(sqlite3_bind_parameter_index(self.stmt, name.cString(using: .utf8))) } + /// Returns the parameter name of the parameter at index `idx`. public func paramName(_ idx: Int) throws -> String? { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -217,6 +255,7 @@ public class SQLiteStatement { return String(cString: sqlite3_bind_parameter_name(self.stmt, Int32(idx))) } + /// Binds the given integer value `x` to the parameter at index `idx`. public func bind(integer x: Int64, toParam idx: Int) throws { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -227,6 +266,7 @@ public class SQLiteStatement { } } + /// Binds the given floating-point value `x` to the parameter at index `idx`. public func bind(float x: Double, toParam idx: Int) throws { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -237,6 +277,7 @@ public class SQLiteStatement { } } + /// Binds the given string value `x` to the parameter at index `idx`. public func bind(text x: String, toParam idx: Int) throws { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -251,6 +292,7 @@ public class SQLiteStatement { } } + /// Binds the given data value `x` to the parameter at index `idx`. public func bind(blob x: Data, toParam idx: Int) throws { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -267,6 +309,7 @@ public class SQLiteStatement { } } + /// Binds the given date `x` to the parameter at index `idx`. public func bind(date x: Date, toParam idx: Int) throws { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) @@ -281,6 +324,7 @@ public class SQLiteStatement { } } + /// Binds _null_ to the parameter at index `idx`. public func bindNull(toParam idx: Int) throws { guard self.stmt != nil else { throw SQLiteError(SQLITE_MISUSE) diff --git a/Sources/SQLiteExpress/SQLiteType.swift b/Sources/SQLiteExpress/SQLiteType.swift index 257120d..3d4339f 100644 --- a/Sources/SQLiteExpress/SQLiteType.swift +++ b/Sources/SQLiteExpress/SQLiteType.swift @@ -23,10 +23,25 @@ import SQLite3 /// `SQLiteType` enumerates the types supported natively by SQLite. -public enum SQLiteType: Int32 { +public enum SQLiteType: Int32, CustomStringConvertible { case integer = 1 case float case text case blob case null + + public var description: String { + switch self { + case .integer: + return "INTEGER" + case .float: + return "REAL" + case .text: + return "TEXT" + case .blob: + return "BLOB" + case .null: + return "NULL" + } + } } diff --git a/Tests/SQLiteExpressTests/Info.plist b/Tests/SQLiteExpressTests/Info.plist index 64d65ca..14642d5 100644 --- a/Tests/SQLiteExpressTests/Info.plist +++ b/Tests/SQLiteExpressTests/Info.plist @@ -15,8 +15,10 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Google LLC. All rights reserved. diff --git a/Tests/SQLiteExpressTests/SQLiteExpressTests.swift b/Tests/SQLiteExpressTests/SQLiteExpressTests.swift index cfc304f..4fe0c43 100644 --- a/Tests/SQLiteExpressTests/SQLiteExpressTests.swift +++ b/Tests/SQLiteExpressTests/SQLiteExpressTests.swift @@ -48,7 +48,7 @@ class SQLiteExpressTests: XCTestCase { ); """) XCTAssertTrue(try stmt0.step()) - stmt0.finalize() + try stmt0.finalize(throwingError: true) XCTAssertNil(try? stmt0.step()) let stmt1 = try db.prepare(sql: """ INSERT INTO Contacts VALUES (?, ?, ?, ?); @@ -74,7 +74,7 @@ class SQLiteExpressTests: XCTestCase { try stmt1.bind(text: "mark@white-house.gov", toParam: 3) try stmt1.bindNull(toParam: 4) XCTAssertTrue(try stmt1.step()) - stmt1.finalize() + try stmt1.finalize(throwingError: true) let stmt2 = try db.prepare(sql: """ SELECT COUNT(DISTINCT phone) FROM Contacts; """) @@ -82,7 +82,7 @@ class SQLiteExpressTests: XCTestCase { XCTAssertEqual(stmt2.columnCount, 1) XCTAssertEqual(try stmt2.int(atColumn: 0), 1) XCTAssertTrue(try stmt2.step()) - stmt2.finalize() + try stmt2.finalize(throwingError: true) let stmt3 = try db.prepare(sql: """ SELECT name, email FROM Contacts; """) @@ -101,6 +101,6 @@ class SQLiteExpressTests: XCTestCase { XCTAssertEqual(try stmt3.text(atColumn: 0), "Mark Meadows") XCTAssertEqual(try stmt3.text(atColumn: 1), "mark@white-house.gov") XCTAssertTrue(try stmt3.step()) - stmt3.finalize() + try stmt3.finalize(throwingError: true) } }