Skip to content
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ CRUD support is built directly into each of these database connector packages.
* <a href="#table">Table</a>
* <a href="#sql">SQL</a>
* <a href="#table-1">Table</a>
* <a href="#index">Index</a>
* <a href="#join">Join</a>
* <a href="#parent-child">Parent Child</a>
* <a href="#many-to-many">Many to Many</a>
Expand Down Expand Up @@ -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.

<a name="index"></a>
#### 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<OverAllForm>...) throws -> Index<OverAllForm, Table>
}
```

<a name="join"></a>
### Join

Expand Down
1 change: 0 additions & 1 deletion Sources/PerfectCRUD/Coding/Coding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,3 @@ public enum SpecialType {
}
}
}

3 changes: 1 addition & 2 deletions Sources/PerfectCRUD/Create.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
}

Expand Down Expand Up @@ -172,4 +172,3 @@ public extension Table {
return try .init(fromTable: self, keys: [key, key2, key3, key4, key5], unique: unique)
}
}

13 changes: 5 additions & 8 deletions Sources/PerfectCRUD/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ public struct Database<C: DatabaseConfigurationProtocol>: DatabaseProtocol {
public func table<T: Codable>(_ form: T.Type) -> Table<T, Database> {
return .init(database: self)
}
}

public extension Database {
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)
Expand All @@ -36,10 +36,7 @@ public extension Database {
}
return ret
}
}

public extension Database {
func transaction<T>(_ body: () throws -> T) throws -> T {
public func transaction<T>(_ body: () throws -> T) throws -> T {
try sql("BEGIN")
do {
let r = try body()
Expand Down
6 changes: 6 additions & 0 deletions Sources/PerfectCRUD/Expression/Comparison.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public func < <A: Codable>(lhs: KeyPath<A, String>, rhs: String) -> CRUDBooleanE
public func < <A: Codable>(lhs: KeyPath<A, Double>, rhs: Double) -> CRUDBooleanExpression {
return RealBooleanExpression(.lessThan(lhs: .keyPath(lhs), rhs: .decimal(rhs)))
}
public func < <A: Codable>(lhs: KeyPath<A, Double?>, rhs: Double) -> CRUDBooleanExpression {
return RealBooleanExpression(.lessThan(lhs: .keyPath(lhs), rhs: .decimal(rhs)))
}
public func < <A: Codable>(lhs: KeyPath<A, Bool>, rhs: Bool) -> CRUDBooleanExpression {
return RealBooleanExpression(.lessThan(lhs: .keyPath(lhs), rhs: .bool(rhs)))
}
Expand All @@ -30,6 +33,9 @@ public func > <A: Codable>(lhs: KeyPath<A, String>, rhs: String) -> CRUDBooleanE
public func > <A: Codable>(lhs: KeyPath<A, Double>, rhs: Double) -> CRUDBooleanExpression {
return RealBooleanExpression(.greaterThan(lhs: .keyPath(lhs), rhs: .decimal(rhs)))
}
public func > <A: Codable>(lhs: KeyPath<A, Double?>, rhs: Double) -> CRUDBooleanExpression {
return RealBooleanExpression(.greaterThan(lhs: .keyPath(lhs), rhs: .decimal(rhs)))
}
public func > <A: Codable>(lhs: KeyPath<A, Bool>, rhs: Bool) -> CRUDBooleanExpression {
return RealBooleanExpression(.greaterThan(lhs: .keyPath(lhs), rhs: .bool(rhs)))
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/PerfectCRUD/Expression/Equality.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public func == <A: Codable>(lhs: KeyPath<A, UUID>, rhs: UUID) -> CRUDBooleanExpr
public func == <A: Codable>(lhs: KeyPath<A, Date>, rhs: Date) -> CRUDBooleanExpression {
return RealBooleanExpression(.equality(lhs: .keyPath(lhs), rhs: .date(rhs)))
}
public func == <K: Codable, V: CRUDPrimitive>(lhs: KeyPath<K, V>, rhs: V) -> CRUDBooleanExpression {
return RealBooleanExpression(.equality(lhs: .keyPath(lhs), rhs: rhs.crudExpression))
}

// == ?
public func == <A: Codable>(lhs: KeyPath<A, String?>, rhs: String?) -> CRUDBooleanExpression {
if let rhs = rhs {
Expand Down
135 changes: 128 additions & 7 deletions Sources/PerfectCRUD/Expression/Expression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -57,7 +57,7 @@ public indirect enum CRUDExpression {
case date(Date)
case url(URL)
case null

// todo:
// .blob with Data
// .integer of varying width
Expand All @@ -74,6 +74,130 @@ struct RealBooleanExpression: CRUDBooleanExpression {
}
}

public protocol CRUDPrimitive {
var crudExpression: CRUDExpression { get }
}

extension Int : CRUDPrimitive {
public var crudExpression: CRUDExpression {
get {
return .integer(self)
}
}
}

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 {
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 {
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
Expand Down Expand Up @@ -202,6 +326,3 @@ extension CRUDExpression {
}
}
}



27 changes: 18 additions & 9 deletions Sources/PerfectCRUD/PerfectCRUD.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?,
Expand All @@ -64,6 +64,8 @@ public protocol DatabaseProtocol {
associatedtype Configuration: DatabaseConfigurationProtocol
var configuration: Configuration { get }
func table<T: Codable>(_ form: T.Type) -> Table<T, Self>
func sql(_ sql: String) throws
func transaction<T>(_ body: () throws -> T) throws -> T
}

public protocol TableNameProvider {
Expand Down Expand Up @@ -100,15 +102,15 @@ public extension Joinable {
equals: KeyPath<NewType, KeyType>) throws -> Join<OverAllForm, Self, NewType, KeyType> {
return .init(fromTable: self, to: to, on: on, equals: equals)
}

func join<NewType: Codable, Pivot: Codable, FirstKeyType: Equatable, SecondKeyType: Equatable>(
_ to: KeyPath<OverAllForm, [NewType]?>,
with: Pivot.Type,
on: KeyPath<OverAllForm, FirstKeyType>,
equals: KeyPath<Pivot, FirstKeyType>,
and: KeyPath<NewType, SecondKeyType>,
is: KeyPath<Pivot, SecondKeyType>) throws -> JoinPivot<OverAllForm, Self, NewType, Pivot, FirstKeyType, SecondKeyType> {

return .init(fromTable: self, to: to, on: on, equals: equals, and: and, alsoEquals: `is`)
}
}
Expand Down Expand Up @@ -406,17 +408,24 @@ 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")
dateFormatter.timeZone = TimeZone.current
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
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:ss.SSSx"
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ssx"
if let d = dateFormatter.date(from: string) {
self = d
return
Expand Down
Loading