Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions Sources/StructuredQueriesCore/Internal/KeyPath+Sendable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#if compiler(>=6)
public typealias _SendableAnyKeyPath = any AnyKeyPath & Sendable
public typealias _SendablePartialKeyPath<Root> = any PartialKeyPath<Root> & Sendable
public typealias _SendableKeyPath<Root, Value> = any KeyPath<Root, Value> & Sendable
public typealias _SendableWritableKeyPath<Root, Value> = any WritableKeyPath<Root, Value>
& Sendable
public typealias _SendableReferenceWritableKeyPath<Root, Value> = any ReferenceWritableKeyPath<
Root, Value
>
& Sendable
#else
public typealias _SendableAnyKeyPath = AnyKeyPath
public typealias _SendablePartialKeyPath<Root> = PartialKeyPath<Root>
public typealias _SendableKeyPath<Root, Value> = KeyPath<Root, Value>
public typealias _SendableWritableKeyPath<Root, Value> = WritableKeyPath<Root, Value>
public typealias _SendableReferenceWritableKeyPath<Root, Value> = ReferenceWritableKeyPath<
Root, Value
>
#endif

// NB: Dynamic member lookup does not currently support sendable key paths and even breaks
// autocomplete.
//
// * https://github.com/swiftlang/swift/issues/77035
// * https://github.com/swiftlang/swift/issues/77105
extension _AppendKeyPath {
@_transparent
func unsafeSendable() -> _SendableAnyKeyPath
where Self == AnyKeyPath {
#if compiler(>=6)
unsafeBitCast(self, to: _SendableAnyKeyPath.self)
#else
self
#endif
}

@_transparent
func unsafeSendable<Root>() -> _SendablePartialKeyPath<Root>
where Self == PartialKeyPath<Root> {
#if compiler(>=6)
unsafeBitCast(self, to: _SendablePartialKeyPath<Root>.self)
#else
self
#endif
}

@_transparent
func unsafeSendable<Root, Value>() -> _SendableKeyPath<Root, Value>
where Self == KeyPath<Root, Value> {
#if compiler(>=6)
unsafeBitCast(self, to: _SendableKeyPath<Root, Value>.self)
#else
self
#endif
}

@_transparent
func unsafeSendable<Root, Value>() -> _SendableWritableKeyPath<Root, Value>
where Self == WritableKeyPath<Root, Value> {
#if compiler(>=6)
unsafeBitCast(self, to: _SendableWritableKeyPath<Root, Value>.self)
#else
self
#endif
}

@_transparent
func unsafeSendable<Root, Value>() -> _SendableReferenceWritableKeyPath<Root, Value>
where Self == ReferenceWritableKeyPath<Root, Value> {
#if compiler(>=6)
unsafeBitCast(self, to: _SendableReferenceWritableKeyPath<Root, Value>.self)
#else
self
#endif
}
}
6 changes: 6 additions & 0 deletions Sources/StructuredQueriesCore/Never.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
extension Never: Table {
public typealias QueryValue = Never

public struct TableColumns: TableDefinition {
public typealias QueryValue = Never

Expand All @@ -11,6 +13,10 @@ extension Never: Table {

public static let tableName = "nevers"

public var queryFragment: QueryFragment {
fatalError()
}

public init(decoder: inout some QueryDecoder) throws {
throw NotDecodable()
}
Expand Down
10 changes: 4 additions & 6 deletions Sources/StructuredQueriesCore/Operators.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
extension QueryExpression where QueryValue: QueryBindable {
extension QueryExpression where QueryValue: _OptionalPromotable {
/// A predicate expression indicating whether two query expressions are equal.
///
/// ```swift
Expand Down Expand Up @@ -861,10 +861,8 @@ extension QueryExpression where QueryValue: QueryBindable {
_ lowerBound: some QueryExpression<QueryValue>,
and upperBound: some QueryExpression<QueryValue>
) -> some QueryExpression<Bool> {
BinaryOperator(
lhs: self,
operator: "BETWEEN",
rhs: SQLQueryExpression("\(lowerBound) AND \(upperBound)")
SQLQueryExpression(
"\(self) BETWEEN \(lowerBound) AND \(upperBound)"
)
}
}
Expand Down Expand Up @@ -963,7 +961,7 @@ struct BinaryOperator<QueryValue>: QueryExpression {
}

var queryFragment: QueryFragment {
"(\(lhs) \(`operator`) \(rhs))"
"((\(lhs)) \(`operator`) (\(rhs)))"
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/StructuredQueriesCore/QueryRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Foundation
/// offsets from the Unix epoch, or double Julian day numbers. This library defaults to ISO-8601
/// text, but provides ``Foundation/Date/UnixTimeRepresentation`` to represent a date as an integer,
/// and ``Foundation/Date/JulianDayRepresentation`` to represent a date as a floating point number.
public protocol QueryRepresentable<QueryOutput>: QueryDecodable {
public protocol QueryRepresentable<QueryOutput>: QueryDecodable, QueryExpression {
/// The Swift type this value is ultimately decoded to.
associatedtype QueryOutput

Expand Down
43 changes: 43 additions & 0 deletions Sources/StructuredQueriesCore/SubtableColumns.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@dynamicMemberLookup
public struct SubtableColumns<Root: Table, Value: Table>: QueryExpression {
public typealias QueryValue = Value

public static func allColumns(keyPath: KeyPath<Root, Value> & Sendable) -> [any TableColumnExpression] {
return Value.TableColumns.allColumns.map { column in
func open<R, V>(
_ column: some TableColumnExpression<R, V>
) -> any TableColumnExpression {
let keyPath = keyPath.appending(
path: unsafeDowncast(column.keyPath, to: KeyPath<Value, V.QueryOutput>.self)
)
return TableColumn<Root, V>(
column.name,
keyPath: keyPath.unsafeSendable(),
default: column.defaultValue
)
}
return open(column)
}
}

let keyPath: KeyPath<Root, Value> & Sendable

public init(keyPath: KeyPath<Root, Value> & Sendable) {
self.keyPath = keyPath
}

public subscript<Member>(
dynamicMember keyPath: KeyPath<Value.TableColumns, TableColumn<Value, Member>> & Sendable
) -> TableColumn<Root, Member> {
let column = Value.columns[keyPath: keyPath]
return TableColumn<Root, Member>(
column.name,
keyPath: self.keyPath.appending(path: column._keyPath).unsafeSendable(),
default: column.defaultValue
)
}

public var queryFragment: QueryFragment {
Value.columns.queryFragment
}
}
3 changes: 2 additions & 1 deletion Sources/StructuredQueriesCore/TableColumn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
///
/// This protocol has a single conformance, ``TableColumn``, and simply provides type erasure over
/// a table's columns. You should not conform to this protocol directly.
public protocol TableColumnExpression<Root, Value>: QueryExpression where Value == QueryValue {
public protocol TableColumnExpression<Root, Value>: QueryExpression
where Value == QueryValue, Value.QueryOutput: Sendable {
associatedtype Root: Table
associatedtype Value: QueryRepresentable & QueryBindable

Expand Down
9 changes: 9 additions & 0 deletions Sources/StructuredQueriesCore/Updates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,12 @@ extension Updates: QueryExpression {
"SET \(updates.map { "\(quote: $0) = \($1)" }.joined(separator: ", "))"
}
}

extension Updates {
public subscript<Member: Table>(
dynamicMember keyPath: KeyPath<Base.TableColumns, SubtableColumns<Base, Member>>
) -> Updates<Member.QueryValue> {
get { Updates<Member.QueryValue> { _ in } }
set { updates.append(contentsOf: newValue.updates) }
}
}
Loading
Loading