I’m trying to use Predicate
with generics but I keep getting the following compilation error :
Cannot convert value of type 'PredicateExpressions.Equal<PredicateExpressions.ConditionalCast<PredicateExpressions.KeyPath<PredicateExpressions.Variable<E.EntityType>, E.EntityType.PrimaryKey?>, Key>, PredicateExpressions.Value<Optional>>' to closure result type 'any StandardPredicateExpression'
I have the following :
public protocol Entity {
associatedtype DomainModel
associatedtype PrimaryKey: Equatable
/// The entity's domain model
var domainModel: DomainModel { get }
/// The entity's primary key
var primaryKey: PrimaryKey { get }
}
public protocol DomainEntity {
associatedtype EntityType: PersistentModel & Entity
var entity: EntityType? { get }
}
@ModelActor
actor DataSource<E: DomainEntity> {
typealias Model = E.EntityType.DomainModel
func get<Key: Equatable>(by key: Key) throws -> Model? {
let predicate: Predicate<E.EntityType> = #Predicate {
($0.primaryKey as? Key) == key // I’m having the error here
}
let fetchDescriptor = FetchDescriptor<E.EntityType>(predicate: predicate)
guard let entity: E.EntityType = try modelContext.fetch(fetchDescriptor).first else {
return nil
}
guard let model = entity.domainModel as? Model else {
throw DatabaseError.conversionFailed
}
return model
}
}
I get that the error is kind of explaining that it can’t figure out the underlying types, but as both properties are Equatable
, it shouldn’t be an issue.
Does anyone have an idea of how I could do such a comparison using predicates? Or is there another way?
#Predicate
requires you to write a StandardPredicateExpression
, which inherits from Codable
.
For the predicate to be a StandardPredicateExpression
, constant values such as the parameter key
should also be Codable
. Otherwise it would be no more than a regular PredicateExpression
.
Try expanding the #Predicate
macro and see how the parameter key
becomes PredicateExpressions.build_Arg(key)
. This creates a PredicateExpressions.Value<Key>
, which only conforms to StandardPredicateExpression
when Key
is Codable
.
The generic type parameter Key
should be constrained to Codable
as well:
func get<Key: Equatable & Codable>(by key: Key) throws -> Model?
That said, wouldn't it make more sense to just use E.EntityType.PrimaryKey
, instead of a separate type parameter? You don't need the as? Key
cast at all.
func get(by key: E.EntityType.PrimaryKey) throws -> Model? {
let predicate: Predicate<E.EntityType> = #Predicate {
$0.primaryKey == key
}
...
}
This means that PrimaryKey
should be constrained to Codable
too:
associatedtype PrimaryKey: Equatable, Codable