diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index c9d0cf7..0000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-version: 2
-
-jobs:
- macos:
- macos:
- xcode: "9.0"
- steps:
- - run: brew install vapor/tap/vapor
- - checkout
- - run: swift build
- - run: swift test
-
- linux-3:
- docker:
- - image: swift:3.1.1
- steps:
- - run: apt-get install -yq libssl-dev
- - checkout
- - run: swift build
- - run: swift test
-
- linux:
- docker:
- - image: swift:4.0.3
- steps:
- - run: apt-get install -yq libssl-dev
- - checkout
- - run: swift build
- - run: swift test
-
-workflows:
- version: 2
- tests:
- jobs:
- - macos
- - linux-3
- - linux
diff --git a/.gitignore b/.gitignore
index 56e95c2..19342c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,6 @@
Sources/main.swift
.build
Packages
-Database
*.xcodeproj
Package.pins
Package.resolved
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index fe71bfb..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-os:
- - linux
- - osx
-language: generic
-sudo: required
-dist: trusty
-osx_image: xcode8
-script:
- - eval "$(curl -sL swift.qutheory.io/ci-3.1)"
- - eval "$(curl -sL swift.qutheory.io/codecov)"
diff --git a/LICENSE b/LICENSE
index b977105..dbc4271 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2016 Qutheory, LLC
+Copyright (c) 2018 Qutheory, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+SOFTWARE.
\ No newline at end of file
diff --git a/Package.swift b/Package.swift
index 58a9a74..5c2a8bb 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,12 +1,21 @@
+// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "SQLite",
- targets: [
- Target(name: "SQLite", dependencies: ["CSQLite"])
+ products: [
+ .library(name: "SQLite", targets: ["SQLite"]),
],
dependencies: [
- .Package(url: "https://github.com/vapor/core.git", majorVersion: 2),
- .Package(url: "https://github.com/vapor/node.git", majorVersion: 2),
+ // ⏱ Promises and reactive-streams in Swift built for high-performance and scalability.
+ .package(url: "https://github.com/vapor/async.git", from: "1.0.0-rc"),
+
+ // 🌎 Utility package containing tools for byte manipulation, Codable, OS APIs, and debugging.
+ .package(url: "https://github.com/vapor/core.git", from: "3.0.0-rc"),
+ ],
+ targets: [
+ .target(name: "CSQLite"),
+ .target(name: "SQLite", dependencies: ["Async", "Bits", "CodableKit", "CSQLite", "Debugging"]),
+ .testTarget(name: "SQLiteTests", dependencies: ["SQLite"]),
]
)
diff --git a/Package@swift-4.swift b/Package@swift-4.swift
deleted file mode 100644
index 50f62d8..0000000
--- a/Package@swift-4.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-// swift-tools-version:4.0
-import PackageDescription
-
-let package = Package(
- name: "SQLite",
- products: [
- .library(name: "SQLite", targets: ["SQLite"]),
- .library(name: "CSQLite", targets: ["CSQLite"])
- ],
- dependencies: [
- .package(url: "https://github.com/vapor/core.git", .upToNextMajor(from: "2.1.2")),
- .package(url: "https://github.com/vapor/node.git", .upToNextMajor(from: "2.1.0")),
- ],
- targets: [
- .target(name: "SQLite", dependencies: ["Core", "CSQLite", "Node"]),
- .testTarget(name: "SQLiteTests", dependencies: ["SQLite"]),
- .target(name: "CSQLite"),
- ]
-)
diff --git a/README.md b/README.md
index 027f67d..c1e540b 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
-
SQLite 3 for Swift
+
-
-
+
+
@@ -11,10 +11,10 @@
-
-
+
+
-
+
diff --git a/SQLite.podspec b/SQLite.podspec
deleted file mode 100644
index 5d479f7..0000000
--- a/SQLite.podspec
+++ /dev/null
@@ -1,25 +0,0 @@
-Pod::Spec.new do |spec|
- spec.name = 'SQLite'
- spec.version = '2.0.0-alpha.1'
- spec.license = 'MIT'
- spec.homepage = 'https://github.com/vapor/sqlite'
- spec.authors = { 'Vapor' => 'contact@vapor.codes' }
- spec.summary = 'SQLite 3 wrapper for Swift'
- spec.source = { :git => "#{spec.homepage}.git", :tag => "#{spec.version}" }
- spec.ios.deployment_target = "8.0"
- spec.osx.deployment_target = "10.9"
- spec.watchos.deployment_target = "2.0"
- spec.tvos.deployment_target = "9.0"
- spec.requires_arc = true
- spec.social_media_url = 'https://twitter.com/codevapor'
- spec.default_subspec = "Default"
-
- spec.subspec "Default" do |ss|
- ss.source_files = 'Sources/CSQLite/**/*.{h,c}'
- end
-
- # spec.subspec "libc" do |ss|
- # ss.source_files = 'Sources/libc/**/*.{swift}'
- # end
-
-end
diff --git a/Sources/SQLite/Data/SQLiteData.swift b/Sources/SQLite/Data/SQLiteData.swift
new file mode 100644
index 0000000..754525c
--- /dev/null
+++ b/Sources/SQLite/Data/SQLiteData.swift
@@ -0,0 +1,99 @@
+import Foundation
+
+/// All possibles cases for SQLite data.
+public enum SQLiteData {
+ case integer(Int)
+ case float(Double)
+ case text(String)
+ case blob(Foundation.Data)
+ case null
+}
+
+extension SQLiteData {
+ /// Returns an Int if the data is case .integer
+ public var integer: Int? {
+ switch self {
+ case .integer(let int):
+ return int
+ default:
+ return nil
+ }
+ }
+
+ /// Returns a String if the data is case .text
+ public var text: String? {
+ switch self {
+ case .text(let string):
+ return string
+ default:
+ return nil
+ }
+ }
+
+ /// Returns a float if the data is case .double
+ public var float: Double? {
+ switch self {
+ case .float(let double):
+ return double
+ default:
+ return nil
+ }
+ }
+
+ /// Returns Foundation.Data if the data is case .blob
+ public var blob: Foundation.Data? {
+ switch self {
+ case .blob(let data):
+ return data
+ default:
+ return nil
+ }
+ }
+
+ /// Returns true if the data == .null
+ public var isNull: Bool {
+ switch self {
+ case .null:
+ return true
+ default:
+ return false
+ }
+ }
+}
+
+extension SQLiteData: CustomStringConvertible {
+ /// Description of data
+ public var description: String {
+ switch self {
+ case .blob(let data):
+ return data.description
+ case .float(let float):
+ return float.description
+ case .integer(let int):
+ return int.description
+ case .null:
+ return ""
+ case .text(let text):
+ return text
+ }
+ }
+}
+
+public protocol SQLiteDataConvertible {
+ static func convertFromSQLiteData(_ data: SQLiteData) throws -> Self
+ func convertToSQLiteData() throws -> SQLiteData
+}
+
+
+extension Data: SQLiteDataConvertible {
+ public static func convertFromSQLiteData(_ data: SQLiteData) throws -> Data {
+ switch data {
+ case .blob(let data): return data
+ default: throw SQLiteError(problem: .warning, reason: "Could not convert to Data: \(data)", source: .capture())
+ }
+ }
+
+ public func convertToSQLiteData() throws -> SQLiteData {
+ return .blob(self)
+ }
+}
diff --git a/Sources/SQLite/Data/SQLiteDataDecoder.swift b/Sources/SQLite/Data/SQLiteDataDecoder.swift
new file mode 100644
index 0000000..8b7551c
--- /dev/null
+++ b/Sources/SQLite/Data/SQLiteDataDecoder.swift
@@ -0,0 +1,24 @@
+public final class SQLiteDataDecoder: Decoder {
+ public var codingPath: [CodingKey]
+ public var userInfo: [CodingUserInfoKey: Any]
+ public let data: SQLiteData
+
+ public init(data: SQLiteData) {
+ self.codingPath = []
+ self.userInfo = [:]
+ self.data = data
+ }
+
+ public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer {
+ fatalError("unsupported")
+ }
+
+ public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
+ fatalError("unsupported")
+ }
+
+ public func singleValueContainer() -> SingleValueDecodingContainer {
+ return DataDecodingContainer(decoder: self)
+ }
+}
+
diff --git a/Sources/SQLite/Data/SQLiteDataDecodingContainer.swift b/Sources/SQLite/Data/SQLiteDataDecodingContainer.swift
new file mode 100644
index 0000000..508fcff
--- /dev/null
+++ b/Sources/SQLite/Data/SQLiteDataDecodingContainer.swift
@@ -0,0 +1,83 @@
+internal final class DataDecodingContainer: SingleValueDecodingContainer {
+ var codingPath: [CodingKey] {
+ return decoder.codingPath
+ }
+
+ let decoder: SQLiteDataDecoder
+ init(decoder: SQLiteDataDecoder) {
+ self.decoder = decoder
+ }
+
+ func decodeNil() -> Bool {
+ return decoder.data.isNull
+ }
+
+ func decode(_ type: Bool.Type) throws -> Bool {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: Int.Type) throws -> Int {
+ guard let int = decoder.data.fuzzyInt else {
+ fatalError("todo")
+ }
+ return int
+ }
+
+ func decode(_ type: Int8.Type) throws -> Int8 {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: Int16.Type) throws -> Int16 {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: Int32.Type) throws -> Int32 {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: Int64.Type) throws -> Int64 {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: UInt.Type) throws -> UInt {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: UInt8.Type) throws -> UInt8 {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: UInt16.Type) throws -> UInt16 {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: UInt32.Type) throws -> UInt32 {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: UInt64.Type) throws -> UInt64 {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: Float.Type) throws -> Float {
+ fatalError("unsupported")
+ }
+
+ func decode(_ type: Double.Type) throws -> Double {
+ guard let double = decoder.data.fuzzyDouble else {
+ fatalError("todo")
+ }
+ return double
+ }
+
+ func decode(_ type: String.Type) throws -> String {
+ guard let string = decoder.data.fuzzyString else {
+ fatalError("todo")
+ }
+ return string
+ }
+
+ func decode(_ type: T.Type) throws -> T {
+ return try T(from: decoder)
+ }
+}
diff --git a/Sources/SQLite/Data/SQLiteDataEncoder.swift b/Sources/SQLite/Data/SQLiteDataEncoder.swift
new file mode 100644
index 0000000..9b305f5
--- /dev/null
+++ b/Sources/SQLite/Data/SQLiteDataEncoder.swift
@@ -0,0 +1,23 @@
+public final class SQLiteDataEncoder: Encoder {
+ public var codingPath: [CodingKey]
+ public var userInfo: [CodingUserInfoKey: Any]
+ public var data: SQLiteData
+
+ public init() {
+ self.codingPath = []
+ self.userInfo = [:]
+ self.data = .null
+ }
+
+ public func container(keyedBy type: Key.Type) -> KeyedEncodingContainer {
+ fatalError("SQLite rows do not support nested dictionaries")
+ }
+
+ public func unkeyedContainer() -> UnkeyedEncodingContainer {
+ fatalError("SQLite rows do not support nested arrays")
+ }
+
+ public func singleValueContainer() -> SingleValueEncodingContainer {
+ return DataEncodingContainer(encoder: self)
+ }
+}
diff --git a/Sources/SQLite/Data/SQLiteDataEncodingContainer.swift b/Sources/SQLite/Data/SQLiteDataEncodingContainer.swift
new file mode 100644
index 0000000..e32afcb
--- /dev/null
+++ b/Sources/SQLite/Data/SQLiteDataEncodingContainer.swift
@@ -0,0 +1,74 @@
+internal final class DataEncodingContainer: SingleValueEncodingContainer {
+ var codingPath: [CodingKey] {
+ return encoder.codingPath
+ }
+
+ let encoder: SQLiteDataEncoder
+ init(encoder: SQLiteDataEncoder) {
+ self.encoder = encoder
+ }
+
+ func encodeNil() throws {
+ encoder.data = .null
+ }
+
+ func encode(_ value: Bool) throws {
+ encoder.data = .integer(value ? 1 : 0)
+ }
+
+ func encode(_ value: Int) throws {
+ encoder.data = .integer(value)
+ }
+
+ func encode(_ value: Int8) throws {
+ try encode(Int(value))
+ }
+
+ func encode(_ value: Int16) throws {
+ try encode(Int(value))
+ }
+
+ func encode(_ value: Int32) throws {
+ try encode(Int(value))
+ }
+
+ func encode(_ value: Int64) throws {
+ try encode(Int(value))
+ }
+
+ func encode(_ value: UInt) throws {
+ try encode(Int(value))
+ }
+
+ func encode(_ value: UInt8) throws {
+ try encode(Int(value))
+ }
+
+ func encode(_ value: UInt16) throws {
+ try encode(Int(value))
+ }
+
+ func encode(_ value: UInt32) throws {
+ try encode(Int(value))
+ }
+
+ func encode(_ value: UInt64) throws {
+ try encode(Int(value))
+ }
+
+ func encode(_ value: Float) throws {
+ try encode(Double(value))
+ }
+
+ func encode(_ value: Double) throws {
+ encoder.data = .float(value)
+ }
+
+ func encode(_ value: String) throws {
+ encoder.data = .text(value)
+ }
+
+ func encode(_ value: T) throws {
+ try value.encode(to: encoder)
+ }
+}
diff --git a/Sources/SQLite/Database/SQLiteColumn.swift b/Sources/SQLite/Database/SQLiteColumn.swift
new file mode 100644
index 0000000..498ebff
--- /dev/null
+++ b/Sources/SQLite/Database/SQLiteColumn.swift
@@ -0,0 +1,40 @@
+import CSQLite
+
+/// A SQLite column. One instance of each column is created per
+/// result set and all rows will point to the same column instance.
+public final class SQLiteColumn {
+ /// The columns string name.
+ public var name: String
+
+ /// Create a new SQLite column from the name.
+ public init(name: String) {
+ self.name = name
+ }
+
+ /// Create a column from a statement pointer and offest.
+ init(query: SQLiteQuery.Raw, offset: Int32) throws {
+ guard let nameRaw = sqlite3_column_name(query, offset) else {
+ throw SQLiteError(problem: .error, reason: "Unexpected nil column name", source: .capture())
+ }
+ self.name = String(cString: nameRaw)
+ }
+}
+
+extension SQLiteColumn: Hashable {
+ /// Hashable
+ public var hashValue: Int {
+ return name.hashValue
+ }
+
+ /// Equatable
+ public static func ==(lhs: SQLiteColumn, rhs: SQLiteColumn) -> Bool {
+ return lhs.name == rhs.name
+ }
+}
+
+extension SQLiteColumn: CustomStringConvertible {
+ /// Column name
+ public var description: String {
+ return name
+ }
+}
diff --git a/Sources/SQLite/Database/SQLiteConnection.swift b/Sources/SQLite/Database/SQLiteConnection.swift
new file mode 100644
index 0000000..2ae0ba6
--- /dev/null
+++ b/Sources/SQLite/Database/SQLiteConnection.swift
@@ -0,0 +1,56 @@
+import Async
+import CSQLite
+import Dispatch
+
+/// SQlite connection. Use this to create statements that can be executed.
+public final class SQLiteConnection {
+ public typealias Raw = OpaquePointer
+ public var raw: Raw
+
+ /// Reference to the database that created this connection.
+ public let database: SQLiteDatabase
+
+ /// This connection's eventloop.
+ public let eventLoop: EventLoop
+
+ /// Returns the last error message, if one exists.
+ var errorMessage: String? {
+ guard let raw = sqlite3_errmsg(raw) else {
+ return nil
+ }
+
+ return String(cString: raw)
+ }
+
+ /// Create a new SQLite conncetion.
+ internal init(
+ raw: Raw,
+ database: SQLiteDatabase,
+ on worker: Worker
+ ) {
+ self.raw = raw
+ self.database = database
+ self.eventLoop = worker.eventLoop
+ }
+
+ /// Returns an identifier for the last inserted row.
+ public var lastAutoincrementID: Int? {
+ let id = sqlite3_last_insert_rowid(raw)
+ return Int(id)
+ }
+
+ /// Closes the database connection.
+ public func close() {
+ sqlite3_close(raw)
+ }
+
+ /// Convenience for creating a SQLite query.
+ public func query(string: String) -> SQLiteQuery {
+ return SQLiteQuery(string: string, connection: self)
+ }
+
+ /// Closes the database when deinitialized.
+ deinit {
+ close()
+ }
+}
diff --git a/Sources/SQLite/Database/SQLiteDatabase.swift b/Sources/SQLite/Database/SQLiteDatabase.swift
new file mode 100644
index 0000000..fafd59f
--- /dev/null
+++ b/Sources/SQLite/Database/SQLiteDatabase.swift
@@ -0,0 +1,55 @@
+import Async
+import CSQLite
+import Dispatch
+import Foundation
+
+/// SQlite database. Used to make connections.
+public final class SQLiteDatabase {
+ /// The path to the SQLite file.
+ public let storage: SQLiteStorage
+
+ /// If set, query logs will be sent to the supplied logger.
+ public var logger: SQLiteLogger?
+
+ /// Create a new SQLite database.
+ public init(storage: SQLiteStorage) throws {
+ self.storage = storage
+ switch storage {
+ case .memory:
+ if FileManager.default.fileExists(atPath: storage.path) {
+ try FileManager.default.removeItem(atPath: storage.path)
+ }
+ case .file: break
+ }
+ }
+
+ /// Opens a connection to the SQLite database at a given path.
+ /// If the database does not already exist, it will be created.
+ ///
+ /// The supplied DispatchQueue will be used to dispatch output stream calls.
+ /// Make sure to supply the event loop to this parameter so you get called back
+ /// on the appropriate thread.
+ public func makeConnection(
+ on worker: Worker
+ ) -> Future {
+ let promise = Promise(SQLiteConnection.self)
+ do {
+ // make connection
+ let options = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX
+ var raw: SQLiteConnection.Raw?
+ guard sqlite3_open_v2(storage.path, &raw, options, nil) == SQLITE_OK else {
+ throw SQLiteError(problem: .error, reason: "Could not open database.", source: .capture())
+ }
+
+ guard let r = raw else {
+ throw SQLiteError(problem: .error, reason: "Unexpected nil database.", source: .capture())
+ }
+
+ let conn = SQLiteConnection(raw: r, database: self, on: worker)
+ promise.complete(conn)
+ } catch {
+ promise.fail(error)
+ }
+ return promise.future
+ }
+}
diff --git a/Sources/SQLite/Database/SQLiteError.swift b/Sources/SQLite/Database/SQLiteError.swift
new file mode 100644
index 0000000..58ac8ff
--- /dev/null
+++ b/Sources/SQLite/Database/SQLiteError.swift
@@ -0,0 +1,143 @@
+import CSQLite
+import Debugging
+
+/// Errors that can be thrown while using SQLite
+struct SQLiteError: Debuggable {
+ let problem: Problem
+ public let reason: String
+ var sourceLocation: SourceLocation?
+ public var stackTrace: [String]
+ public var identifier: String {
+ return problem.rawValue
+ }
+
+ /// Create an error from a manual problem and reason.
+ init(
+ problem: Problem,
+ reason: String,
+ source: SourceLocation
+ ) {
+ self.problem = problem
+ self.reason = reason
+ self.sourceLocation = source
+ self.stackTrace = SQLiteError.makeStackTrace()
+ }
+
+ /// Dynamically generate an error from status code and database.
+ init(
+ statusCode: Int32,
+ connection: SQLiteConnection,
+ source: SourceLocation
+ ) {
+ self.problem = Problem(statusCode: statusCode)
+ self.reason = connection.errorMessage ?? "Unknown"
+ self.sourceLocation = source
+ self.stackTrace = SQLiteError.makeStackTrace()
+ }
+}
+
+/// Problem kinds.
+internal enum Problem: String {
+ case error
+ case intern
+ case permission
+ case abort
+ case busy
+ case locked
+ case noMemory
+ case readOnly
+ case interrupt
+ case ioError
+ case corrupt
+ case notFound
+ case full
+ case cantOpen
+ case proto
+ case empty
+ case schema
+ case tooBig
+ case constraint
+ case mismatch
+ case misuse
+ case noLFS
+ case auth
+ case format
+ case range
+ case notADatabase
+ case notice
+ case warning
+ case row
+ case done
+ case connection
+ case close
+ case prepare
+ case bind
+ case execute
+
+ init(statusCode: Int32) {
+ switch statusCode {
+ case SQLITE_ERROR:
+ self = .error
+ case SQLITE_INTERNAL:
+ self = .intern
+ case SQLITE_PERM:
+ self = .permission
+ case SQLITE_ABORT:
+ self = .abort
+ case SQLITE_BUSY:
+ self = .busy
+ case SQLITE_LOCKED:
+ self = .locked
+ case SQLITE_NOMEM:
+ self = .noMemory
+ case SQLITE_READONLY:
+ self = .readOnly
+ case SQLITE_INTERRUPT:
+ self = .interrupt
+ case SQLITE_IOERR:
+ self = .ioError
+ case SQLITE_CORRUPT:
+ self = .corrupt
+ case SQLITE_NOTFOUND:
+ self = .notFound
+ case SQLITE_FULL:
+ self = .full
+ case SQLITE_CANTOPEN:
+ self = .cantOpen
+ case SQLITE_PROTOCOL:
+ self = .proto
+ case SQLITE_EMPTY:
+ self = .empty
+ case SQLITE_SCHEMA:
+ self = .schema
+ case SQLITE_TOOBIG:
+ self = .tooBig
+ case SQLITE_CONSTRAINT:
+ self = .constraint
+ case SQLITE_MISMATCH:
+ self = .mismatch
+ case SQLITE_MISUSE:
+ self = .misuse
+ case SQLITE_NOLFS:
+ self = .noLFS
+ case SQLITE_AUTH:
+ self = .auth
+ case SQLITE_FORMAT:
+ self = .format
+ case SQLITE_RANGE:
+ self = .range
+ case SQLITE_NOTADB:
+ self = .notADatabase
+ case SQLITE_NOTICE:
+ self = .notice
+ case SQLITE_WARNING:
+ self = .warning
+ case SQLITE_ROW:
+ self = .row
+ case SQLITE_DONE:
+ self = .done
+ default:
+ self = .error
+ }
+ }
+}
diff --git a/Sources/SQLite/Database/SQLiteField.swift b/Sources/SQLite/Database/SQLiteField.swift
new file mode 100644
index 0000000..abd3a22
--- /dev/null
+++ b/Sources/SQLite/Database/SQLiteField.swift
@@ -0,0 +1,54 @@
+import Bits
+import CSQLite
+import Foundation
+
+/// A single SQLite field. There are one or more of these per Row.
+/// Each field references a unique column for that result set.
+public struct SQLiteField {
+ /// The field's data
+ public var data: SQLiteData
+
+ /// Create a new SQLite field from the data.
+ public init(data: SQLiteData) {
+ self.data = data
+ }
+
+ /// Create a field from statement pointer, column, and offset.
+ init(query: SQLiteQuery.Raw, offset: Int32) throws {
+ let type = try SQLiteFieldType(query: query, offset: offset)
+ switch type {
+ case .integer:
+ let val = sqlite3_column_int64(query, offset)
+ let integer = Int(val)
+ data = .integer(integer)
+ case .real:
+ let val = sqlite3_column_double(query, offset)
+ let double = Double(val)
+ data = .float(double)
+ case .text:
+ guard let val = sqlite3_column_text(query, offset) else {
+ throw SQLiteError(problem: .error, reason: "Unexpected nil column text.", source: .capture())
+ }
+ let string = String(cString: val)
+ data = .text(string)
+ case .blob:
+ let blobPointer = sqlite3_column_blob(query, offset)
+ let length = Int(sqlite3_column_bytes(query, offset))
+
+ let buffer = UnsafeBufferPointer(
+ start: blobPointer?.assumingMemoryBound(to: Byte.self),
+ count: length
+ )
+ data = .blob(Foundation.Data(buffer: buffer))
+ case .null:
+ data = .null
+ }
+ }
+}
+
+extension SQLiteField: CustomStringConvertible {
+ /// Description of field
+ public var description: String {
+ return data.description
+ }
+}
diff --git a/Sources/SQLite/Database/SQLiteFieldType.swift b/Sources/SQLite/Database/SQLiteFieldType.swift
new file mode 100644
index 0000000..0eedec9
--- /dev/null
+++ b/Sources/SQLite/Database/SQLiteFieldType.swift
@@ -0,0 +1,29 @@
+import CSQLite
+
+/// The type of a certain field. This determines how the field's data should be parsed.
+/// Note: The field type is not directly tied to the column type, and can vary between rows.
+public enum SQLiteFieldType {
+ case integer
+ case real
+ case text
+ case blob
+ case null
+
+ /// Create a new field type from statement and an offset.
+ init(query: SQLiteQuery.Raw, offset: Int32) throws {
+ switch sqlite3_column_type(query, offset) {
+ case SQLITE_INTEGER:
+ self = .integer
+ case SQLITE_FLOAT:
+ self = .real
+ case SQLITE_TEXT:
+ self = .text
+ case SQLITE_BLOB:
+ self = .blob
+ case SQLITE_NULL:
+ self = .null
+ default:
+ throw SQLiteError(problem: .error, reason: "Unexpected column type.", source: .capture())
+ }
+ }
+}
diff --git a/Sources/SQLite/Database/SQLiteLogger.swift b/Sources/SQLite/Database/SQLiteLogger.swift
new file mode 100644
index 0000000..baddc47
--- /dev/null
+++ b/Sources/SQLite/Database/SQLiteLogger.swift
@@ -0,0 +1,7 @@
+import Async
+
+/// A SQLite logger.
+public protocol SQLiteLogger {
+ /// Log the query.
+ func log(query: SQLiteQuery)
+}
diff --git a/Sources/SQLite/Database/SQLiteStorage.swift b/Sources/SQLite/Database/SQLiteStorage.swift
new file mode 100644
index 0000000..174bd3a
--- /dev/null
+++ b/Sources/SQLite/Database/SQLiteStorage.swift
@@ -0,0 +1,12 @@
+/// Available SQLite storage methods.
+public enum SQLiteStorage {
+ case memory
+ case file(path: String)
+
+ var path: String {
+ switch self {
+ case .memory: return "/tmp/_swift-tmp.sqlite"
+ case .file(let path): return path
+ }
+ }
+}
diff --git a/Sources/SQLite/Query/SQLiteQuery+Bind.swift b/Sources/SQLite/Query/SQLiteQuery+Bind.swift
new file mode 100644
index 0000000..cd8fa7c
--- /dev/null
+++ b/Sources/SQLite/Query/SQLiteQuery+Bind.swift
@@ -0,0 +1,54 @@
+import Bits
+import CSQLite
+import Foundation
+
+extension SQLiteQuery {
+ /// Binds SQLite data to the query.
+ @discardableResult
+ public func bind(_ sqliteData: SQLiteData) -> Self {
+ binds.append(sqliteData)
+ return self
+ }
+
+ /// Bind a Double to the current bind position.
+ @discardableResult
+ public func bind(_ value: Double) -> Self {
+ bind(.float(value))
+ return self
+ }
+
+ /// Bind an Int to the current bind position.
+ @discardableResult
+ public func bind(_ value: Int) -> Self {
+ bind(.integer(value))
+ return self
+ }
+
+ /// Bind a String to the current bind position.
+ @discardableResult
+ public func bind(_ value: String) -> Self {
+ bind(.text(value))
+ return self
+ }
+
+ /// Bind Bytes to the current bind position.
+ @discardableResult
+ public func bind(_ value: Data) -> Self {
+ bind(.blob(value))
+ return self
+ }
+
+ /// Bind a Bool to the current bind position.
+ @discardableResult
+ public func bind(_ value: Bool) -> Self {
+ return bind(value ? 1 : 0)
+ }
+
+ /// Binds null to the current bind position
+ @discardableResult
+ public func bindNull() -> Self {
+ bind(.null)
+ return self
+ }
+
+}
diff --git a/Sources/SQLite/Query/SQLiteQuery.swift b/Sources/SQLite/Query/SQLiteQuery.swift
new file mode 100644
index 0000000..6dcd5f9
--- /dev/null
+++ b/Sources/SQLite/Query/SQLiteQuery.swift
@@ -0,0 +1,141 @@
+import Async
+import Bits
+import CSQLite
+import Dispatch
+import Foundation
+
+let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
+
+/// An executable statement. Use this to bind parameters to a query, and finally
+/// execute the statement asynchronously.
+///
+/// try database.statement("INSERT INTO `foo` VALUES(?, ?)")
+/// .bind(42)
+/// .bind("Hello, world!")
+/// .execute()
+/// .then { ... }
+/// .catch { ... }
+///
+public final class SQLiteQuery {
+ // internal C api pointer for this query
+ typealias Raw = OpaquePointer
+
+ /// the database this statement will
+ /// be executed on.
+ public let connection: SQLiteConnection
+
+ /// the raw query string
+ public let string: String
+
+ /// data bound to this query
+ public var binds: [SQLiteData]
+
+ /// Create a new SQLite statement with a supplied query string and database.
+ internal init(string: String, connection: SQLiteConnection) {
+ self.connection = connection
+ self.string = string
+ self.binds = []
+ }
+
+ /// Resets the query.
+ public func reset(_ statementPointer: OpaquePointer) {
+ sqlite3_reset(statementPointer)
+ sqlite3_clear_bindings(statementPointer)
+ }
+
+ // MARK: Execute
+
+ /// Executes the query, completing the future with the results.
+ public func execute() -> Future {
+ let promise = Promise(SQLiteResults?.self)
+ do {
+ try promise.complete(self.blockingExecute())
+ } catch {
+ promise.fail(error)
+ }
+ return promise.future
+ }
+
+ /// Executes the query, blocking until complete.
+ private func blockingExecute() throws -> SQLiteResults? {
+ var columns: [SQLiteColumn] = []
+
+ var raw: Raw?
+
+ // log before anything happens, in case there's an error
+ connection.database.logger?.log(query: self)
+
+ let ret = sqlite3_prepare_v2(connection.raw, string, -1, &raw, nil)
+ guard ret == SQLITE_OK else {
+ throw SQLiteError(statusCode: ret, connection: connection, source: .capture())
+ }
+
+ guard let r = raw else {
+ throw SQLiteError(statusCode: ret, connection: connection, source: .capture())
+ }
+
+ var nextBindPosition: Int32 = 1
+
+ for bind in binds {
+ switch bind {
+ case .blob(let value):
+ let count = Int32(value.count)
+ let pointer: UnsafePointer = value.withUnsafeBytes { $0 }
+ let ret = sqlite3_bind_blob(r, nextBindPosition, UnsafeRawPointer(pointer), count, SQLITE_TRANSIENT)
+ guard ret == SQLITE_OK else {
+ throw SQLiteError(statusCode: ret, connection: connection, source: .capture())
+ }
+ case .float(let value):
+ let ret = sqlite3_bind_double(r, nextBindPosition, value)
+ guard ret == SQLITE_OK else {
+ throw SQLiteError(statusCode: ret, connection: connection, source: .capture())
+ }
+ case .integer(let value):
+ let ret = sqlite3_bind_int64(r, nextBindPosition, Int64(value))
+ guard ret == SQLITE_OK else {
+ throw SQLiteError(statusCode: ret, connection: connection, source: .capture())
+ }
+ case .null:
+ let ret = sqlite3_bind_null(r, nextBindPosition)
+ if ret != SQLITE_OK {
+ throw SQLiteError(statusCode: ret, connection: connection, source: .capture())
+ }
+ case .text(let value):
+ let strlen = Int32(value.utf8.count)
+ let ret = sqlite3_bind_text(r, nextBindPosition, value, strlen, SQLITE_TRANSIENT)
+ guard ret == SQLITE_OK else {
+ throw SQLiteError(statusCode: ret, connection: connection, source: .capture())
+ }
+ }
+
+ nextBindPosition += 1
+ }
+
+ let count = sqlite3_column_count(r)
+ columns.reserveCapacity(Int(count))
+
+ // iterate over column count and intialize columns once
+ // we will then re-use the columns for each row
+ for i in 0..?
+
+ /// Use `SQLiteResults.stream()` to create a `SQLiteResultStream`
+ internal init(results: SQLiteResults) {
+ self.results = results
+ }
+
+ /// See OutputStream.output
+ public func output(to inputStream: S) where S: Async.InputStream, S.Input == Output {
+ downstream = AnyInputStream(inputStream)
+ }
+
+ /// See ConnectionContext.connection
+ public func start() {
+ results.fetchRow().do { row in
+ if let row = row {
+ self.downstream?.next(row).do {
+ self.start()
+ }.catch { error in
+ self.downstream?.error(error)
+ }
+ } else {
+ self.downstream?.close()
+ }
+ }.catch { error in
+ self.downstream?.error(error)
+ }
+ }
+}
+
+/// MARK: Convenience
+
+extension SQLiteResults {
+ /// Create a SQLiteResultStream from these results
+ public func stream() -> SQLiteResultStream {
+ return .init(results: self)
+ }
+}
+
+/// FIXME: move this to async
+
+extension OutputStream {
+ /// Convenience for gathering all rows into a single array.
+ public func all() -> Future<[Output]> {
+ let promise = Promise([Output].self)
+
+ // cache the rows
+ var rows: [Output] = []
+
+ // drain the stream of results
+ self.drain { row in
+ rows.append(row)
+ }.catch { error in
+ promise.fail(error)
+ }.finally {
+ promise.complete(rows)
+ }
+
+ return promise.future
+ }
+}
diff --git a/Sources/SQLite/Query/SQLiteResults.swift b/Sources/SQLite/Query/SQLiteResults.swift
new file mode 100644
index 0000000..0750fe5
--- /dev/null
+++ b/Sources/SQLite/Query/SQLiteResults.swift
@@ -0,0 +1,78 @@
+import Async
+import CSQLite
+
+/// Results from a SQLite query. Call `.fetchRow` to continue
+/// fetching rows from this result set until there are none left.
+public final class SQLiteResults {
+ /// The raw query pointer
+ private let raw: SQLiteQuery.Raw
+
+ /// Parsed columns for this query
+ private let columns: [SQLiteColumn]
+
+ /// The connection we are executing on
+ private var connection: SQLiteConnection
+
+ /// The state of the results
+ private var state: SQLiteResultsState
+
+ /// Use `SQLiteQuery.execute` to create a `SQLiteResultStream`
+ internal init(raw: SQLiteQuery.Raw, columns: [SQLiteColumn], on connection: SQLiteConnection) {
+ self.raw = raw
+ self.columns = columns
+ self.connection = connection
+ /// sqlite query will only create a result object if
+ /// there are rows available
+ state = .rowAvailable
+ }
+
+ /// Fetches rows in blocking fashion. This should be called from a
+ /// background thread.
+ public func fetchRow() -> Future {
+ let promise = Promise(SQLiteRow?.self)
+ do {
+ try promise.complete(self.blockingFetchRow())
+ } catch {
+ promise.fail(error)
+ }
+ return promise.future
+ }
+
+ /// Fetches rows in blocking fashion. This should be called from a
+ /// background thread.
+ public func blockingFetchRow() throws -> SQLiteRow? {
+ guard case .rowAvailable = state else {
+ return nil
+ }
+
+ var row = SQLiteRow()
+
+ // iterator over column count again and create a field
+ // for each column. Use the column we have already initialized.
+ for i in 0..:
+ KeyedDecodingContainerProtocol,
+ UnkeyedDecodingContainer,
+ SingleValueDecodingContainer
+{
+ typealias Key = K
+
+ var codingPath: [CodingKey] {
+ return decoder.codingPath
+ }
+
+ var count: Int? {
+ return nil
+ }
+
+ var isAtEnd: Bool {
+ return false
+ }
+
+ var currentIndex: Int {
+ return 0
+ }
+
+ var allKeys: [K] {
+ return []
+ }
+
+ let decoder: SQLiteRowDecoder
+ init(decoder: SQLiteRowDecoder) {
+ self.decoder = decoder
+ }
+
+ func contains(_ key: K) -> Bool {
+ let col = SQLiteColumn(name: key.stringValue)
+ return decoder.row.fields.keys.contains(col)
+ }
+
+ func decodeNil(forKey key: K) throws -> Bool {
+ return decoder.row[key.stringValue]?.isNull ?? true
+ }
+
+ func decode(_ type: Bool.Type, forKey key: K) throws -> Bool {
+ guard let bool = decoder.row[key.stringValue]?.fuzzyBool else {
+ fatalError("No bool found at key `\(key.stringValue)`")
+ }
+ return bool
+ }
+
+ func decode(_ type: Int.Type, forKey key: K) throws -> Int {
+ guard let int = decoder.row[key.stringValue]?.fuzzyInt else {
+ fatalError("No int found at key `\(key.stringValue)`")
+ }
+
+ return int
+ }
+
+ func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: UInt.Type, forKey key: K) throws -> UInt {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Float.Type, forKey key: K) throws -> Float {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Double.Type, forKey key: K) throws -> Double {
+ guard let double = decoder.row[key.stringValue]?.fuzzyDouble else {
+ fatalError("No double found at key `\(key.stringValue)`")
+ }
+
+ return double
+ }
+
+ func decode(_ type: String.Type, forKey key: K) throws -> String {
+ guard let string = decoder.row[key.stringValue]?.fuzzyString else {
+ fatalError("No string found at key `\(key.stringValue)`")
+ }
+
+ return string
+ }
+
+ func decode(_ type: T.Type, forKey key: K) throws -> T {
+ guard let data = decoder.row[key.stringValue] else {
+ fatalError("no data at key")
+ }
+
+ if let convertible = T.self as? SQLiteDataConvertible.Type {
+ return try convertible.convertFromSQLiteData(data) as! T
+ } else {
+ let d = SQLiteDataDecoder(data: data)
+ return try T(from: d)
+ }
+ }
+
+ func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer {
+ fatalError("unimplemented")
+ }
+
+ func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer {
+ fatalError("unimplemented")
+ }
+
+ func superDecoder(forKey key: K) throws -> Decoder {
+ fatalError("unimplemented")
+ }
+
+ func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey {
+ fatalError("unimplemented")
+ }
+
+ func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
+ fatalError("unimplemented")
+ }
+
+ func superDecoder() throws -> Decoder {
+ fatalError("unimplemented")
+ }
+
+ func decodeNil() -> Bool {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Bool.Type) throws -> Bool {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Int.Type) throws -> Int {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Int8.Type) throws -> Int8 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Int16.Type) throws -> Int16 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Int32.Type) throws -> Int32 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Int64.Type) throws -> Int64 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: UInt.Type) throws -> UInt {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: UInt8.Type) throws -> UInt8 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: UInt16.Type) throws -> UInt16 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: UInt32.Type) throws -> UInt32 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: UInt64.Type) throws -> UInt64 {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Float.Type) throws -> Float {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: Double.Type) throws -> Double {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: String.Type) throws -> String {
+ fatalError("unimplemented")
+ }
+
+ func decode(_ type: T.Type) throws -> T where T : Decodable {
+ fatalError("unimplemented")
+ }
+}
+
+extension SQLiteData {
+ internal var fuzzyBool: Bool? {
+ if let int = fuzzyInt {
+ switch int {
+ case 1:
+ return true
+ case 0:
+ return false
+ default:
+ return nil
+ }
+ } else {
+ return nil
+ }
+ }
+
+ internal var fuzzyInt: Int? {
+ switch self {
+ case .integer(let int):
+ return int
+ case .text(let text):
+ return Int(text)
+ default:
+ return nil
+ }
+ }
+
+ internal var fuzzyString: String? {
+ switch self {
+ case .integer(let int):
+ return int.description
+ case .text(let text):
+ return text
+ default:
+ return nil
+ }
+ }
+
+ internal var fuzzyDouble: Double? {
+ switch self {
+ case .float(let double):
+ return double
+ case .text(let text):
+ return Double(text)
+ default:
+ return nil
+ }
+ }
+}
+
diff --git a/Sources/SQLite/Row/EncodingContainer.swift b/Sources/SQLite/Row/EncodingContainer.swift
new file mode 100644
index 0000000..5534716
--- /dev/null
+++ b/Sources/SQLite/Row/EncodingContainer.swift
@@ -0,0 +1,71 @@
+internal final class RowEncodingContainer: KeyedEncodingContainerProtocol
+{
+ typealias Key = K
+ var encoder: SQLiteRowEncoder
+ var codingPath: [CodingKey] {
+ get { return encoder.codingPath }
+ }
+
+ public init(encoder: SQLiteRowEncoder) {
+ self.encoder = encoder
+ }
+
+ func encode(_ value: Bool, forKey key: K) throws {
+ encoder.row[key.stringValue] = .integer(value ? 1 : 0)
+ }
+
+ func encode(_ value: Int, forKey key: K) throws {
+ encoder.row[key.stringValue] = .integer(value)
+ }
+
+ func encode(_ value: Double, forKey key: K) throws {
+ encoder.row[key.stringValue] = .float(value)
+ }
+
+ func encode(_ value: String, forKey key: K) throws {
+ encoder.row[key.stringValue] = .text(value)
+ }
+
+ func encode(_ value: T, forKey key: K) throws where T: Encodable {
+ if let convertible = value as? SQLiteDataConvertible {
+ encoder.row[key.stringValue] = try convertible.convertToSQLiteData()
+ } else {
+ let d = SQLiteDataEncoder()
+ try value.encode(to: d)
+ encoder.row[key.stringValue] = d.data
+ }
+ }
+
+ func encodeIfPresent(_ value: T?, forKey key: K) throws where T : Encodable {
+ /// Strange that this is required now.... it this a bug?
+ if let value = value {
+ try encode(value, forKey: key)
+ } else {
+ try encodeNil(forKey: key)
+ }
+ }
+
+ func encodeNil(forKey key: K) throws {
+ encoder.row[key.stringValue] = .null
+ }
+
+ func nestedContainer(
+ keyedBy keyType: NestedKey.Type, forKey key: K
+ ) -> KeyedEncodingContainer {
+ fatalError("SQLite rows do not support nested dictionaries")
+ }
+
+ func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer {
+ fatalError("SQLite rows do not support nested arrays")
+ }
+
+ func superEncoder() -> Encoder {
+ return encoder
+ }
+
+ func superEncoder(forKey key: K) -> Encoder {
+ return encoder
+ }
+}
+
+
diff --git a/Sources/SQLite/Row/SQLiteRow.swift b/Sources/SQLite/Row/SQLiteRow.swift
new file mode 100644
index 0000000..9970540
--- /dev/null
+++ b/Sources/SQLite/Row/SQLiteRow.swift
@@ -0,0 +1,32 @@
+import CSQLite
+
+/// A SQlite row of data. This contains one or more Fields.
+public struct SQLiteRow {
+ /// The row's fields, stored by column name for O(1) access.
+ public var fields: [SQLiteColumn: SQLiteField]
+
+ /// Create a new row with fields.
+ public init(fields: [SQLiteColumn: SQLiteField] = [:]) {
+ self.fields = fields
+ }
+
+ /// Access the row by field name, returning optional Data.
+ public subscript(_ field: String) -> SQLiteData? {
+ get {
+ let col = SQLiteColumn(name: field)
+ guard let field = fields[col] else {
+ return nil
+ }
+
+ return field.data
+ }
+ set {
+ let col = SQLiteColumn(name: field)
+ if let value = newValue {
+ fields[col] = SQLiteField(data: value)
+ } else {
+ fields.removeValue(forKey: col)
+ }
+ }
+ }
+}
diff --git a/Sources/SQLite/Row/SQLiteRowDecoder.swift b/Sources/SQLite/Row/SQLiteRowDecoder.swift
new file mode 100644
index 0000000..6ed10f6
--- /dev/null
+++ b/Sources/SQLite/Row/SQLiteRowDecoder.swift
@@ -0,0 +1,25 @@
+import CodableKit
+
+public final class SQLiteRowDecoder: Decoder {
+ public var codingPath: [CodingKey]
+ public var userInfo: [CodingUserInfoKey: Any]
+ public let row: SQLiteRow
+
+ public init(row: SQLiteRow) {
+ self.row = row
+ self.codingPath = []
+ self.userInfo = [:]
+ }
+
+ public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer {
+ return KeyedDecodingContainer(DecodingContainer(decoder: self))
+ }
+
+ public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
+ fatalError("unsupported")
+ }
+
+ public func singleValueContainer() throws -> SingleValueDecodingContainer {
+ return DecodingContainer(decoder: self)
+ }
+}
diff --git a/Sources/SQLite/Row/SQLiteRowEncoder.swift b/Sources/SQLite/Row/SQLiteRowEncoder.swift
new file mode 100644
index 0000000..47d3e04
--- /dev/null
+++ b/Sources/SQLite/Row/SQLiteRowEncoder.swift
@@ -0,0 +1,23 @@
+public final class SQLiteRowEncoder: Encoder {
+ public var codingPath: [CodingKey]
+ public var userInfo: [CodingUserInfoKey: Any]
+ public var row: SQLiteRow
+
+ public init() {
+ self.codingPath = []
+ self.userInfo = [:]
+ self.row = SQLiteRow(fields: [:])
+ }
+
+ public func container(keyedBy type: Key.Type) -> KeyedEncodingContainer {
+ return KeyedEncodingContainer(RowEncodingContainer(encoder: self))
+ }
+
+ public func unkeyedContainer() -> UnkeyedEncodingContainer {
+ fatalError("unsupported")
+ }
+
+ public func singleValueContainer() -> SingleValueEncodingContainer {
+ fatalError("unsupported")
+ }
+}
diff --git a/Sources/SQLite/SQLite+Result.swift b/Sources/SQLite/SQLite+Result.swift
deleted file mode 100644
index 1920e92..0000000
--- a/Sources/SQLite/SQLite+Result.swift
+++ /dev/null
@@ -1,83 +0,0 @@
-import CSQLite
-import Node
-import typealias Core.Bytes
-
-extension SQLite {
- /**
- Represents a row of data from
- a SQLite table.
- */
- public struct Result {
-
- public struct Row {
- public var data: [String: Node]
-
- init() {
- data = [:]
- }
- /**
- Binds a Result.Row result at a certain position
- to a proper SQLite.DataType enum.
- - parameter i : position in current row
- - parameter pointer : the current sqlite pointer
- */
- public mutating func bind(at i: Int32, pointer: Statement.Pointer) throws {
- //Retrieve column name at i
- let name = sqlite3_column_name(pointer, i)
- let column: String
- if let name = name {
- column = String(cString: name)
- } else {
- column = ""
- }
-
- //Iterates over possible SQLite data types.
- switch sqlite3_column_type(pointer, i) {
- case SQLITE_TEXT:
- let text = sqlite3_column_text(pointer, i)
-
- var value: String = ""
- if let text = text {
- value = String(cString: text)
-
- }
-
- data[column] = .string(value)
-
- case SQLITE_BLOB:
- if let blobPointer = sqlite3_column_blob(pointer, i) {
- let length = Int(sqlite3_column_bytes(pointer, i))
-
- let i8bufptr = UnsafeBufferPointer(start: blobPointer.assumingMemoryBound(to: Bytes.Element.self), count: length)
-
- data[column] = .bytes(Bytes(i8bufptr))
- } else {
- // The return value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
- // https://www.sqlite.org/c3ref/column_blob.html
- data[column] = .bytes([])
- }
-
- case SQLITE_INTEGER:
- let integer = Int(sqlite3_column_int64(pointer, i))
- data[column] = .number(.int(integer))
-
- case SQLITE_FLOAT: // as in floating point, actually returns a double.
- let double = Double(sqlite3_column_double(pointer, i))
- data[column] = .number(.double(double))
- case SQLITE_NULL:
- data[column] = .null
-
- default:
- throw StatusError.misuse("unsupported type")
- }
- }
-
- }
-
- var rows: [Row]
-
- init() {
- rows = []
- }
- }
-}
diff --git a/Sources/SQLite/SQLite+Statement.swift b/Sources/SQLite/SQLite+Statement.swift
deleted file mode 100644
index 05e3d8d..0000000
--- a/Sources/SQLite/SQLite+Statement.swift
+++ /dev/null
@@ -1,74 +0,0 @@
-import CSQLite
-import typealias Core.Bytes
-
-extension SQLite {
- /**
- Represents a single database statement.
- The statement is used to bind prepared
- values and contains a pointer to the
- underlying SQLite statement memory.
- */
- public class Statement {
- public typealias Pointer = OpaquePointer
-
- public var pointer: Pointer
- public var database: Database
-
- var bindPosition: Int32
- var nextBindPosition: Int32 {
- bindPosition += 1
- return bindPosition
- }
-
- public init(pointer: Pointer, database: Database) {
- self.pointer = pointer
- self.database = database
- bindPosition = 0
- }
-
- public func reset(_ statementPointer: OpaquePointer) {
- sqlite3_reset(statementPointer)
- sqlite3_clear_bindings(statementPointer)
- }
-
- public func bind(_ value: Double) throws {
- let status = sqlite3_bind_double(pointer, nextBindPosition, value)
-
- try StatusError.check(with: status, msg: database.errorMessage)
- }
-
- public func bind(_ value: Int) throws {
- let status = sqlite3_bind_int64(pointer, nextBindPosition, Int64(value))
-
- try StatusError.check(with: status, msg: database.errorMessage)
-
- }
-
- public func bind(_ value: String) throws {
- let strlen = Int32(value.utf8.count)
-
- let status = sqlite3_bind_text(pointer, nextBindPosition, value, strlen, SQLITE_TRANSIENT)
-
- try StatusError.check(with: status, msg: database.errorMessage)
- }
-
- public func bind(_ value: Bytes) throws {
- let count = Int32(value.count)
-
- let status = sqlite3_bind_blob(pointer, nextBindPosition, value, count, SQLITE_TRANSIENT)
-
- try StatusError.check(with: status, msg: database.errorMessage)
- }
-
- public func bind(_ value: Bool) throws {
- try bind(value ? 1 : 0)
- }
-
- public func null() throws {
- let status = sqlite3_bind_null(pointer, nextBindPosition)
-
- try StatusError.check(with: status, msg: database.errorMessage)
- }
- }
-
-}
diff --git a/Sources/SQLite/SQLite.swift b/Sources/SQLite/SQLite.swift
deleted file mode 100644
index 80bc6e1..0000000
--- a/Sources/SQLite/SQLite.swift
+++ /dev/null
@@ -1,141 +0,0 @@
-import CSQLite
-
-let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
-
-public class SQLite {
- /**
- The prepare closure is used
- to bind values to the SQLite statement
- in a safe, escaped manner.
- */
- public typealias PrepareClosure = ((Statement) throws -> ())
-
- /**
- Provides more useful type
- information for the Database pointer.
- */
- public typealias Database = OpaquePointer
-
- /**
- An optional pointer to the
- connection to the SQLite database.
- */
- public var database: Database?
-
- /**
- Opens a connection to the SQLite
- database at a given path.
-
- If the database does not already exist,
- it will be created.
- */
- public init(path: String) throws {
- let options = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
-
- let status = sqlite3_open_v2(path, &database, options, nil)
-
- try StatusError.check(with: status, msg: database?.errorMessage ?? "")
- }
-
- /**
- Closes a connection to the database.
- */
- public func close() {
- sqlite3_close(database)
- }
-
- /**
- Closes the database when de-initialized.
- */
- deinit {
- self.close()
- }
-
- /**
- Executes a statement query string
- and calls the prepare closure to bind
- any prepared values.
-
- The resulting rows are returned if
- no errors occur.
- */
- public func execute(_ queryString: String, prepareClosure: PrepareClosure = { _ in }) throws -> [Result.Row] {
- guard let database = self.database else {
- throw StatusError(with: SQLITE_ERROR, msg: "No database")!
- }
-
- let statementContainer = UnsafeMutablePointer.allocate(capacity: 1)
- defer {
- statementContainer.deallocate(capacity: 1)
- }
-
- let status = sqlite3_prepare_v2(database, queryString, -1, statementContainer, nil);
-
- try StatusError.check(with: status, msg: database.errorMessage)
-
- guard let statementPointer = statementContainer.pointee else {
- throw StatusError(with: SQLITE_ERROR, msg: "Statement pointer error")!
- }
-
- let statement = Statement(pointer: statementPointer, database: database)
- try prepareClosure(statement)
-
- var result = Result()
- while sqlite3_step(statement.pointer) == SQLITE_ROW {
-
- var row = Result.Row()
- let count = sqlite3_column_count(statement.pointer)
-
- for i in 0.. SQLiteQuery {
+ return SQLiteQuery(string: string, connection: self)
+ }
+}
diff --git a/Tests/SQLiteTests/Utilities.swift b/Tests/SQLiteTests/Utilities.swift
index 7f5e5b2..6c3662d 100644
--- a/Tests/SQLiteTests/Utilities.swift
+++ b/Tests/SQLiteTests/Utilities.swift
@@ -1,12 +1,13 @@
+import Async
+import Dispatch
import XCTest
@testable import SQLite
-extension SQLite {
- static func makeTestConnection() -> SQLite? {
+extension SQLiteConnection {
+ static func makeTestConnection(queue: EventLoop) -> SQLiteConnection? {
do {
- let sqlite = try SQLite(path:"test_database.sqlite")
- return sqlite
-
+ let sqlite = try SQLiteDatabase(storage: .memory)
+ return try sqlite.makeConnection(on: queue).blockingAwait()
} catch {
XCTFail()
}
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 0000000..76950d2
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,24 @@
+version: 2
+
+jobs:
+ macos:
+ macos:
+ xcode: "9.2"
+ steps:
+ - checkout
+ - run: swift build
+ - run: swift test
+ linux:
+ docker:
+ - image: norionomura/swift:swift-4.1-branch
+ steps:
+ - checkout
+ - run: swift build
+ - run: swift test
+ - run: swift build -c release
+workflows:
+ version: 2
+ tests:
+ jobs:
+ - linux
+ # - macos
diff --git a/test_database.sqlite b/test_database.sqlite
deleted file mode 100644
index c08b4a0..0000000
Binary files a/test_database.sqlite and /dev/null differ