Skip to content

Commit d5c402e

Browse files
authored
Log start and finish of migration prepares and reverts (#526)
* Add documentation comments to the various Property protocols, such as they are. * Fixup CI run events and coverage ignores
1 parent 64f799f commit d5c402e

File tree

4 files changed

+97
-17
lines changed

4 files changed

+97
-17
lines changed

.github/workflows/main-codecov.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
name: Update code coverage baselines
22
on:
33
push: { branches: [ main ] }
4+
45
jobs:
56
update-main-codecov:
67
uses: vapor/ci/.github/workflows/run-unit-tests.yml@reusable-workflows
78
with:
89
with_coverage: true
910
with_tsan: false
11+
coverage_ignores: '/Tests/|/Sources/FluentBenchmark/'

.github/workflows/test.yml

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
name: test
22
on:
33
pull_request:
4-
push:
5-
branches:
6-
- main
4+
75
env:
86
LOG_LEVEL: info
97
SWIFT_DETERMINISTIC_HASHING: 1

Sources/FluentKit/Migration/Migrator.swift

+4
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,17 @@ private final class DatabaseMigrator {
246246
// MARK: Private
247247

248248
private func prepare(_ migration: Migration, batch: Int) -> EventLoopFuture<Void> {
249+
self.database.logger.info("[Migrator] Starting prepare", metadata: ["migration": .string(migration.name)])
249250
return migration.prepare(on: self.database).flatMap {
251+
self.database.logger.info("[Migrator] Finished prepare", metadata: ["migration": .string(migration.name)])
250252
return MigrationLog(name: migration.name, batch: batch).save(on: self.database)
251253
}
252254
}
253255

254256
private func revert(_ migration: Migration) -> EventLoopFuture<Void> {
257+
self.database.logger.info("[Migrator] Starting revert", metadata: ["migration": .string(migration.name)])
255258
return migration.revert(on: self.database).flatMap {
259+
self.database.logger.info("[Migrator] Finished revert", metadata: ["migration": .string(migration.name)])
256260
return MigrationLog.query(on: self.database).filter(\.$name == migration.name).delete()
257261
}
258262
}

Sources/FluentKit/Properties/Property.swift

+90-14
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,150 @@
1+
/// The type-erased form of ``Property`` (see below). ``AnyProperty`` is used to
2+
/// access a model's set of Fluent properties in a fully generic fashion (with a
3+
/// little help from runtime reflection). It is generally not meaningful to conform
4+
/// to this protocol without also at least conforming to ``Property``.
15
public protocol AnyProperty: AnyObject {
26
static var anyValueType: Any.Type { get }
37
var anyValue: Any? { get }
48
}
59

10+
/// A property wrapper type conforms to this protocol to participate in Fluent's
11+
/// system for interfacing between the various properties of a model and the
12+
/// representations of those properties in a database. All properties whose
13+
/// wrappers conform to this protocol appear in Fluent's list of the data items
14+
/// which exist on a given model - whether those items contain actual data,
15+
/// such as a property representing a field in a database table, or are means to
16+
/// access other data, such a list of associated models on the far side of a
17+
/// many-to-many relation.
618
public protocol Property: AnyProperty {
719
associatedtype Model: Fields
820
associatedtype Value: Codable
921
var value: Value? { get set }
1022
}
1123

24+
/// ``AnyProperty``'s requirements are implemented in terms of ``Property``'s
25+
/// requirements - they're the same requirements; ``Property`` is just more
26+
/// specific about the types.
1227
extension AnyProperty where Self: Property {
28+
/// The type-erased value of a property is the property's value.
1329
public var anyValue: Any? {
1430
self.value
1531
}
16-
32+
33+
/// The type-erased type of a property's value is the type of the property's value.
1734
public static var anyValueType: Any.Type {
1835
Value.self
1936
}
2037
}
2138

39+
/// Marks a property as having "database" capability - in other words, the property
40+
/// receives output from the results of read queries, provides input to write queries,
41+
/// and/or represents one or more model fields.
42+
///
43+
/// - Note: Most "database" properties participate in all three aspects (is/has fields,
44+
/// provides input, receives output), but certain properties only participate in
45+
/// receiving output (most notably the non-parent relation property types). Those
46+
/// properties only behave in this manner because the ability to look up the needed
47+
/// information on demand was not available in Swift until after the implementation was
48+
/// effectively complete. They should not be considered actual "database" properties.
2249
public protocol AnyDatabaseProperty: AnyProperty {
2350
var keys: [FieldKey] { get }
2451
func input(to input: DatabaseInput)
2552
func output(from output: DatabaseOutput) throws
2653
}
2754

55+
/// Marks a property as participating in the ``Fields`` protocol's (defaulted)
56+
/// implementation of ``Decodable`` and ``Encodable``. This allows the property
57+
/// to encode and decode to and from representations other than storage in a
58+
/// database, and to act as a container if it contains any additional properties
59+
/// which also wish to participate. Just about every property type is codable.
2860
public protocol AnyCodableProperty: AnyProperty {
2961
func encode(to encoder: Encoder) throws
3062
func decode(from decoder: Decoder) throws
3163
}
3264

65+
/// The type-erased form of ``QueryableProperty`` (see below). ``AnyQueryableProperty``
66+
/// is used most often as a type-generic check for whether or not a given property
67+
/// represents an actual database field.
3368
public protocol AnyQueryableProperty: AnyProperty {
69+
/// Provides the database field's "path" - a nonempty list of field keys whose last
70+
/// item provides the name the field has in the database (which need not be the same
71+
/// as the name the corresponding model property has in Swift). A path containing
72+
/// more than one key theoretically describes a nested structure within the database,
73+
/// such as a field containing a complex JSON document, but at present this is not
74+
/// fully implemented by Fluent, making a multi-key path as invalid as an empty one.
3475
var path: [FieldKey] { get }
76+
77+
/// If the property's current value has been set, return a description of the
78+
/// appropriate method for encoding that value into a database query. See
79+
/// ``DatabaseQuery/Value`` for more details. If the value is not set, the
80+
/// property must choose whether to request the `NULL` encoding or to return
81+
/// no value at all (whether or not this results in an error is highly context-
82+
/// dependent).
3583
func queryableValue() -> DatabaseQuery.Value?
3684
}
3785

86+
/// Marks a property as being "queryable", meaning that it represents exactly one
87+
/// "real" database field (i.e. the database table will contain a "physical" field
88+
/// corresponding to the property, and it will be the only field that does so).
3889
public protocol QueryableProperty: AnyQueryableProperty, Property {
90+
/// Requests a description of the appropriate method of encoding a value of the
91+
/// property's wrapped type into a database query. In essence, this is the static
92+
/// version of ``AnyQueryableProperty/queryableValue()-3uzih``, except that this
93+
/// version will always have an input and thus can not return `nil`.
94+
///
95+
/// - Warning: The existence of this method implies that any two identically-typed
96+
/// instances of a property _must_ encode their values into queries in exactly
97+
/// the same fashion, and Fluent does have code paths which proceed on that
98+
/// assumption. For example, this requirement is the primary reason that a
99+
/// ``TimestampProperty``'s format is represented as a generic type parameter
100+
/// rather than being provided to an initializer.
39101
static func queryValue(_ value: Value) -> DatabaseQuery.Value
40102
}
41103

42104
extension AnyQueryableProperty where Self: QueryableProperty {
105+
/// By default, ``QueryableProperty``s uses ``QueryableProperty/queryValue(_:)-5df0n``
106+
/// to provide its ``queryableValue()-4tkjo``. While it is not strictly required that
107+
/// this be the case, providing an alternative implementation risks violating the
108+
/// "identical encoding for identical property types" rule (see
109+
/// ``QueryableProperty/queryValue(_:)-5df0n``).
43110
public func queryableValue() -> DatabaseQuery.Value? {
44111
return self.value.map { Self.queryValue($0) }
45112
}
46113
}
47114

48115
extension QueryableProperty {
116+
/// Since ``Property/Value`` conforms to ``Swift/Codable``, the default encoding for
117+
/// any ``QueryableProperty``'s value is as a query placeholder and associated parameter
118+
/// binding (bindings are sent to a database driver encoded via ``Swift/Encodable``).
119+
/// See ``DatabaseQuery/Value`` for more details on possible alternative encodings.
49120
public static func queryValue(_ value: Value) -> DatabaseQuery.Value {
50121
.bind(value)
51122
}
52123
}
53124

54-
/// A property which can be addressed as a single value by a query, even if an indirection through
55-
/// a more convenient representation must be made to do so.
56-
///
57-
/// This protocol bridges the gap between `AnyQueryableProperty` - which describes a property whose
58-
/// singular `Value` directly relates to the value stored in the database for that property - and
59-
/// the concrete relations `Parent` and `OptionalParent`, for which the notions of equality and
60-
/// identity are not interchangeable. Via `AnyQueryAddressableProperty`, both of these categories
61-
/// may be handled dynamically, rather than special-casing on the behaviors of the relations.
62-
///
63-
/// In other words, to be "queryable" a property must be equatable, but to be "query-addressable",
64-
/// it need only be identifiable. Any queryable property is automatically query-addressable, but
65-
/// the reverse is not necessarily true.
125+
/// The type-erased form of ``QueryAddressableProperty`` (see below). Both protocols serve to
126+
/// bridge the gap between `AnyQueryableProperty` - which describes a property whose singular
127+
/// `Value` directly corresponds to the value stored in the database for that property - and
128+
/// property types whose `Value` is a derivative of or expansion upon an underlying queryable
129+
/// property. See the discussion of ``QueryAddressableProperty`` itself for additional details.
66130
public protocol AnyQueryAddressableProperty: AnyProperty {
67131
var anyQueryableProperty: AnyQueryableProperty { get }
68132
var queryablePath: [FieldKey] { get }
69133
}
70134

71-
/// The type-bound version of `AnyQueryAddressableProperty`.
135+
/// Marks a property as being "query addressable", meaning that it is either itself queryable
136+
/// (``QueryableProperty`` implies ``QueryAddressableProperty``), or it represents some other
137+
/// single property that _is_ queryable. This allows properties whose purpose is to wrap or
138+
/// otherwise stand in for other properties to be handled generically without the need to
139+
/// add special case exceptions for those property types.
140+
///
141+
/// `@Parent` is the canonical example of an addressable, non-queryable property. It provides
142+
/// the related model as its value, and contains a `@Field` property holding that model's ID.
143+
/// That underlying property means the relation can be "addressed" by a query, but the value
144+
/// type is wrong for it to be directly queryable. Providing the underlying field when the
145+
/// relation is "addressed" allows handling a model's property list (or, say, the property
146+
/// list of a ``Fields`` type being used as a composite ID value) fully generically and without
147+
/// special-casing or having to revisit the logic if additional property types come along.
72148
public protocol QueryAddressableProperty: AnyQueryAddressableProperty, Property {
73149
associatedtype QueryablePropertyType: QueryableProperty where QueryablePropertyType.Model == Self.Model
74150
var queryableProperty: QueryablePropertyType { get }

0 commit comments

Comments
 (0)