Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

define the CollectionStats and StorageStats documents #114

Merged
merged 1 commit into from
Jan 18, 2024
Merged
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
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ let package:Package = .init(name: "swift-mongodb",
[
.target(name: "BSON"),
.target(name: "BSONReflection"),
.target(name: "BSON_OrderedCollections"),
.target(name: "BSON_UUID"),
.target(name: "MongoBuiltins"),
.target(name: "MongoCommands"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ extension Mongo.LatencyStatsDocument
}
}
}
extension Mongo.LatencyStatsDocument:ExpressibleByDictionaryLiteral
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ extension Mongo.StorageStatsDocument
}
}
}
extension Mongo.StorageStatsDocument:ExpressibleByDictionaryLiteral
{
}
32 changes: 32 additions & 0 deletions Sources/MongoDB/Mongo.Session (ext).swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
extension Mongo.Session
{
@_spi(session) public
func stats(
collection:Mongo.Namespaced<Mongo.Collection>) async throws -> Mongo.CollectionStats?
{
let command:Mongo.Aggregate<Mongo.Single<Mongo.CollectionStats>> = .init(
collection.name,
pipeline: .init
{
$0[.collectionStats] = .init
{
$0[.storageStats] = [:]
}

// A typical collection stats output document contains a huge amount of
// data, most of which is redundant.
$0[.project] = .init
{
for key:Mongo.CollectionStats.Storage.CodingKey
in Mongo.CollectionStats.Storage.CodingKey.allCases
{
$0[Mongo.CollectionStats[.storage] /
Mongo.CollectionStats.Storage[key]] = true
}
}
},
stride: nil)

return try await self.run(command: command, against: collection.database)
}
}
197 changes: 102 additions & 95 deletions Sources/MongoDBTests/Aggregate/Aggregate.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@_spi(session)
import MongoDB
import MongoTesting

Expand All @@ -7,119 +8,125 @@ struct Aggregate<Configuration>:MongoTestBattery where Configuration:MongoTestCo
func run(tests:TestGroup, pool:Mongo.SessionPool, database:Mongo.Database) async throws
{
let session:Mongo.Session = try await .init(from: pool)
let collection:Mongo.Collection = "articles"

if let tests:TestGroup = tests / "articles-example"
await tests.do
{
let collection:Mongo.Collection = "articles"
let expected:Mongo.InsertResponse = .init(inserted: 4)
let response:Mongo.InsertResponse = try await session.run(
command: Mongo.Insert.init(collection, encoding: [
.init(id: 0x5276_9ea0_f3dc_6ead_47c9_a1b2,
author: "barbie",
title: "Brain Surgery for Beginners",
views: 527,
tags: ["medicine", "neuroscience", "education"]),

await tests.do
{
let expected:Mongo.InsertResponse = .init(inserted: 4)
let response:Mongo.InsertResponse = try await session.run(
command: Mongo.Insert.init(collection, encoding: [
.init(id: 0x5276_9ea0_f3dc_6ead_47c9_a1b2,
author: "barbie",
title: "Brain Surgery for Beginners",
views: 527,
tags: ["medicine", "neuroscience", "education"]),

.init(id: 0x5276_9ea0_f3dc_6ead_47c9_a1b3,
author: "barbie",
title: "NATO Expansion and Unipolar Norms: A Review",
views: 760,
tags: ["politics", "history"]),
.init(id: 0x5276_9ea0_f3dc_6ead_47c9_a1b3,
author: "barbie",
title: "NATO Expansion and Unipolar Norms: A Review",
views: 760,
tags: ["politics", "history"]),

.init(id: 0x5276_9ea0_f3dc_6ead_47c9_a1b4,
author: "raquelle",
title: "A Brief History of Raquelle (Vol. 1)",
views: 288,
tags: ["history", "autobiography"]),
.init(id: 0x5276_9ea0_f3dc_6ead_47c9_a1b4,
author: "raquelle",
title: "A Brief History of Raquelle (Vol. 1)",
views: 288,
tags: ["history", "autobiography"]),

.init(id: 0x5276_9ea0_f3dc_6ead_47c9_a1b5,
author: "raquelle",
title: "A Brief History of Raquelle (Vol. 2)",
views: 115,
tags: ["history", "autobiography"]),
] as [Article]),
against: database)
.init(id: 0x5276_9ea0_f3dc_6ead_47c9_a1b5,
author: "raquelle",
title: "A Brief History of Raquelle (Vol. 2)",
views: 115,
tags: ["history", "autobiography"]),
] as [Article]),
against: database)

tests.expect(response ==? expected)
}
tests.expect(response ==? expected)
}

await tests.do
{
let expected:[TagStats] =
[
.init(id: "medicine", count: 1),
.init(id: "neuroscience", count: 1),
.init(id: "education", count: 1),
.init(id: "history", count: 3),
.init(id: "politics", count: 1),
.init(id: "autobiography", count: 2),
]
let response:[TagStats] = try await session.run(
command: Mongo.Aggregate<Mongo.Cursor<TagStats>>.init(collection,
writeConcern: .majority,
readConcern: .majority,
pipeline: .init
await tests.do
{
let expected:[TagStats] =
[
.init(id: "medicine", count: 1),
.init(id: "neuroscience", count: 1),
.init(id: "education", count: 1),
.init(id: "history", count: 3),
.init(id: "politics", count: 1),
.init(id: "autobiography", count: 2),
]
let response:[TagStats] = try await session.run(
command: Mongo.Aggregate<Mongo.Cursor<TagStats>>.init(collection,
writeConcern: .majority,
readConcern: .majority,
pipeline: .init
{
$0[.project] = .init { $0[Article[.tags]] = 1 }
$0[.unwind] = Article[.tags]
$0[.group] = .init
{
$0[.project] = .init { $0[Article[.tags]] = 1 }
$0[.unwind] = Article[.tags]
$0[.group] = .init
{
$0[.id] = Article[.tags]

$0[TagStats[.count]] = .init { $0[.sum] = 1 }
}
},
stride: 10),
against: database)
{
try await $0.reduce(into: []) { $0 += $1 }
}
$0[.id] = Article[.tags]

tests.expect(response **? expected)
$0[TagStats[.count]] = .init { $0[.sum] = 1 }
}
},
stride: 10),
against: database)
{
try await $0.reduce(into: []) { $0 += $1 }
}

await tests.do
{
let expected:[AuthorStats] =
[
.init(id: "barbie", views: 527 + 760),
.init(id: "raquelle", views: 288 + 115),
]
let response:[AuthorStats] = try await session.run(
command: Mongo.Aggregate<Mongo.Cursor<AuthorStats>>.init(collection,
writeConcern: .majority,
readConcern: .majority,
pipeline: .init
tests.expect(response **? expected)
}

await tests.do
{
let expected:[AuthorStats] =
[
.init(id: "barbie", views: 527 + 760),
.init(id: "raquelle", views: 288 + 115),
]
let response:[AuthorStats] = try await session.run(
command: Mongo.Aggregate<Mongo.Cursor<AuthorStats>>.init(collection,
writeConcern: .majority,
readConcern: .majority,
pipeline: .init
{
$0.stage
{
$0.stage
$0[.project] = .init
{
$0[.project] = .init
{
$0[Article[.author]] = 1
$0[Article[.views]] = 1
}
$0[Article[.author]] = 1
$0[Article[.views]] = 1
}
$0.stage
}
$0.stage
{
$0[.group] = .init
{
$0[.group] = .init
{
$0[.id] = Article[.author]
$0[.id] = Article[.author]

$0[Article[.views]] = .init { $0[.sum] = Article[.views] }
}
$0[Article[.views]] = .init { $0[.sum] = Article[.views] }
}
},
stride: 10),
against: database)
{
try await $0.reduce(into: []) { $0 += $1 }
}

tests.expect(response **? expected)
}
},
stride: 10),
against: database)
{
try await $0.reduce(into: []) { $0 += $1 }
}

tests.expect(response **? expected)
}

guard
let tests:TestGroup = tests / "CollectionStats"
else
{
return
}

let _:Mongo.CollectionStats? = tests.expect(value: try await session.stats(
collection: database | collection))
}
}
72 changes: 72 additions & 0 deletions Sources/MongoQL/Collections/Mongo.CollectionStats.Storage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import BSON
import BSON_OrderedCollections
import OrderedCollections

extension Mongo.CollectionStats
{
@frozen public
struct Storage:Sendable
{
/// The portion of ``storageSize`` that is available for reuse.
public
let storageFree:Int
/// The total **compressed** size of all the documents in the collection.
public
let storageSize:Int
/// The total **uncompressed** size of all the documents in the collection.
public
let logicalSize:Int
/// The total size of all the collection’s indexes.
public
let indexesSize:Int
/// The individual sizes of each of the collection’s indexes.
public
let indexSizes:OrderedDictionary<BSON.Key, Int>
/// The number of documents in the collection.
public
let count:Int

@inlinable public
init(storageFree:Int,
storageSize:Int,
logicalSize:Int,
indexesSize:Int,
indexSizes:OrderedDictionary<BSON.Key, Int>,
count:Int)
{
self.storageFree = storageFree
self.storageSize = storageSize
self.logicalSize = logicalSize
self.indexesSize = indexesSize
self.indexSizes = indexSizes
self.count = count
}
}
}
extension Mongo.CollectionStats.Storage:MongoMasterCodingModel
{
@frozen public
enum CodingKey:String, Sendable, CaseIterable
{
case storageFree = "freeStorageSize"
case storageSize = "storageSize"
case logicalSize = "size"
case indexesSize = "totalIndexSize"
case indexSizes = "indexSizes"
case count
}
}
extension Mongo.CollectionStats.Storage:BSONDocumentDecodable
{
@inlinable public
init(bson:BSON.DocumentDecoder<CodingKey, some RandomAccessCollection<UInt8>>) throws
{
self.init(
storageFree: try bson[.storageFree]?.decode() ?? 0,
storageSize: try bson[.storageSize].decode(),
logicalSize: try bson[.logicalSize].decode(),
indexesSize: try bson[.indexesSize].decode(),
indexSizes: try bson[.indexSizes].decode(),
count: try bson[.count].decode())
}
}
33 changes: 33 additions & 0 deletions Sources/MongoQL/Collections/Mongo.CollectionStats.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import BSON

extension Mongo
{
@frozen public
struct CollectionStats:Sendable
{
public
let storage:Storage

@inlinable public
init(storage:Storage)
{
self.storage = storage
}
}
}
extension Mongo.CollectionStats:MongoMasterCodingModel
{
@frozen public
enum CodingKey:String, Sendable
{
case storage = "storageStats"
}
}
extension Mongo.CollectionStats:BSONDocumentDecodable
{
@inlinable public
init(bson:BSON.DocumentDecoder<CodingKey, some RandomAccessCollection<UInt8>>) throws
{
self.init(storage: try bson[.storage].decode())
}
}