Skip to content

Commit e77647e

Browse files
authored
add array support (#105)
* performance test * add array test * fix array test * add new tests to testAll * mysql, psql, sqlite support
1 parent 0c623e7 commit e77647e

File tree

10 files changed

+209
-85
lines changed

10 files changed

+209
-85
lines changed

Sources/FluentBenchmark/FluentBenchmarker.swift

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public final class FluentBenchmarker {
4646
try self.testFieldFilter()
4747
try self.testJoinedFieldFilter()
4848
try self.testSameChildrenFromKey()
49+
try self.testArray()
50+
try self.testPerformance()
4951
try self.testSoftDeleteWithQuery()
5052
}
5153

@@ -1524,6 +1526,179 @@ public final class FluentBenchmarker {
15241526
}
15251527
}
15261528

1529+
public func testArray() throws {
1530+
struct Qux: Codable {
1531+
var foo: String
1532+
}
1533+
1534+
final class Foo: Model {
1535+
static let schema = "foos"
1536+
1537+
struct _Migration: Migration {
1538+
func prepare(on database: Database) -> EventLoopFuture<Void> {
1539+
return database.schema("foos")
1540+
.field("id", .uuid, .identifier(auto: false))
1541+
.field("bar", .array(of: .int), .required)
1542+
.field("baz", .array(of: .string))
1543+
.field("qux", .json, .required)
1544+
.create()
1545+
}
1546+
1547+
func revert(on database: Database) -> EventLoopFuture<Void> {
1548+
return database.schema("foos").delete()
1549+
}
1550+
}
1551+
1552+
@ID(key: "id")
1553+
var id: UUID?
1554+
1555+
@Field(key: "bar")
1556+
var bar: [Int]
1557+
1558+
@Field(key: "baz")
1559+
var baz: [String]?
1560+
1561+
@Field(key: "qux")
1562+
var qux: [Qux]
1563+
1564+
init() { }
1565+
1566+
init(id: UUID? = nil, bar: [Int], baz: [String]?, qux: [Qux]) {
1567+
self.id = id
1568+
self.bar = bar
1569+
self.baz = baz
1570+
self.qux = qux
1571+
}
1572+
}
1573+
1574+
try runTest(#function, [
1575+
Foo._Migration(),
1576+
]) {
1577+
let new = Foo(
1578+
bar: [1, 2, 3],
1579+
baz: ["4", "5", "6"],
1580+
qux: [.init(foo: "7"), .init(foo: "8"), .init(foo: "9")]
1581+
)
1582+
try new.create(on: self.database).wait()
1583+
1584+
guard let fetched = try Foo.find(new.id, on: self.database).wait() else {
1585+
throw Failure("foo didnt save")
1586+
}
1587+
XCTAssertEqual(fetched.bar, [1, 2, 3])
1588+
XCTAssertEqual(fetched.baz, ["4", "5", "6"])
1589+
XCTAssertEqual(fetched.qux.map { $0.foo }, ["7", "8", "9"])
1590+
}
1591+
}
1592+
1593+
public func testPerformance() throws {
1594+
final class Foo: Model {
1595+
static let schema = "foos"
1596+
1597+
struct _Migration: Migration {
1598+
func prepare(on database: Database) -> EventLoopFuture<Void> {
1599+
return database.schema("foos")
1600+
.field("id", .uuid, .identifier(auto: false))
1601+
.field("bar", .int, .required)
1602+
.field("baz", .double, .required)
1603+
.field("qux", .string, .required)
1604+
.field("quux", .datetime, .required)
1605+
.field("quuz", .float, .required)
1606+
.field("corge", .array(of: .int), .required)
1607+
.field("grault", .array(of: .double), .required)
1608+
.field("garply", .array(of: .string), .required)
1609+
.field("fred", .string, .required)
1610+
.field("plugh", .int)
1611+
.field("xyzzy", .double)
1612+
.field("thud", .json, .required)
1613+
.create()
1614+
}
1615+
1616+
func revert(on database: Database) -> EventLoopFuture<Void> {
1617+
return database.schema("foos").delete()
1618+
}
1619+
}
1620+
1621+
struct Thud: Codable {
1622+
var foo: Int
1623+
var bar: Double
1624+
var baz: String
1625+
}
1626+
1627+
@ID(key: "id") var id: UUID?
1628+
@Field(key: "bar") var bar: Int
1629+
@Field(key: "baz") var baz: Double
1630+
@Field(key: "qux") var qux: String
1631+
@Field(key: "quux") var quux: Date
1632+
@Field(key: "quuz") var quuz: Float
1633+
@Field(key: "corge") var corge: [Int]
1634+
@Field(key: "grault") var grault: [Double]
1635+
@Field(key: "garply") var garply: [String]
1636+
@Field(key: "fred") var fred: Decimal
1637+
@Field(key: "plugh") var plugh: Int?
1638+
@Field(key: "xyzzy") var xyzzy: Double?
1639+
@Field(key: "thud") var thud: Thud
1640+
1641+
init() { }
1642+
1643+
init(
1644+
id: UUID? = nil,
1645+
bar: Int,
1646+
baz: Double,
1647+
qux: String,
1648+
quux: Date,
1649+
quuz: Float,
1650+
corge: [Int],
1651+
grault: [Double],
1652+
garply: [String],
1653+
fred: Decimal,
1654+
plugh: Int?,
1655+
xyzzy: Double?,
1656+
thud: Thud
1657+
) {
1658+
self.id = id
1659+
self.bar = bar
1660+
self.baz = baz
1661+
self.qux = qux
1662+
self.quux = quux
1663+
self.quuz = quuz
1664+
self.corge = corge
1665+
self.grault = grault
1666+
self.garply = garply
1667+
self.fred = fred
1668+
self.plugh = plugh
1669+
self.xyzzy = xyzzy
1670+
self.thud = thud
1671+
}
1672+
}
1673+
1674+
try runTest(#function, [
1675+
Foo._Migration()
1676+
]) {
1677+
for _ in 0..<100 {
1678+
let foo = Foo(
1679+
bar: 42,
1680+
baz: 3.14159,
1681+
qux: "foobar",
1682+
quux: .init(),
1683+
quuz: 2.71828,
1684+
corge: [1, 2, 3],
1685+
grault: [4, 5, 6],
1686+
garply: ["foo", "bar", "baz"],
1687+
fred: 1.4142135623730950,
1688+
plugh: 1337,
1689+
xyzzy: 9.94987437106,
1690+
thud: .init(foo: 5, bar: 23, baz: "1994")
1691+
)
1692+
try foo.save(on: self.database).wait()
1693+
}
1694+
let foos = try Foo.query(on: self.database).all().wait()
1695+
for foo in foos {
1696+
XCTAssertNotNil(foo.id)
1697+
}
1698+
XCTAssertEqual(foos.count, 100)
1699+
}
1700+
}
1701+
15271702
public func testSoftDeleteWithQuery() throws {
15281703
try runTest(#function, [
15291704
TrashMigration()

Sources/FluentKit/Error.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33
public enum FluentError: Error, LocalizedError, CustomStringConvertible {
44
case idRequired
5-
case invalidField(name: String, valueType: Any.Type)
5+
case invalidField(name: String, valueType: Any.Type, error: Error)
66
case missingField(name: String)
77
case missingEagerLoad(name: String)
88
case missingParent
@@ -18,8 +18,8 @@ public enum FluentError: Error, LocalizedError, CustomStringConvertible {
1818
return "eager load missing: \(name)"
1919
case .missingParent:
2020
return "parent missing"
21-
case .invalidField(let name, let valueType):
22-
return "invalid field: \(name), expected type: \(valueType)"
21+
case .invalidField(let name, let valueType, let error):
22+
return "invalid field: \(name) type: \(valueType) error: \(error)"
2323
case .noResults:
2424
return "Query returned no results"
2525
}

Sources/FluentKit/Model/Model.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,10 @@ extension AnyModel {
221221
}
222222

223223
var anyID: AnyID {
224-
guard let id = Mirror(reflecting: self).descendant("_id") else {
224+
guard let id = Mirror(reflecting: self).descendant("_id") as? AnyID else {
225225
fatalError("id property must be declared using @ID")
226226
}
227-
return id as! AnyID
227+
return id
228228
}
229229
}
230230

Sources/FluentKit/Model/SoftDeletable.swift

Lines changed: 0 additions & 19 deletions
This file was deleted.

Sources/FluentKit/Model/Timestampable.swift

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
extension AnyModel {
22
var timestamps: [(String, Timestamp)] {
3-
return self.properties.compactMap { (label, property) in
4-
guard let field = property as? Timestamp else {
3+
self.properties.compactMap {
4+
guard let value = $1 as? Timestamp else {
55
return nil
66
}
7-
return (label, field)
7+
return ($0, value)
88
}
99
}
1010
func touchTimestamps(_ triggers: Timestamp.Trigger...) {
@@ -101,30 +101,3 @@ public final class Timestamp: AnyField, FieldRepresentable {
101101
try self.field.decode(from: decoder)
102102
}
103103
}
104-
105-
//public protocol Timestampable: Model, _AnyTimestampable {
106-
// var createdAt: Date? { get set }
107-
// var updatedAt: Date? { get set }
108-
//}
109-
//
110-
//
111-
//public protocol _AnyTimestampable {
112-
// var _createdAtField: Field<Date?> { get }
113-
// var _updatedAtField: Field<Date?> { get }
114-
//}
115-
//
116-
//extension Timestampable {
117-
// public var _createdAtField: Field<Date?> {
118-
// guard let createdAt = Mirror(reflecting: self).descendant("_createdAt") else {
119-
// fatalError("createdAt must be declared using @Field")
120-
// }
121-
// return createdAt as! Field<Date?>
122-
// }
123-
//
124-
// public var _updatedAtField: Field<Date?> {
125-
// guard let updatedAt = Mirror(reflecting: self).descendant("_updatedAt") else {
126-
// fatalError("updatedAt must be declared using @Field")
127-
// }
128-
// return updatedAt as! Field<Date?>
129-
// }
130-
//}

Sources/FluentKit/Properties/Field.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ public final class Field<Value>: AnyField, FieldRepresentable
4848
do {
4949
self.outputValue = try output.decode(self.key, as: Value.self)
5050
} catch {
51-
throw FluentError.invalidField(name: self.key, valueType: Value.self)
51+
throw FluentError.invalidField(
52+
name: self.key,
53+
valueType: Value.self,
54+
error: error
55+
)
5256
}
5357
}
5458
}

Sources/FluentKit/Properties/OptionalParent.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ public final class OptionalParent<To>
3535
public func get(on database: Database) -> EventLoopFuture<To?> {
3636
return self.query(on: database).first()
3737
}
38-
3938
}
4039

4140
extension OptionalParent: FieldRepresentable {

Sources/FluentKit/Properties/Property.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,20 +60,20 @@ extension AnyField { }
6060

6161
extension AnyModel {
6262
var eagerLoadables: [(String, AnyEagerLoadable)] {
63-
return self.properties.compactMap { (label, property) in
64-
guard let eagerLoadable = property as? AnyEagerLoadable else {
63+
self.properties.compactMap {
64+
guard let value = $1 as? AnyEagerLoadable else {
6565
return nil
6666
}
67-
return (label, eagerLoadable)
67+
return ($0, value)
6868
}
6969
}
7070

7171
var fields: [(String, AnyField)] {
72-
return self.properties.compactMap { (label, property) in
73-
guard let field = property as? AnyField else {
72+
self.properties.compactMap {
73+
guard let value = $1 as? AnyField else {
7474
return nil
7575
}
76-
return (label, field)
76+
return ($0, value)
7777
}
7878
}
7979

@@ -84,11 +84,11 @@ extension AnyModel {
8484
guard let label = child.label else {
8585
return nil
8686
}
87-
guard let value = child.value as? AnyProperty else {
87+
guard let property = child.value as? AnyProperty else {
8888
return nil
8989
}
9090
// remove underscore
91-
return (String(label.dropFirst()), value)
91+
return (String(label.dropFirst()), property)
9292
}
9393
}
9494
}

Sources/FluentKit/Schema/DatabaseSchema.swift

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,7 @@ public struct DatabaseSchema {
1818
case delete
1919
}
2020

21-
public enum DataType {
22-
static func bestFor(type: Any.Type) -> DataType {
23-
if let optional = type as? _OptionalType.Type {
24-
return self.bestFor(type: optional._wrappedType)
25-
}
26-
27-
func id(_ type: Any.Type) -> ObjectIdentifier {
28-
return ObjectIdentifier(type)
29-
}
30-
31-
switch id(type) {
32-
case id(String.self): return .string
33-
case id(Int.self), id(Int64.self): return .int64
34-
case id(UInt.self), id(UInt64.self): return .uint64
35-
case id(UUID.self): return .uuid
36-
case id(Date.self): return .datetime
37-
case id(Bool.self): return .bool
38-
default: return .json
39-
}
40-
}
41-
21+
public indirect enum DataType {
4222
case json
4323

4424
public static var int: DataType {
@@ -75,6 +55,8 @@ public struct DatabaseSchema {
7555
case double
7656
case data
7757
case uuid
58+
59+
case array(of: DataType)
7860
case custom(Any)
7961
}
8062

0 commit comments

Comments
 (0)