diff --git a/Snippets/ExampleProjectionDocuments.swift b/Snippets/ExampleProjectionDocuments.swift index 1c428d9e..dec3fee3 100644 --- a/Snippets/ExampleProjectionDocuments.swift +++ b/Snippets/ExampleProjectionDocuments.swift @@ -5,30 +5,15 @@ func ExampleProjectionDocuments() { let _:Mongo.ProjectionDocument = .init { - $0["expression"] = .expr + $0["expression"] { $0[.abs] = "$field" } + $0["key1"] = true + $0["key2"] { $0[.literal] = 1 } + $0["a"] { $0[.slice] = 1 } + $0["b"] { $0[.slice] = (1, 1) } + $0["c"] { $0[.meta] = .indexKey } + $0["d"] { - $0[.abs] = "$field" - } - $0["key1"] = 1 - $0["key2"] = .expr - { - $0[.literal] = 1 - } - $0["a"] = .init - { - $0[.slice] = 1 - } - $0["b"] = .init - { - $0[.slice] = (1, 1) - } - $0["c"] = .init - { - $0[.meta] = .indexKey - } - $0["d"] = .init - { - $0[.first] = .init + $0[.first] { $0[.or] { diff --git a/Sources/MongoBuiltins/Documents/Predicate/Mongo.PredicateEncodable.swift b/Sources/MongoBuiltins/Documents/Predicate/Mongo.PredicateEncodable.swift new file mode 100644 index 00000000..2b0a9582 --- /dev/null +++ b/Sources/MongoBuiltins/Documents/Predicate/Mongo.PredicateEncodable.swift @@ -0,0 +1,8 @@ +extension Mongo +{ + public + protocol PredicateEncodable + { + func encode(to predicate:inout PredicateEncoder) + } +} diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionDocument.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionDocument.swift index 3cf15a5b..2f7e9b12 100644 --- a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionDocument.swift +++ b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionDocument.swift @@ -4,7 +4,7 @@ import MongoABI extension Mongo { @frozen public - struct ProjectionDocument:Mongo.EncodableDocument, Sendable + struct ProjectionDocument:Sendable { public var bson:BSON.Document @@ -16,35 +16,8 @@ extension Mongo } } } -extension Mongo.ProjectionDocument +extension Mongo.ProjectionDocument:Mongo.EncodableDocument { - /// Encodes a ``ProjectionOperator``. - /// - /// This does not require [`@_disfavoredOverload`](), because - /// ``ProjectionOperator`` has no subscripts that accept string - /// literals, so it will never conflict with ``BSON.Document``. - @inlinable public - subscript(path:Mongo.AnyKeyPath) -> Mongo.ProjectionOperator? - { - get - { - nil - } - set(value) - { - value?.encode(to: &self.bson[with: path.stem]) - } - } - @inlinable public - subscript(path:Mongo.AnyKeyPath) -> Encodable? where Encodable:BSONEncodable - { - get - { - nil - } - set(value) - { - value?.encode(to: &self.bson[with: path.stem]) - } - } + public + typealias Encoder = Mongo.ProjectionEncoder } diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionEncodable.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionEncodable.swift new file mode 100644 index 00000000..f5827d94 --- /dev/null +++ b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionEncodable.swift @@ -0,0 +1,8 @@ +extension Mongo +{ + public + protocol ProjectionEncodable + { + func encode(to projection:inout ProjectionEncoder) + } +} diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionEncoder.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionEncoder.swift new file mode 100644 index 00000000..7ed3acf1 --- /dev/null +++ b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionEncoder.swift @@ -0,0 +1,138 @@ +import BSON +import MongoABI + +extension Mongo +{ + @frozen public + struct ProjectionEncoder:Sendable + { + @usableFromInline + var bson:BSON.DocumentEncoder + + @inlinable internal + init(bson:BSON.DocumentEncoder) + { + self.bson = bson + } + } +} +extension Mongo.ProjectionEncoder:BSON.Encoder +{ + @inlinable public + init(_ output:consuming BSON.Output) { self.init(bson: .init(output)) } + + @inlinable public consuming + func move() -> BSON.Output { self.bson.move() } + + @inlinable public static + var type:BSON.AnyType { .document } +} + +extension Mongo.ProjectionEncoder +{ + @available(*, unavailable, message: "Use the boolean subscript instead.") + @inlinable public + subscript(path:Mongo.AnyKeyPath) -> Int? + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.bson[with: path]) + } + } + + @inlinable public + subscript(path:Mongo.AnyKeyPath) -> Bool? + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.bson[with: path]) + } + } + + /// Encodes a nested projection document. + @inlinable public + subscript(path:Mongo.AnyKeyPath, yield:(inout Mongo.ProjectionEncoder) -> ()) -> Void + { + mutating get + { + yield(&self.bson[with: path][as: Mongo.ProjectionEncoder.self]) + } + } + + /// Encodes a projection expression. + @inlinable public + subscript(path:Mongo.AnyKeyPath, yield:(inout Mongo.ExpressionEncoder) -> ()) -> Void + { + mutating get + { + yield(&self.bson[with: path][as: Mongo.ExpressionEncoder.self]) + } + } + + /// Encodes a projection operator. + @inlinable public + subscript(path:Mongo.AnyKeyPath, + yield:(inout Mongo.ProjectionOperatorEncoder) -> ()) -> Void + { + mutating get + { + yield(&self.bson[with: path][as: Mongo.ProjectionOperatorEncoder.self]) + } + } + + /// Encodes a projection operator from a model type. + @inlinable public + subscript(path:Mongo.AnyKeyPath) -> Operator? + where Operator:Mongo.ProjectionOperatorEncodable + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.bson[with: path][as: Mongo.ProjectionOperatorEncoder.self]) + } + } +} +@available(*, deprecated) +extension Mongo.ProjectionEncoder +{ + /// Encodes a ``ProjectionOperator``. + /// + /// This does not require `@_disfavoredOverload`, because ``ProjectionOperator`` has no + /// subscripts that accept string literals, so it will never conflict with + /// ``BSON.Document``. + @inlinable public + subscript(path:Mongo.AnyKeyPath) -> Mongo.ProjectionOperator? + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.bson[with: path.stem]) + } + } + @inlinable public + subscript(path:Mongo.AnyKeyPath) -> Encodable? where Encodable:BSONEncodable + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.bson[with: path.stem]) + } + } +} diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionMetadata.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionMetadata.swift new file mode 100644 index 00000000..a83409d6 --- /dev/null +++ b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionMetadata.swift @@ -0,0 +1,14 @@ +import BSON + +extension Mongo +{ + @frozen public + enum ProjectionMetadata:String, Hashable, Sendable + { + case textScore + case indexKey + } +} +extension Mongo.ProjectionMetadata:BSONDecodable, BSONEncodable +{ +} diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.First.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.First.swift deleted file mode 100644 index 2d023817..00000000 --- a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.First.swift +++ /dev/null @@ -1,17 +0,0 @@ -extension Mongo.ProjectionOperator -{ - @frozen public - enum First:String, Hashable, Sendable - { - case first = "$elemMatch" - } -} -extension Mongo.ProjectionOperator.First -{ - @available(*, unavailable, renamed: "first") - public static - var elemMatch:Self - { - .first - } -} diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.Meta.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.Meta.swift deleted file mode 100644 index c9001f30..00000000 --- a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.Meta.swift +++ /dev/null @@ -1,8 +0,0 @@ -extension Mongo.ProjectionOperator -{ - @frozen public - enum Meta:String, Hashable, Sendable - { - case meta = "$meta" - } -} diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.Metadata.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.Metadata.swift deleted file mode 100644 index 551a14e9..00000000 --- a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.Metadata.swift +++ /dev/null @@ -1,14 +0,0 @@ -import BSON - -extension Mongo.ProjectionOperator -{ - @frozen public - enum Metadata:String, Hashable, Sendable - { - case textScore - case indexKey - } -} -extension Mongo.ProjectionOperator.Metadata:BSONDecodable, BSONEncodable -{ -} diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.Slice.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.Slice.swift deleted file mode 100644 index 535f31cf..00000000 --- a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.Slice.swift +++ /dev/null @@ -1,8 +0,0 @@ -extension Mongo.ProjectionOperator -{ - @frozen public - enum Slice:String, Hashable, Sendable - { - case slice = "$slice" - } -} diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.swift index 640b1b9d..a0b4187f 100644 --- a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.swift +++ b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperator.swift @@ -3,7 +3,7 @@ import BSON extension Mongo { @frozen public - struct ProjectionOperator:Mongo.EncodableDocument, Sendable + struct ProjectionOperator:Sendable { public var bson:BSON.Document @@ -15,74 +15,8 @@ extension Mongo } } } -extension Mongo.ProjectionOperator +extension Mongo.ProjectionOperator:Mongo.EncodableDocument { - @inlinable public - subscript(key:First) -> Mongo.PredicateDocument? - { - get - { - nil - } - set(value) - { - value?.encode(to: &self.bson[with: key]) - } - } -} -extension Mongo.ProjectionOperator -{ - @inlinable public - subscript(key:Meta) -> Metadata? - { - get - { - nil - } - set(value) - { - value?.encode(to: &self.bson[with: key]) - } - } -} -extension Mongo.ProjectionOperator -{ - @inlinable public - subscript(key:Slice) -> Distance? - where Distance:BSONEncodable - { - get - { - nil - } - set(value) - { - value?.encode(to: &self.bson[with: key]) - } - } - @inlinable public - subscript(key:Slice) -> (at:Index?, count:Count?) - where Index:BSONEncodable, Count:BSONEncodable - { - get - { - (nil, nil) - } - set(value) - { - guard let count:Count = value.count - else - { - return - } - - { - if let index:Index = value.at - { - $0.append(index) - } - $0.append(count) - } (&self.bson[with: key][as: BSON.ListEncoder.self]) - } - } + public + typealias Encoder = Mongo.ProjectionOperatorEncoder } diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperatorEncodable.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperatorEncodable.swift new file mode 100644 index 00000000..788eb17c --- /dev/null +++ b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperatorEncodable.swift @@ -0,0 +1,8 @@ +extension Mongo +{ + public + protocol ProjectionOperatorEncodable + { + func encode(to operator:inout ProjectionOperatorEncoder) + } +} diff --git a/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperatorEncoder.swift b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperatorEncoder.swift new file mode 100644 index 00000000..b12522b6 --- /dev/null +++ b/Sources/MongoBuiltins/Documents/Projection/Mongo.ProjectionOperatorEncoder.swift @@ -0,0 +1,147 @@ +import BSON +import MongoABI + +extension Mongo +{ + @frozen public + struct ProjectionOperatorEncoder:Sendable + { + @usableFromInline + var bson:BSON.DocumentEncoder + + @inlinable internal + init(bson:BSON.DocumentEncoder) + { + self.bson = bson + } + } +} +extension Mongo.ProjectionOperatorEncoder:BSON.Encoder +{ + @inlinable public + init(_ output:consuming BSON.Output) { self.init(bson: .init(output)) } + + @inlinable public consuming + func move() -> BSON.Output { self.bson.move() } + + @inlinable public static + var type:BSON.AnyType { .document } +} + +extension Mongo.ProjectionOperatorEncoder +{ + @frozen public + enum First:String, Sendable + { + case first = "$elemMatch" + + @available(*, unavailable, renamed: "first") + public static + var elemMatch:Self { .first } + } + + @inlinable public + subscript(key:First, yield:(inout Mongo.PredicateEncoder) -> ()) -> Void + { + mutating get + { + yield(&self.bson[with: key][as: Mongo.PredicateEncoder.self]) + } + } + + @inlinable public + subscript(key:First) -> Predicate? where Predicate:Mongo.PredicateEncodable + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.bson[with: key][as: Mongo.PredicateEncoder.self]) + } + } + + @available(*, deprecated) + @inlinable public + subscript(key:First) -> Mongo.PredicateDocument? + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.bson[with: key]) + } + } +} +extension Mongo.ProjectionOperatorEncoder +{ + @frozen public + enum Meta:String, Sendable + { + case meta = "$meta" + } + + @inlinable public + subscript(key:Meta) -> Mongo.ProjectionMetadata? + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.bson[with: key]) + } + } +} +extension Mongo.ProjectionOperatorEncoder +{ + /// Not to be confused with ``Mongo/ExpressionEncoder.Slice`` which can also appear in the + /// same position as this operator. + @frozen public + enum Slice:String, Sendable + { + case slice = "$slice" + } + + @inlinable public + subscript(key:Slice) -> Int? + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.bson[with: key]) + } + } + + @inlinable public + subscript(key:Slice) -> (at:Int?, count:Int?) + { + get + { + (nil, nil) + } + set(value) + { + guard let count:Int = value.count + else + { + return + } + + { + if let index:Int = value.at + { + $0.append(index) + } + $0.append(count) + } (&self.bson[with: key][as: BSON.ListEncoder.self]) + } + } +} diff --git a/Sources/MongoDBTests/Aggregate/Aggregate.swift b/Sources/MongoDBTests/Aggregate/Aggregate.swift index 38f3bdb3..748c34d2 100644 --- a/Sources/MongoDBTests/Aggregate/Aggregate.swift +++ b/Sources/MongoDBTests/Aggregate/Aggregate.swift @@ -61,7 +61,7 @@ struct Aggregate:MongoTestBattery where Configuration:MongoTestCo readConcern: .majority, stride: 10) { - $0[stage: .project] = .init { $0[Article[.tags]] = 1 } + $0[stage: .project] = .init { $0[Article[.tags]] = true } $0[stage: .unwind] = Article[.tags] $0[stage: .group] = .init { @@ -93,8 +93,8 @@ struct Aggregate:MongoTestBattery where Configuration:MongoTestCo { $0[stage: .project] = .init { - $0[Article[.author]] = 1 - $0[Article[.views]] = 1 + $0[Article[.author]] = true + $0[Article[.views]] = true } $0[stage: .group] = .init { diff --git a/Sources/MongoDBTests/Find/Find.swift b/Sources/MongoDBTests/Find/Find.swift index 306e628b..4e4d0c6f 100644 --- a/Sources/MongoDBTests/Find/Find.swift +++ b/Sources/MongoDBTests/Find/Find.swift @@ -153,13 +153,10 @@ struct Find:MongoTestBattery where Configuration:MongoTestConfigu command: Mongo.Find>>.init(collection, stride: 10) { - $0[.projection] = .init + $0[.projection] { - $0["_id"] = 1 as Int32 - $0["value"] = .expr - { - $0[.add] = ("$value", 5) - } + $0["_id"] = true + $0["value"] { $0[.add] = ("$value", 5) } } }, against: database) diff --git a/Sources/MongoQL/Commands/CreateIndexes/Mongo.CreateIndexStatementEncoder.swift b/Sources/MongoQL/Commands/CreateIndexes/Mongo.CreateIndexStatementEncoder.swift index a61aec9e..5df0f1c0 100644 --- a/Sources/MongoQL/Commands/CreateIndexes/Mongo.CreateIndexStatementEncoder.swift +++ b/Sources/MongoQL/Commands/CreateIndexes/Mongo.CreateIndexStatementEncoder.swift @@ -170,6 +170,32 @@ extension Mongo.CreateIndexStatementEncoder case wildcardProjection } + /// Encodes a projection document. + @inlinable public + subscript(key:WildcardProjection, yield:(inout Mongo.ProjectionEncoder) -> ()) -> Void + { + mutating get + { + yield(&self.bson[with: key][as: Mongo.ProjectionEncoder.self]) + } + } + + /// Encodes a projection document from a model type. + @inlinable public + subscript(key:WildcardProjection) -> ProjectionDocument? + where ProjectionDocument:Mongo.ProjectionEncodable + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.bson[with: key][as: Mongo.ProjectionEncoder.self]) + } + } + + @available(*, deprecated) @inlinable public subscript(key:WildcardProjection) -> Mongo.ProjectionDocument? { diff --git a/Sources/MongoQL/Commands/Find/Mongo.Find.swift b/Sources/MongoQL/Commands/Find/Mongo.Find.swift index f898fa89..c6abb694 100644 --- a/Sources/MongoQL/Commands/Find/Mongo.Find.swift +++ b/Sources/MongoQL/Commands/Find/Mongo.Find.swift @@ -280,6 +280,32 @@ extension Mongo.Find case projection } + /// Encodes a projection document. + @inlinable public + subscript(key:Projection, yield:(inout Mongo.ProjectionEncoder) -> ()) -> Void + { + mutating get + { + yield(&self.fields[with: key][as: Mongo.ProjectionEncoder.self]) + } + } + + /// Encodes a projection document from a model type. + @inlinable public + subscript(key:Projection) -> ProjectionDocument? + where ProjectionDocument:Mongo.ProjectionEncodable + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.fields[with: key][as: Mongo.ProjectionEncoder.self]) + } + } + + @available(*, deprecated) @inlinable public subscript(key:Projection) -> Mongo.ProjectionDocument? { diff --git a/Sources/MongoQL/Commands/FindAndModify/Mongo.FindAndModify.swift b/Sources/MongoQL/Commands/FindAndModify/Mongo.FindAndModify.swift index d4939803..0d33ae3f 100644 --- a/Sources/MongoQL/Commands/FindAndModify/Mongo.FindAndModify.swift +++ b/Sources/MongoQL/Commands/FindAndModify/Mongo.FindAndModify.swift @@ -119,6 +119,32 @@ extension Mongo.FindAndModify case fields } + /// Encodes a projection document. + @inlinable public + subscript(key:Fields, yield:(inout Mongo.ProjectionEncoder) -> ()) -> Void + { + mutating get + { + yield(&self.fields[with: key][as: Mongo.ProjectionEncoder.self]) + } + } + + /// Encodes a projection document from a model type. + @inlinable public + subscript(key:Fields) -> ProjectionDocument? + where ProjectionDocument:Mongo.ProjectionEncodable + { + get + { + nil + } + set(value) + { + value?.encode(to: &self.fields[with: key][as: Mongo.ProjectionEncoder.self]) + } + } + + @available(*, deprecated) @inlinable public subscript(key:Fields) -> Mongo.ProjectionDocument? {