From a115a188935189a2fe3fda712eb711d3839f28fd Mon Sep 17 00:00:00 2001 From: Natsuki Kawai Date: Tue, 11 Sep 2018 21:03:15 +0900 Subject: [PATCH 01/10] Added "transaction" function to DatabaseProtocol. --- Sources/PerfectCRUD/Database.swift | 8 +------- Sources/PerfectCRUD/PerfectCRUD.swift | 9 +++++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Sources/PerfectCRUD/Database.swift b/Sources/PerfectCRUD/Database.swift index 94f42a7..f7e5f8f 100644 --- a/Sources/PerfectCRUD/Database.swift +++ b/Sources/PerfectCRUD/Database.swift @@ -16,9 +16,6 @@ public struct Database: DatabaseProtocol { public func table(_ form: T.Type) -> Table { return .init(database: self) } -} - -public extension Database { func sql(_ sql: String, bindings: Bindings = []) throws { CRUDLogging.log(.query, sql) let delegate = try configuration.sqlExeDelegate(forSQL: sql) @@ -36,10 +33,7 @@ public extension Database { } return ret } -} - -public extension Database { - func transaction(_ body: () throws -> T) throws -> T { + public func transaction(_ body: () throws -> T) throws -> T { try sql("BEGIN") do { let r = try body() diff --git a/Sources/PerfectCRUD/PerfectCRUD.swift b/Sources/PerfectCRUD/PerfectCRUD.swift index 948fa10..996bfcc 100644 --- a/Sources/PerfectCRUD/PerfectCRUD.swift +++ b/Sources/PerfectCRUD/PerfectCRUD.swift @@ -51,7 +51,7 @@ public protocol SQLExeDelegate { public protocol DatabaseConfigurationProtocol { var sqlGenDelegate: SQLGenDelegate { get } func sqlExeDelegate(forSQL: String) throws -> SQLExeDelegate - + init(url: String?, name: String?, host: String?, @@ -64,6 +64,7 @@ public protocol DatabaseProtocol { associatedtype Configuration: DatabaseConfigurationProtocol var configuration: Configuration { get } func table(_ form: T.Type) -> Table + func transaction(_ body: () throws -> T) throws -> T } public protocol TableNameProvider { @@ -100,7 +101,7 @@ public extension Joinable { equals: KeyPath) throws -> Join { return .init(fromTable: self, to: to, on: on, equals: equals) } - + func join( _ to: KeyPath, with: Pivot.Type, @@ -108,7 +109,7 @@ public extension Joinable { equals: KeyPath, and: KeyPath, is: KeyPath) throws -> JoinPivot { - + return .init(fromTable: self, to: to, on: on, equals: equals, and: and, alsoEquals: `is`) } } @@ -406,7 +407,7 @@ public extension Date { let ret = dateFormatter.string(from: self) + "Z" return ret } - + init?(fromISO8601 string: String) { let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US_POSIX") From a5c6d8881330094a7d1e53653558fdcfa0287004 Mon Sep 17 00:00:00 2001 From: Natsuki Kawai Date: Tue, 18 Sep 2018 12:00:46 +0900 Subject: [PATCH 02/10] Bugfix for LIMIT expressions. --- Sources/PerfectCRUD/Table.swift | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Sources/PerfectCRUD/Table.swift b/Sources/PerfectCRUD/Table.swift index 590e044..f8eb72a 100644 --- a/Sources/PerfectCRUD/Table.swift +++ b/Sources/PerfectCRUD/Table.swift @@ -67,6 +67,8 @@ public struct Table: TableProtocol, Joinable, S } else if !orderings.isEmpty { let m = try orderings.map { "\(try Expression.keyPath($0.key).sqlSnippet(state: state))\($0.desc ? " DESC" : "")" } sqlStr += "\nORDER BY \(m.joined(separator: ", "))" + limitStr + } else { + sqlStr += limitStr } state.statements.append(.init(sql: sqlStr, bindings: delegate.bindings)) state.delegate.bindings = [] @@ -79,7 +81,7 @@ public struct Table: TableProtocol, Joinable, S let bindings = try encoder.completedBindings(allKeys: allKeys, ignoreKeys: Set(ignoreKeys)) let columnNames = try bindings.map { try delegate.quote(identifier: $0.column) } let bindIdentifiers = bindings.map { $0.identifier } - + var sqlStr = "UPDATE \(nameQ)\nSET \(zip(columnNames, bindIdentifiers).map { "\($0.0)=\($0.1)" }.joined(separator: ", "))" if let whereExpr = state.whereExpr { sqlStr += "\nWHERE \(try whereExpr.sqlSnippet(state: state))" @@ -102,13 +104,3 @@ public struct Table: TableProtocol, Joinable, S } } } - - - - - - - - - - From b5775eea4492c3a31daacb7d619abc949beac6d8 Mon Sep 17 00:00:00 2001 From: Natsuki Kawai Date: Fri, 28 Sep 2018 17:55:17 +0900 Subject: [PATCH 03/10] Supported ISO 8601 format without subseconds. --- Sources/PerfectCRUD/PerfectCRUD.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/PerfectCRUD/PerfectCRUD.swift b/Sources/PerfectCRUD/PerfectCRUD.swift index 996bfcc..849933b 100644 --- a/Sources/PerfectCRUD/PerfectCRUD.swift +++ b/Sources/PerfectCRUD/PerfectCRUD.swift @@ -422,6 +422,11 @@ public extension Date { self = d return } + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ssx" + if let d = dateFormatter.date(from: string) { + self = d + return + } return nil } } From 21d68d3c1f457e88f3fe1bb9d8ba3e58426eac1f Mon Sep 17 00:00:00 2001 From: Kyle Jessup Date: Mon, 24 Sep 2018 16:01:44 -0400 Subject: [PATCH 04/10] Added more valid date formats --- Sources/PerfectCRUD/PerfectCRUD.swift | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Sources/PerfectCRUD/PerfectCRUD.swift b/Sources/PerfectCRUD/PerfectCRUD.swift index 849933b..db6eddf 100644 --- a/Sources/PerfectCRUD/PerfectCRUD.swift +++ b/Sources/PerfectCRUD/PerfectCRUD.swift @@ -412,15 +412,17 @@ public extension Date { let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US_POSIX") dateFormatter.timeZone = TimeZone.current - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" - if let d = dateFormatter.date(from: string) { - self = d - return - } - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSx" - if let d = dateFormatter.date(from: string) { - self = d - return + let validFormats = [ + "yyyy-MM-dd'T'HH:mm:ss.SSSZ", + "yyyy-MM-dd HH:mm:ss.SSSx", + "yyyy-MM-dd'T'HH:mm:ssZ", + "yyyy-MM-dd HH:mm:ssx"] + for fmt in validFormats { + dateFormatter.dateFormat = fmt + if let slf = dateFormatter.date(from: string) { + self = slf + return + } } dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ssx" if let d = dateFormatter.date(from: string) { From 59c13b89d185cb5162edaf146aa882a41256a2bb Mon Sep 17 00:00:00 2001 From: Kyle Jessup Date: Mon, 24 Sep 2018 17:01:00 -0400 Subject: [PATCH 05/10] Added index func to readme --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index c650984..97de0be 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ CRUD support is built directly into each of these database connector packages. * Table * SQL * Table + * Index * Join * Parent Child * Many to Many @@ -276,6 +277,45 @@ let table1 = db.table(TestTable1.self) In the example above, TestTable1 is the OverAllForm. Any destructive operations will affect the corresponding database table. Any selects will produce a collection of TestTable1 objects. + +#### Index + +**Index** can follow: `table`. + +Database indexes are important for good query performance. Given a table object, a database index can be added by calling the `index` function. Indexes should be added along with the code which creates the table. + +The `index` function accepts one or more table keypaths. + +Example usage: + +```swift +struct Person: Codable { + let id: UUID + let firstName: String + let lastName: String +} +// create the Person table +try db.create(Person.self) +// get a table object representing the Person struct +let table = db.table(Person.self) +// add index for lastName column +try table.index(\.lastName) +// add unique index for firstName & lastName columns +try table.index(unique: true, \.firstName, \.lastName) +``` + +Indexes can be created for individual columns, or for columns as a group. If multiple columns are frequenty used together in queries, then it can often improve performance by adding indexes including those columns. + +By including the `unique: true` parameter, a unique index will be created, meaning that only one row can contain any possible column value. This can be applied to multiple columns, as seen in the example above. Consult your specific database's documentation for the exact behaviours of database indexes. + +The `index` function is defined as: + +```swift +public extension Table { + func index(unique: Bool = false, _ keys: PartialKeyPath...) throws -> Index +} +``` + ### Join From 93b990a6a9d964a33984b9955060023ea803a8d9 Mon Sep 17 00:00:00 2001 From: Natsuki Kawai Date: Sat, 22 Sep 2018 15:16:22 +0900 Subject: [PATCH 06/10] Introduced CRUDPrimitive protocol to support generic types in .where() method. --- Sources/PerfectCRUD/Expression/Equality.swift | 4 ++ .../PerfectCRUD/Expression/Expression.swift | 43 ++++++++++++++++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/Sources/PerfectCRUD/Expression/Equality.swift b/Sources/PerfectCRUD/Expression/Equality.swift index 42b8236..b423ea0 100644 --- a/Sources/PerfectCRUD/Expression/Equality.swift +++ b/Sources/PerfectCRUD/Expression/Equality.swift @@ -26,6 +26,10 @@ public func == (lhs: KeyPath, rhs: UUID) -> CRUDBooleanExpr public func == (lhs: KeyPath, rhs: Date) -> CRUDBooleanExpression { return RealBooleanExpression(.equality(lhs: .keyPath(lhs), rhs: .date(rhs))) } +public func == (lhs: KeyPath, rhs: V) -> CRUDBooleanExpression { + return RealBooleanExpression(.equality(lhs: .keyPath(lhs), rhs: rhs.crudExpression)) +} + // == ? public func == (lhs: KeyPath, rhs: String?) -> CRUDBooleanExpression { if let rhs = rhs { diff --git a/Sources/PerfectCRUD/Expression/Expression.swift b/Sources/PerfectCRUD/Expression/Expression.swift index e313e5d..e6d1783 100644 --- a/Sources/PerfectCRUD/Expression/Expression.swift +++ b/Sources/PerfectCRUD/Expression/Expression.swift @@ -20,7 +20,7 @@ import Foundation public indirect enum CRUDExpression { public typealias ExpressionProducer = () -> CRUDExpression - + case column(String) case and(lhs: CRUDExpression, rhs: CRUDExpression) case or(lhs: CRUDExpression, rhs: CRUDExpression) @@ -35,7 +35,7 @@ public indirect enum CRUDExpression { case like(lhs: CRUDExpression, wild1: Bool, String, wild2: Bool) case lazy(ExpressionProducer) case keyPath(AnyKeyPath) - + case integer(Int) case uinteger(UInt) case integer64(Int64) @@ -46,7 +46,7 @@ public indirect enum CRUDExpression { case uinteger16(UInt16) case integer8(Int8) case uinteger8(UInt8) - + case decimal(Double) case float(Float) case string(String) @@ -57,7 +57,7 @@ public indirect enum CRUDExpression { case date(Date) case url(URL) case null - + // todo: // .blob with Data // .integer of varying width @@ -74,6 +74,38 @@ struct RealBooleanExpression: CRUDBooleanExpression { } } +public protocol CRUDPrimitive { + var crudExpression: CRUDExpression { get } +} + +extension Int : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .integer(self) + } + } +} + +extension UUID : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .uuid(self) + } + } +} + +extension Optional : CRUDPrimitive where Wrapped: CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + if let value = self { + return value.crudExpression + } else { + return .null + } + } + } +} + infix operator ~: ComparisonPrecedence // IN, matches infix operator !~: ComparisonPrecedence // NOT IN, matches infix operator %=%: ComparisonPrecedence // LIKE %v% . string or regexp or in array @@ -202,6 +234,3 @@ extension CRUDExpression { } } } - - - From f6ed513d5f2a72415fc87aa2d61e67d8dd948fb8 Mon Sep 17 00:00:00 2001 From: Natsuki Kawai Date: Tue, 20 Nov 2018 14:44:38 +0900 Subject: [PATCH 07/10] Implemented lt operator for Double?. --- Sources/PerfectCRUD/Expression/Comparison.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/PerfectCRUD/Expression/Comparison.swift b/Sources/PerfectCRUD/Expression/Comparison.swift index 4b66a3c..2af9ad6 100644 --- a/Sources/PerfectCRUD/Expression/Comparison.swift +++ b/Sources/PerfectCRUD/Expression/Comparison.swift @@ -14,6 +14,9 @@ public func < (lhs: KeyPath, rhs: String) -> CRUDBooleanE public func < (lhs: KeyPath, rhs: Double) -> CRUDBooleanExpression { return RealBooleanExpression(.lessThan(lhs: .keyPath(lhs), rhs: .decimal(rhs))) } +public func < (lhs: KeyPath, rhs: Double) -> CRUDBooleanExpression { + return RealBooleanExpression(.lessThan(lhs: .keyPath(lhs), rhs: .decimal(rhs))) +} public func < (lhs: KeyPath, rhs: Bool) -> CRUDBooleanExpression { return RealBooleanExpression(.lessThan(lhs: .keyPath(lhs), rhs: .bool(rhs))) } @@ -30,6 +33,9 @@ public func > (lhs: KeyPath, rhs: String) -> CRUDBooleanE public func > (lhs: KeyPath, rhs: Double) -> CRUDBooleanExpression { return RealBooleanExpression(.greaterThan(lhs: .keyPath(lhs), rhs: .decimal(rhs))) } +public func > (lhs: KeyPath, rhs: Double) -> CRUDBooleanExpression { + return RealBooleanExpression(.greaterThan(lhs: .keyPath(lhs), rhs: .decimal(rhs))) +} public func > (lhs: KeyPath, rhs: Bool) -> CRUDBooleanExpression { return RealBooleanExpression(.greaterThan(lhs: .keyPath(lhs), rhs: .bool(rhs))) } From 8af8397fb90cd17c79027181327a37ceb4e52f41 Mon Sep 17 00:00:00 2001 From: Natsuki Kawai Date: Wed, 28 Nov 2018 21:44:15 +0900 Subject: [PATCH 08/10] Conformed Bool to CRUDPrimitive. --- .../PerfectCRUD/Expression/Expression.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Sources/PerfectCRUD/Expression/Expression.swift b/Sources/PerfectCRUD/Expression/Expression.swift index e6d1783..acae84f 100644 --- a/Sources/PerfectCRUD/Expression/Expression.swift +++ b/Sources/PerfectCRUD/Expression/Expression.swift @@ -86,6 +86,27 @@ extension Int : CRUDPrimitive { } } +extension Bool : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .bool(self) + } + } + public var crudBooleanExpression: CRUDBooleanExpression { + get { + return RealBooleanExpression(crudExpression) + } + } +} + +extension Date : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .date(self) + } + } +} + extension UUID : CRUDPrimitive { public var crudExpression: CRUDExpression { get { From 4b86ddb687ff0cdba8369646479cacf12b1d94b7 Mon Sep 17 00:00:00 2001 From: Natsuki Kawai Date: Thu, 29 Nov 2018 14:55:15 +0900 Subject: [PATCH 09/10] Made sql() public. --- Sources/PerfectCRUD/Coding/Coding.swift | 1 - Sources/PerfectCRUD/Create.swift | 3 +-- Sources/PerfectCRUD/Database.swift | 5 ++++- Sources/PerfectCRUD/PerfectCRUD.swift | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/PerfectCRUD/Coding/Coding.swift b/Sources/PerfectCRUD/Coding/Coding.swift index 6aacc55..b6e82c3 100644 --- a/Sources/PerfectCRUD/Coding/Coding.swift +++ b/Sources/PerfectCRUD/Coding/Coding.swift @@ -69,4 +69,3 @@ public enum SpecialType { } } } - diff --git a/Sources/PerfectCRUD/Create.swift b/Sources/PerfectCRUD/Create.swift index a358a12..a599291 100644 --- a/Sources/PerfectCRUD/Create.swift +++ b/Sources/PerfectCRUD/Create.swift @@ -13,7 +13,7 @@ public struct TableCreatePolicy: OptionSet { public static let shallow = TableCreatePolicy(rawValue: 1) public static let dropTable = TableCreatePolicy(rawValue: 2) public static let reconcileTable = TableCreatePolicy(rawValue: 4) - + public static let defaultPolicy: TableCreatePolicy = [] } @@ -172,4 +172,3 @@ public extension Table { return try .init(fromTable: self, keys: [key, key2, key3, key4, key5], unique: unique) } } - diff --git a/Sources/PerfectCRUD/Database.swift b/Sources/PerfectCRUD/Database.swift index f7e5f8f..c2f932a 100644 --- a/Sources/PerfectCRUD/Database.swift +++ b/Sources/PerfectCRUD/Database.swift @@ -16,7 +16,10 @@ public struct Database: DatabaseProtocol { public func table(_ form: T.Type) -> Table { return .init(database: self) } - func sql(_ sql: String, bindings: Bindings = []) throws { + public func sql(_ sql: String) throws { + try self.sql(sql, bindings: []) + } + func sql(_ sql: String, bindings: Bindings) throws { CRUDLogging.log(.query, sql) let delegate = try configuration.sqlExeDelegate(forSQL: sql) try delegate.bind(bindings, skip: 0) diff --git a/Sources/PerfectCRUD/PerfectCRUD.swift b/Sources/PerfectCRUD/PerfectCRUD.swift index db6eddf..8843ad6 100644 --- a/Sources/PerfectCRUD/PerfectCRUD.swift +++ b/Sources/PerfectCRUD/PerfectCRUD.swift @@ -64,6 +64,7 @@ public protocol DatabaseProtocol { associatedtype Configuration: DatabaseConfigurationProtocol var configuration: Configuration { get } func table(_ form: T.Type) -> Table + func sql(_ sql: String) throws func transaction(_ body: () throws -> T) throws -> T } From b1b0823193e6b24ef7dd5af0bf922d97f9d6566d Mon Sep 17 00:00:00 2001 From: Natsuki Kawai Date: Tue, 18 Dec 2018 15:50:13 +0900 Subject: [PATCH 10/10] Conformed integer types as CRUDPrimitive. --- .../PerfectCRUD/Expression/Expression.swift | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/Sources/PerfectCRUD/Expression/Expression.swift b/Sources/PerfectCRUD/Expression/Expression.swift index acae84f..0151e22 100644 --- a/Sources/PerfectCRUD/Expression/Expression.swift +++ b/Sources/PerfectCRUD/Expression/Expression.swift @@ -86,6 +86,77 @@ extension Int : CRUDPrimitive { } } +extension UInt : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .uinteger(self) + } + } +} + +extension Int64 : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .integer64(self) + } + } +} + +extension UInt64 : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .uinteger64(self) + } + } +} + +extension Int32 : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .integer32(self) + } + } +} + +extension UInt32 : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .uinteger32(self) + } + } +} + +extension Int16 : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .integer16(self) + } + } +} + +extension UInt16 : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .uinteger16(self) + } + } +} + +extension Int8 : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .integer8(self) + } + } +} + +extension UInt8 : CRUDPrimitive { + public var crudExpression: CRUDExpression { + get { + return .uinteger8(self) + } + } +} extension Bool : CRUDPrimitive { public var crudExpression: CRUDExpression { get {