diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1af6d10..e40ce14 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: permissions: contents: read runs-on: ubuntu-latest - container: swift:6.2-noble + container: swift:6.3-noble steps: - name: Check out sql-kit uses: actions/checkout@v6 @@ -54,7 +54,7 @@ jobs: strategy: fail-fast: false matrix: - swift-image: ['swift:6.2-noble'] + swift-image: ['swift:6.3-noble'] driver: - { sqlkit: 'sqlite-kit', fluent: 'fluent-sqlite-driver' } - { sqlkit: 'mysql-kit', fluent: 'fluent-mysql-driver' } diff --git a/Package.swift b/Package.swift index 661348e..48cc9be 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:6.0 +// swift-tools-version:6.1 import PackageDescription let package = Package( @@ -38,6 +38,8 @@ let package = Package( .testTarget( name: "SQLKitTests", dependencies: [ + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOEmbedded", package: "swift-nio"), .target(name: "SQLKit"), .target(name: "SQLKitBenchmark"), ], @@ -48,7 +50,7 @@ let package = Package( var swiftSettings: [SwiftSetting] { [ .enableUpcomingFeature("ExistentialAny"), - // .enableUpcomingFeature("InternalImportsByDefault"), + .enableUpcomingFeature("InternalImportsByDefault"), .enableUpcomingFeature("MemberImportVisibility"), .enableUpcomingFeature("InferIsolatedConformances"), // .enableUpcomingFeature("NonisolatedNonsendingByDefault"), diff --git a/Sources/SQLKit/Docs.docc/theme-settings.json b/Sources/SQLKit/Docs.docc/theme-settings.json index 67e6b5d..0e8dd80 100644 --- a/Sources/SQLKit/Docs.docc/theme-settings.json +++ b/Sources/SQLKit/Docs.docc/theme-settings.json @@ -1,16 +1,19 @@ { "theme": { - "aside": { "border-radius": "16px", "border-width": "3px", "border-style": "double" }, "border-radius": "0", + "aside": { "border-radius": "16px", "border-width": "3px", "border-style": "double" }, "button": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" }, "code": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" }, "color": { - "sqlkit": { "dark": "hsl(32, 77%, 63%)", "light": "hsl(32, 80%, 60%)" }, - "documentation-intro-fill": "radial-gradient(circle at top, var(--color-sqlkit) 30%, #000 100%)", + "sqlkit": { + "dark": "hsl(32, 77%, 63%)", + "light": "hsl(32, 80%, 60%)" + }, + "documentation-intro-fill": { + "dark": "radial-gradient(circle at top, var(--color-sqlkit) 0%, #000000 100%)", + "light": "radial-gradient(circle at top, var(--color-sqlkit) 0%, #f0f0f0 100%)" + }, "documentation-intro-accent": "var(--color-sqlkit)", - "hero-eyebrow": "white", - "documentation-intro-figure": "white", - "hero-title": "white", "logo-base": { "dark": "#fff", "light": "#000" }, "logo-shape": { "dark": "#000", "light": "#fff" }, "fill": { "dark": "#000", "light": "#fff" } diff --git a/Sources/SQLKit/Utilities/StringHandling.swift b/Sources/SQLKit/Utilities/StringHandling.swift index e2f2bbf..ae2a29a 100644 --- a/Sources/SQLKit/Utilities/StringHandling.swift +++ b/Sources/SQLKit/Utilities/StringHandling.swift @@ -1,9 +1,7 @@ extension StringProtocol where Self: RangeReplaceableCollection, Self.Element: Equatable { - /// Provides a version of `StringProtocol.firstRange(of:)` which is guaranteed to be available on - /// pre-Ventura Apple platforms. + /// Provides a version of `StringProtocol.firstRange(of:)` guaranteed to be available. @inlinable func sqlkit_firstRange(of other: some StringProtocol) -> Range? { - /// N.B.: This implementation is apparently some 650% faster than `firstRange(of:)`, at least on macOS... guard self.count >= other.count, let starter = other.first else { return nil } var index = self.startIndex let lastIndex = self.index(self.endIndex, offsetBy: -other.count) @@ -21,15 +19,17 @@ extension StringProtocol where Self: RangeReplaceableCollection, Self.Element: E return nil } - /// Provides a version of `StringProtocol.replacing(_:with:)` which is guaranteed to be available on - /// pre-Ventura Apple platforms. + /// Provides a version of `StringProtocol.replacing(_:with:)` which is guaranteed to be available. + #if !DEBUG && !canImport(Darwin) + @inline(__always) + #endif @inlinable func sqlkit_replacing(_ search: some StringProtocol, with replacement: some StringProtocol) -> String { - /// N.B.: Even on Ventura/Sonoma, the handwritten implementation is orders of magnitude faster than - /// `replacing(_:with:)`, at least as of the time of this writing. Thus we use the handwritten version - /// unconditionally. It's still 4x slower than Foundation's version, but that's a lot better than 25x. + #if DEBUG || canImport(Darwin) + // On Apple platforms, this hand-rolled implementation is MUCH faster (10x or more) than the stdlib version, for some reason. + // We also want to use this implementation in debug builds, so that the tests test it rather than the stdlib. guard !self.isEmpty, !search.isEmpty, self.count >= search.count else { return .init(self) } - + var result = "", prevIndex = self.startIndex result.reserveCapacity(self.count + replacement.count) @@ -40,6 +40,10 @@ extension StringProtocol where Self: RangeReplaceableCollection, Self.Element: E } result.append(contentsOf: self[prevIndex...]) return result + #else + // On non-Apple platforms, the stdlib's version is better than our hand-rolled one. + return String(self.replacing(search, with: replacement)) + #endif } /// Returns the string with its first character lowercased. diff --git a/Sources/SQLKitBenchmark/SQLBenchmarker.swift b/Sources/SQLKitBenchmark/SQLBenchmarker.swift index aa01fd5..964de1d 100644 --- a/Sources/SQLKitBenchmark/SQLBenchmarker.swift +++ b/Sources/SQLKitBenchmark/SQLBenchmarker.swift @@ -1,6 +1,6 @@ import Logging import NIOCore -import SQLKit +public import SQLKit import XCTest public final class SQLBenchmarker: Sendable { diff --git a/Tests/SQLKitTests/AsyncTests.swift b/Tests/SQLKitTests/AsyncTests.swift index 4837e41..3031b1e 100644 --- a/Tests/SQLKitTests/AsyncTests.swift +++ b/Tests/SQLKitTests/AsyncTests.swift @@ -1,165 +1,175 @@ import NIOCore import OrderedCollections import SQLKit -import XCTest +import Testing -final class AsyncSQLKitTests: XCTestCase { - var db = TestDatabase() +@Suite("Async tests") +struct AsyncTests { + @Test("SQLDatabase async and futures") + func SQLDatabaseAsyncAndFutures() async throws { + let db = TestDatabase() - override class func setUp() { - XCTAssert(isLoggingConfigured) - } - - func testSQLDatabaseAsyncAndFutures() async throws { - try await self.db.execute(sql: SQLRaw("TEST"), { _ in XCTFail("Should not receive results") }).get() - XCTAssertEqual(self.db.results[0], "TEST") - - try await self.db.execute(sql: SQLRaw("TEST"), { _ in XCTFail("Should not receive results") }) - XCTAssertEqual(self.db.results[1], "TEST") + try await db.execute(sql: SQLRaw("TEST"), { _ in Issue.record("Should not receive results") }).get() + #expect(db.results[0] == "TEST") + + try await db.execute(sql: SQLRaw("TEST"), { _ in Issue.record("Should not receive results") }) + #expect(db.results[1] == "TEST") } - func testSQLQueryBuilderAsyncAndFutures() async throws { - self.db.outputs = [TestRow(data: [:])] - try await self.db.update("a").set("b", to: "c").run().get() - XCTAssertEqual(self.db.results[0], "UPDATE ``a`` SET ``b`` = &1") - - self.db.outputs = [TestRow(data: [:])] - try await self.db.update("a").set("b", to: "c").run() - XCTAssertEqual(self.db.results[1], "UPDATE ``a`` SET ``b`` = &1") + @Test("SQLQueryBuilder async and futures") + func SQLQueryBuilderAsyncAndFutures() async throws { + let db = TestDatabase() + + db.outputs = [TestRow(data: [:])] + try await db.update("a").set("b", to: "c").run().get() + #expect(db.results[0] == "UPDATE ``a`` SET ``b`` = &1") + + db.outputs = [TestRow(data: [:])] + try await db.update("a").set("b", to: "c").run() + #expect(db.results[1] == "UPDATE ``a`` SET ``b`` = &1") } - func testSQLQueryFetcherRunMethodsAsyncAndFutures() async throws { - try await self.db.select().column("a").from("b").run { _ in XCTFail("Should not receive results") } - XCTAssertEqual(self.db.results[0], "SELECT ``a`` FROM ``b``") + @Test("SQLQueryFetcher run methods async and futures") + func SQLQueryFetcherRunMethodsAsyncAndFutures() async throws { + let db = TestDatabase() + + try await db.select().column("a").from("b").run { _ in Issue.record("Should not receive results") } + #expect(db.results[0] == "SELECT ``a`` FROM ``b``") - try await self.db.select().column("a").from("b").run { _ in XCTFail("Should not receive results") }.get() - XCTAssertEqual(self.db.results[1], "SELECT ``a`` FROM ``b``") + try await db.select().column("a").from("b").run { _ in Issue.record("Should not receive results") }.get() + #expect(db.results[1] == "SELECT ``a`` FROM ``b``") - self.db.outputs = [TestRow(data: [:])] - try await self.db.select().column("a").from("b").run { XCTAssert($0.allColumns.isEmpty) } - XCTAssertEqual(self.db.results[2], "SELECT ``a`` FROM ``b``") + db.outputs = [TestRow(data: [:])] + try await db.select().column("a").from("b").run { #expect($0.allColumns.isEmpty) } + #expect(db.results[2] == "SELECT ``a`` FROM ``b``") - self.db.outputs = [TestRow(data: [:])] - try await self.db.select().column("a").from("b").run { XCTAssert($0.allColumns.isEmpty) }.get() - XCTAssertEqual(self.db.results[3], "SELECT ``a`` FROM ``b``") + db.outputs = [TestRow(data: [:])] + try await db.select().column("a").from("b").run { #expect($0.allColumns.isEmpty) }.get() + #expect(db.results[3] == "SELECT ``a`` FROM ``b``") - try await self.db.select().column("a").from("b").run(decoding: [String: String].self, { _ in XCTFail("Should not receive results") }) - XCTAssertEqual(self.db.results[4], "SELECT ``a`` FROM ``b``") + try await db.select().column("a").from("b").run(decoding: [String: String].self, { _ in Issue.record("Should not receive results") }) + #expect(db.results[4] == "SELECT ``a`` FROM ``b``") - try await self.db.select().column("a").from("b").run(decoding: [String: String].self, { _ in XCTFail("Should not receive results") }).get() - XCTAssertEqual(self.db.results[5], "SELECT ``a`` FROM ``b``") + try await db.select().column("a").from("b").run(decoding: [String: String].self, { _ in Issue.record("Should not receive results") }).get() + #expect(db.results[5] == "SELECT ``a`` FROM ``b``") - self.db.outputs = [TestRow(data: [:])] - try await self.db.select().column("a").from("b").run(decoding: [String: String].self, { XCTAssertNotNil(try? $0.get()) }) - XCTAssertEqual(self.db.results[6], "SELECT ``a`` FROM ``b``") + db.outputs = [TestRow(data: [:])] + try await db.select().column("a").from("b").run(decoding: [String: String].self, { #expect((try? $0.get()) != nil) }) + #expect(db.results[6] == "SELECT ``a`` FROM ``b``") - self.db.outputs = [TestRow(data: [:])] - try await self.db.select().column("a").from("b").run(decoding: [String: String].self, { XCTAssertNotNil(try? $0.get()) }).get() - XCTAssertEqual(self.db.results[7], "SELECT ``a`` FROM ``b``") + db.outputs = [TestRow(data: [:])] + try await db.select().column("a").from("b").run(decoding: [String: String].self, { #expect((try? $0.get()) != nil) }).get() + #expect(db.results[7] == "SELECT ``a`` FROM ``b``") - try await self.db.select().column("a").from("b").run(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys, { _ in XCTFail("Should not receive results") }) - XCTAssertEqual(self.db.results[8], "SELECT ``a`` FROM ``b``") + try await db.select().column("a").from("b").run(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys, { _ in Issue.record("Should not receive results") }) + #expect(db.results[8] == "SELECT ``a`` FROM ``b``") - try await self.db.select().column("a").from("b").run(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys, { _ in XCTFail("Should not receive results") }).get() - XCTAssertEqual(self.db.results[9], "SELECT ``a`` FROM ``b``") + try await db.select().column("a").from("b").run(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys, { _ in Issue.record("Should not receive results") }).get() + #expect(db.results[9] == "SELECT ``a`` FROM ``b``") - self.db.outputs = [TestRow(data: [:])] - try await self.db.select().column("a").from("b").run(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys, { XCTAssertNotNil(try? $0.get()) }) - XCTAssertEqual(self.db.results[10], "SELECT ``a`` FROM ``b``") + db.outputs = [TestRow(data: [:])] + try await db.select().column("a").from("b").run(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys, { #expect((try? $0.get()) != nil) }) + #expect(db.results[10] == "SELECT ``a`` FROM ``b``") - self.db.outputs = [TestRow(data: [:])] - try await self.db.select().column("a").from("b").run(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys, { XCTAssertNotNil(try? $0.get()) }).get() - XCTAssertEqual(self.db.results[11], "SELECT ``a`` FROM ``b``") + db.outputs = [TestRow(data: [:])] + try await db.select().column("a").from("b").run(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys, { #expect((try? $0.get()) != nil) }).get() + #expect(db.results[11] == "SELECT ``a`` FROM ``b``") } - func testSQLQueryFetcherAllMethodsAsyncAndFutures() async throws { - let res0 = try await self.db.select().column("a").from("b").all() - XCTAssert(res0.isEmpty) - XCTAssertEqual(self.db.results[0], "SELECT ``a`` FROM ``b``") - - self.db.outputs = [TestRow(data: [:])] - let res1 = try await self.db.select().column("a").from("b").all() - XCTAssertEqual(res1.count, 1) - XCTAssertEqual(self.db.results[1], "SELECT ``a`` FROM ``b``") - - self.db.outputs = [TestRow(data: [:])] - let res2 = try await self.db.select().column("a").from("b").all().get() - XCTAssertEqual(res2.count, 1) - XCTAssertEqual(self.db.results[2], "SELECT ``a`` FROM ``b``") - - self.db.outputs = [TestRow(data: [:])] - let res3 = try await self.db.select().column("a").from("b").all(decoding: [String: String].self) - XCTAssertEqual(res3.count, 1) - XCTAssertEqual(self.db.results[3], "SELECT ``a`` FROM ``b``") - - self.db.outputs = [TestRow(data: [:])] - let res4 = try await self.db.select().column("a").from("b").all(decoding: [String: String].self).get() - XCTAssertEqual(res4.count, 1) - XCTAssertEqual(self.db.results[4], "SELECT ``a`` FROM ``b``") - - self.db.outputs = [TestRow(data: [:])] - let res5 = try await self.db.select().column("a").from("b").all(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys) - XCTAssertEqual(res5.count, 1) - XCTAssertEqual(self.db.results[5], "SELECT ``a`` FROM ``b``") - - self.db.outputs = [TestRow(data: [:])] - let res6 = try await self.db.select().column("a").from("b").all(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys).get() - XCTAssertEqual(res6.count, 1) - XCTAssertEqual(self.db.results[6], "SELECT ``a`` FROM ``b``") - - self.db.outputs = [TestRow(data: ["a": "a"])] - let res7 = try await self.db.select().column("a").from("b").all(decodingColumn: "a", as: String.self) - XCTAssertEqual(res7.count, 1) - XCTAssertEqual(self.db.results[7], "SELECT ``a`` FROM ``b``") - - self.db.outputs = [TestRow(data: ["a": "a"])] - let res8 = try await self.db.select().column("a").from("b").all(decodingColumn: "a", as: String.self).get() - XCTAssertEqual(res8.count, 1) - XCTAssertEqual(self.db.results[8], "SELECT ``a`` FROM ``b``") + @Test("SQLQueryFetcher all methods async and futures") + func SQLQueryFetcherAllMethodsAsyncAndFutures() async throws { + let db = TestDatabase() + + let res0 = try await db.select().column("a").from("b").all() + #expect(res0.isEmpty) + #expect(db.results[0] == "SELECT ``a`` FROM ``b``") + + db.outputs = [TestRow(data: [:])] + let res1 = try await db.select().column("a").from("b").all() + #expect(res1.count == 1) + #expect(db.results[1] == "SELECT ``a`` FROM ``b``") + + db.outputs = [TestRow(data: [:])] + let res2 = try await db.select().column("a").from("b").all().get() + #expect(res2.count == 1) + #expect(db.results[2] == "SELECT ``a`` FROM ``b``") + + db.outputs = [TestRow(data: [:])] + let res3 = try await db.select().column("a").from("b").all(decoding: [String: String].self) + #expect(res3.count == 1) + #expect(db.results[3] == "SELECT ``a`` FROM ``b``") + + db.outputs = [TestRow(data: [:])] + let res4 = try await db.select().column("a").from("b").all(decoding: [String: String].self).get() + #expect(res4.count == 1) + #expect(db.results[4] == "SELECT ``a`` FROM ``b``") + + db.outputs = [TestRow(data: [:])] + let res5 = try await db.select().column("a").from("b").all(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys) + #expect(res5.count == 1) + #expect(db.results[5] == "SELECT ``a`` FROM ``b``") + + db.outputs = [TestRow(data: [:])] + let res6 = try await db.select().column("a").from("b").all(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys).get() + #expect(res6.count == 1) + #expect(db.results[6] == "SELECT ``a`` FROM ``b``") + + db.outputs = [TestRow(data: ["a": "a"])] + let res7 = try await db.select().column("a").from("b").all(decodingColumn: "a", as: String.self) + #expect(res7.count == 1) + #expect(db.results[7] == "SELECT ``a`` FROM ``b``") + + db.outputs = [TestRow(data: ["a": "a"])] + let res8 = try await db.select().column("a").from("b").all(decodingColumn: "a", as: String.self).get() + #expect(res8.count == 1) + #expect(db.results[8] == "SELECT ``a`` FROM ``b``") } - func testSQLQueryFetcherFirstMethodsAsyncAndFutures() async throws { - let res0 = try await self.db.select().column("a").from("b").first() - XCTAssertNil(res0) - XCTAssertEqual(self.db.results[0], "SELECT ``a`` FROM ``b`` LIMIT 1") - - self.db.outputs = [TestRow(data: [:])] - let res1 = try await self.db.select().column("a").from("b").first() - XCTAssertNotNil(res1) - XCTAssertEqual(self.db.results[1], "SELECT ``a`` FROM ``b`` LIMIT 1") - - self.db.outputs = [TestRow(data: [:])] - let res2 = try await self.db.select().column("a").from("b").first(decoding: [String: String].self) - XCTAssertNotNil(res2) - XCTAssertEqual(self.db.results[2], "SELECT ``a`` FROM ``b`` LIMIT 1") - - self.db.outputs = [TestRow(data: [:])] - let res3 = try await self.db.select().column("a").from("b").first(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys) - XCTAssertNotNil(res3) - XCTAssertEqual(self.db.results[3], "SELECT ``a`` FROM ``b`` LIMIT 1") - - self.db.outputs = [TestRow(data: ["a": "a"])] - let res4 = try await self.db.select().column("a").from("b").first(decodingColumn: "a", as: String.self) - XCTAssertNotNil(res4) - XCTAssertEqual(self.db.results[4], "SELECT ``a`` FROM ``b`` LIMIT 1") - - self.db.outputs = [TestRow(data: [:])] - let res5 = try await self.db.select().column("a").from("b").first().get() - XCTAssertNotNil(res5) - XCTAssertEqual(self.db.results[5], "SELECT ``a`` FROM ``b`` LIMIT 1") - - self.db.outputs = [TestRow(data: [:])] - let res6 = try await self.db.select().column("a").from("b").first(decoding: [String: String].self).get() - XCTAssertNotNil(res6) - XCTAssertEqual(self.db.results[6], "SELECT ``a`` FROM ``b`` LIMIT 1") - - self.db.outputs = [TestRow(data: [:])] - let res7 = try await self.db.select().column("a").from("b").first(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys).get() - XCTAssertNotNil(res7) - XCTAssertEqual(self.db.results[7], "SELECT ``a`` FROM ``b`` LIMIT 1") - - let res8 = try await self.db.select().column("a").from("b").first(decodingColumn: "a", as: String.self).get() - XCTAssertNil(res8) - XCTAssertEqual(self.db.results[8], "SELECT ``a`` FROM ``b`` LIMIT 1") + @Test("SQLQueryFetcher first methods async and futures") + func SQLQueryFetcherFirstMethodsAsyncAndFutures() async throws { + let db = TestDatabase() + + let res0 = try await db.select().column("a").from("b").first() + #expect(res0 == nil) + #expect(db.results[0] == "SELECT ``a`` FROM ``b`` LIMIT 1") + + db.outputs = [TestRow(data: [:])] + let res1 = try await db.select().column("a").from("b").first() + #expect(res1 != nil) + #expect(db.results[1] == "SELECT ``a`` FROM ``b`` LIMIT 1") + + db.outputs = [TestRow(data: [:])] + let res2 = try await db.select().column("a").from("b").first(decoding: [String: String].self) + #expect(res2 != nil) + #expect(db.results[2] == "SELECT ``a`` FROM ``b`` LIMIT 1") + + db.outputs = [TestRow(data: [:])] + let res3 = try await db.select().column("a").from("b").first(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys) + #expect(res3 != nil) + #expect(db.results[3] == "SELECT ``a`` FROM ``b`` LIMIT 1") + + db.outputs = [TestRow(data: ["a": "a"])] + let res4 = try await db.select().column("a").from("b").first(decodingColumn: "a", as: String.self) + #expect(res4 != nil) + #expect(db.results[4] == "SELECT ``a`` FROM ``b`` LIMIT 1") + + db.outputs = [TestRow(data: [:])] + let res5 = try await db.select().column("a").from("b").first().get() + #expect(res5 != nil) + #expect(db.results[5] == "SELECT ``a`` FROM ``b`` LIMIT 1") + + db.outputs = [TestRow(data: [:])] + let res6 = try await db.select().column("a").from("b").first(decoding: [String: String].self).get() + #expect(res6 != nil) + #expect(db.results[6] == "SELECT ``a`` FROM ``b`` LIMIT 1") + + db.outputs = [TestRow(data: [:])] + let res7 = try await db.select().column("a").from("b").first(decoding: [String: String].self, keyDecodingStrategy: .useDefaultKeys).get() + #expect(res7 != nil) + #expect(db.results[7] == "SELECT ``a`` FROM ``b`` LIMIT 1") + + let res8 = try await db.select().column("a").from("b").first(decodingColumn: "a", as: String.self).get() + #expect(res8 == nil) + #expect(db.results[8] == "SELECT ``a`` FROM ``b`` LIMIT 1") } } diff --git a/Tests/SQLKitTests/BaseTests.swift b/Tests/SQLKitTests/BaseTests.swift index 8d58d36..d9da5d6 100644 --- a/Tests/SQLKitTests/BaseTests.swift +++ b/Tests/SQLKitTests/BaseTests.swift @@ -3,18 +3,19 @@ import struct Logging.Logger import protocol NIOCore.EventLoop import class NIOCore.EventLoopFuture import SQLKitBenchmark -import XCTest +import Testing -final class SQLKitTests: XCTestCase { - var db = TestDatabase() - - override class func setUp() { - XCTAssert(isLoggingConfigured) +@Suite("Base tests") +struct BaseTests { + init() { + #expect(isLoggingConfigured) } - + // MARK: SQLBenchmark - func testBenchmark() async throws { + @Test("benchmark") + func benchmark() async throws { + let db = TestDatabase() let benchmarker = SQLBenchmarker(on: db) try await benchmarker.runAllTests() @@ -22,9 +23,12 @@ final class SQLKitTests: XCTestCase { // MARK: Operators - func testBinaryOperators() { - XCTAssertSerialization( - of: self.db.update("planets") + @Test("binary operators") + func binaryOperators() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.update("planets") .set(SQLIdentifier("moons"), to: SQLBinaryExpression( left: SQLIdentifier("moons"), op: SQLBinaryOperator.add, @@ -35,26 +39,30 @@ final class SQLKitTests: XCTestCase { ) } - func testInsertWithArrayOfEncodable() { + @Test("insert with array of Encodable") + func insertWithArrayOfEncodable() { + let db = TestDatabase() + func weird(_ builder: SQLInsertBuilder, values: some Sequence) -> SQLInsertBuilder { builder.values(Array(values)) } - let output = XCTAssertNoThrowWithResult(weird( - self.db.insert(into: "planets").columns("name"), - values: ["Jupiter"] - ) - .advancedSerialize() - ) - XCTAssertEqual(output?.sql, "INSERT INTO ``planets`` (``name``) VALUES (&1)") - XCTAssertEqual(output?.binds as? [String], ["Jupiter"]) // instead of [["Jupiter"]] + let output = weird( + db.insert(into: "planets").columns("name"), + values: ["Jupiter"] + ).advancedSerialize() + #expect(output.sql == "INSERT INTO ``planets`` (``name``) VALUES (&1)") + #expect(output.binds as? [String] == ["Jupiter"]) // instead of [["Jupiter"]] } // MARK: JSON paths - func testJSONPaths() { - XCTAssertSerialization( - of: self.db.select() + @Test("JSON paths") + func JSONPaths() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column(SQLNestedSubpathExpression(column: "json", path: ["a"])) .column(SQLNestedSubpathExpression(column: "json", path: ["a", "b"])) .column(SQLNestedSubpathExpression(column: "json", path: ["a", "b", "c"])) @@ -65,108 +73,114 @@ final class SQLKitTests: XCTestCase { // MARK: Misc - func testQuoting() { - XCTAssertSerialization(of: SQLRawBuilder("\(ident: "foo``bar``") \(literal: "foo'bar'")", on: self.db), is: "``foo````bar`````` 'foo''bar'''") + @Test("quoting") + func quoting() throws { + let db = TestDatabase() + + try expectSerialization(of: SQLRawBuilder("\(ident: "foo``bar``") \(literal: "foo'bar'")", on: db), is: "``foo````bar`````` 'foo''bar'''") } - func testStringHandlingUtilities() { + @Test("string handling utilities") + func stringHandlingUtilities() { /// `encapitalized` - XCTAssertEqual("".encapitalized, "") - XCTAssertEqual("a".encapitalized, "A") - XCTAssertEqual("A".encapitalized, "A") - XCTAssertEqual("aa".encapitalized, "Aa") - XCTAssertEqual("Aa".encapitalized, "Aa") - XCTAssertEqual("aA".encapitalized, "AA") - XCTAssertEqual("AA".encapitalized, "AA") + #expect("".encapitalized == "") + #expect("a".encapitalized == "A") + #expect("A".encapitalized == "A") + #expect("aa".encapitalized == "Aa") + #expect("Aa".encapitalized == "Aa") + #expect("aA".encapitalized == "AA") + #expect("AA".encapitalized == "AA") /// `decapitalized` - XCTAssertEqual("".decapitalized, "") - XCTAssertEqual("a".decapitalized, "a") - XCTAssertEqual("A".decapitalized, "a") - XCTAssertEqual("aa".decapitalized, "aa") - XCTAssertEqual("Aa".decapitalized, "aa") - XCTAssertEqual("aA".decapitalized, "aA") - XCTAssertEqual("AA".decapitalized, "aA") + #expect("".decapitalized == "") + #expect("a".decapitalized == "a") + #expect("A".decapitalized == "a") + #expect("aa".decapitalized == "aa") + #expect("Aa".decapitalized == "aa") + #expect("aA".decapitalized == "aA") + #expect("AA".decapitalized == "aA") /// `convertedFromSnakeCase` - XCTAssertEqual("".convertedFromSnakeCase, "") - XCTAssertEqual("_".convertedFromSnakeCase, "_") - XCTAssertEqual("__".convertedFromSnakeCase, "__") - XCTAssertEqual("a".convertedFromSnakeCase, "a") - XCTAssertEqual("a_".convertedFromSnakeCase, "a_") - XCTAssertEqual("a_a".convertedFromSnakeCase, "aA") - XCTAssertEqual("aA_a".convertedFromSnakeCase, "aAA") - XCTAssertEqual("_a".convertedFromSnakeCase, "_a") - XCTAssertEqual("_a_".convertedFromSnakeCase, "_a_") - XCTAssertEqual("a_b_c".convertedFromSnakeCase, "aBC") - XCTAssertEqual("_a_b_c_".convertedFromSnakeCase, "_aBC_") - XCTAssertEqual("_a_b_bcc_".convertedFromSnakeCase, "_aBBcc_") + #expect("".convertedFromSnakeCase == "") + #expect("_".convertedFromSnakeCase == "_") + #expect("__".convertedFromSnakeCase == "__") + #expect("a".convertedFromSnakeCase == "a") + #expect("a_".convertedFromSnakeCase == "a_") + #expect("a_a".convertedFromSnakeCase == "aA") + #expect("aA_a".convertedFromSnakeCase == "aAA") + #expect("_a".convertedFromSnakeCase == "_a") + #expect("_a_".convertedFromSnakeCase == "_a_") + #expect("a_b_c".convertedFromSnakeCase == "aBC") + #expect("_a_b_c_".convertedFromSnakeCase == "_aBC_") + #expect("_a_b_bcc_".convertedFromSnakeCase == "_aBBcc_") /// `convertedToSnakeCase` - XCTAssertEqual("".convertedToSnakeCase, "") - XCTAssertEqual("_".convertedToSnakeCase, "_") - XCTAssertEqual("__".convertedToSnakeCase, "__") - XCTAssertEqual("a".convertedToSnakeCase, "a") - XCTAssertEqual("a_".convertedToSnakeCase, "a_") - XCTAssertEqual("aA".convertedToSnakeCase, "a_a") - XCTAssertEqual("aAA".convertedToSnakeCase, "a_aA") - XCTAssertEqual("_a".convertedToSnakeCase, "_a") - XCTAssertEqual("_a_".convertedToSnakeCase, "_a_") - XCTAssertEqual("aBC".convertedToSnakeCase, "a_bC") - XCTAssertEqual("_aBC_".convertedToSnakeCase, "_a_bC_") - XCTAssertEqual("aBBcc".convertedToSnakeCase, "a_b_bcc") - XCTAssertEqual("_aBBcc_".convertedToSnakeCase, "_a_b_bcc_") + #expect("".convertedToSnakeCase == "") + #expect("_".convertedToSnakeCase == "_") + #expect("__".convertedToSnakeCase == "__") + #expect("a".convertedToSnakeCase == "a") + #expect("a_".convertedToSnakeCase == "a_") + #expect("aA".convertedToSnakeCase == "a_a") + #expect("aAA".convertedToSnakeCase == "a_aA") + #expect("_a".convertedToSnakeCase == "_a") + #expect("_a_".convertedToSnakeCase == "_a_") + #expect("aBC".convertedToSnakeCase == "a_bC") + #expect("_aBC_".convertedToSnakeCase == "_a_bC_") + #expect("aBBcc".convertedToSnakeCase == "a_b_bcc") + #expect("_aBBcc_".convertedToSnakeCase == "_a_b_bcc_") /// `sqlkit_firstRange(of:)` - XCTAssertEqual("a".sqlkit_firstRange(of: "abc"), nil) - XCTAssertEqual("abba".sqlkit_firstRange(of: "abc"), nil) - XCTAssertEqual("abc".sqlkit_firstRange(of: "abc"), "abc".startIndex ..< "abc".endIndex) - XCTAssertEqual("aabca".sqlkit_firstRange(of: "abc"), "aabca".index(after: "aabca".startIndex) ..< "aabca".index(before: "aabca".endIndex)) - XCTAssertEqual("abcabc".sqlkit_firstRange(of: "abc"), "abcabc".startIndex ..< "abcabc".index("abcabc".startIndex, offsetBy: 3)) - XCTAssertEqual("aabc_abca".sqlkit_firstRange(of: "abc"), "aabc_abca".index(after: "aabc_abca".startIndex) ..< "aabc_abca".index("aabc_abca".startIndex, offsetBy: 4)) + #expect("a".sqlkit_firstRange(of: "abc") == nil) + #expect("abba".sqlkit_firstRange(of: "abc") == nil) + #expect("abc".sqlkit_firstRange(of: "abc") == "abc".startIndex ..< "abc".endIndex) + #expect("aabca".sqlkit_firstRange(of: "abc") == "aabca".index(after: "aabca".startIndex) ..< "aabca".index(before: "aabca".endIndex)) + #expect("abcabc".sqlkit_firstRange(of: "abc") == "abcabc".startIndex ..< "abcabc".index("abcabc".startIndex, offsetBy: 3)) + #expect("aabc_abca".sqlkit_firstRange(of: "abc") == "aabc_abca".index(after: "aabc_abca".startIndex) ..< "aabc_abca".index("aabc_abca".startIndex, offsetBy: 4)) /// `sqlkit_replacing(_:with:)` - XCTAssertEqual("abc".sqlkit_replacing("abc", with: "def"), "def") - XCTAssertEqual("aabca".sqlkit_replacing("abc", with: "def"), "adefa") - XCTAssertEqual("abcabc".sqlkit_replacing("abc", with: "def"), "defdef") - XCTAssertEqual("aabc_abca".sqlkit_replacing("abc", with: "def"), "adef_defa") - + #expect("abc".sqlkit_replacing("abc", with: "def") == "def") + #expect("aabca".sqlkit_replacing("abc", with: "def") == "adefa") + #expect("abcabc".sqlkit_replacing("abc", with: "def") == "defdef") + #expect("aabc_abca".sqlkit_replacing("abc", with: "def") == "adef_defa") + /// `codingKeyValue` - XCTAssertEqual("a".codingKeyValue.stringValue, "a") + #expect("a".codingKeyValue.stringValue == "a") /// `drop(prefix:)` - XCTAssertEqual("abcdef".drop(prefix: "abc"), "def") - XCTAssertEqual("acbdef".drop(prefix: "abc"), "acbdef") - XCTAssertEqual("abcdef".drop(prefix: String?.none), "abcdef") + #expect("abcdef".drop(prefix: "abc") == "def") + #expect("acbdef".drop(prefix: "abc") == "acbdef") + #expect("abcdef".drop(prefix: String?.none) == "abcdef") } - func testDatabaseDefaultProperties() { - XCTAssertNil(self.db.version) - XCTAssertEqual(self.db.queryLogLevel, .debug) + @Test("database default properties") + func databaseDefaultProperties() { + let db = TestDatabase() + + #expect(db.version == nil) + #expect(db.queryLogLevel == .debug) } - func testDatabaseLoggerDatabase() async throws { - let db = self.db.logging(to: .init(label: "l")) - - XCTAssertNotNil(db.eventLoop) - XCTAssertNil(db.version) - XCTAssertEqual(db.dialect.name, self.db.dialect.name) - XCTAssertEqual(db.queryLogLevel, self.db.queryLogLevel) - await XCTAssertNotNilAsync(try await db.execute(sql: SQLRaw("TEST"), { _ in })) - await XCTAssertNotNilAsync(try await db.execute(sql: SQLRaw("TEST"), { _ in }).get()) + @Test("logger database") + func loggerDatabase() async throws { + let sdb = TestDatabase() + let db = sdb.logging(to: .init(label: "l")) + + #expect(db.version == nil) + #expect(db.logger.logLevel == Logger(label: "l").logLevel) + #expect(ObjectIdentifier(db.eventLoop) == ObjectIdentifier(sdb.eventLoop)) + #expect(db.dialect.name == db.dialect.name) + #expect(db.queryLogLevel == db.queryLogLevel) + await #expect(throws: Never.self) { try await db.execute(sql: SQLRaw("TEST"), { _ in }) } + await #expect(throws: Never.self) { try await db.execute(sql: SQLRaw("TEST"), { _ in }).get() } } - func testDatabaseDefaultAsyncImpl() async throws { - struct TestNoAsyncDatabase: SQLDatabase { - func execute(sql query: any SQLExpression, _ onRow: @escaping @Sendable (any SQLRow) -> ()) -> EventLoopFuture { self.eventLoop.makeSucceededVoidFuture() } - var logger: Logger { .init(label: "l") } - var eventLoop: any EventLoop { FakeEventLoop() } - var dialect: any SQLDialect { GenericDialect() } - } - await XCTAssertNotNilAsync(try await TestNoAsyncDatabase().execute(sql: SQLRaw("TEST"), { _ in })) + @Test("database default async impl") + func databaseDefaultAsyncImpl() async throws { + await #expect(throws: Never.self) { try await TestNoAsyncDatabase().execute(sql: SQLRaw("TEST"), { _ in }) } } - func testDatabaseVersion() { + @Test("database version") + func databaseVersion() { struct TestVersion: SQLDatabaseReportedVersion { let stringValue: String } @@ -174,24 +188,25 @@ final class SQLKitTests: XCTestCase { let stringValue: String } - XCTAssert(TestVersion(stringValue: "a") == TestVersion(stringValue: "a")) - XCTAssertFalse(TestVersion(stringValue: "a").isEqual(to: AnotherTestVersion(stringValue: "a"))) - XCTAssert(TestVersion(stringValue: "a") != TestVersion(stringValue: "b")) - XCTAssert(TestVersion(stringValue: "a") < TestVersion(stringValue: "b")) - XCTAssertFalse(TestVersion(stringValue: "a").isOlder(than: AnotherTestVersion(stringValue: "a"))) - XCTAssert(TestVersion(stringValue: "a") <= TestVersion(stringValue: "a")) - XCTAssert(TestVersion(stringValue: "b") > TestVersion(stringValue: "a")) - XCTAssert(TestVersion(stringValue: "a") >= TestVersion(stringValue: "a")) + #expect(TestVersion(stringValue: "a") == TestVersion(stringValue: "a")) + #expect(!TestVersion(stringValue: "a").isEqual(to: AnotherTestVersion(stringValue: "a"))) + #expect(TestVersion(stringValue: "a") != TestVersion(stringValue: "b")) + #expect(TestVersion(stringValue: "a") < TestVersion(stringValue: "b")) + #expect(!TestVersion(stringValue: "a").isOlder(than: AnotherTestVersion(stringValue: "a"))) + #expect(TestVersion(stringValue: "a") <= TestVersion(stringValue: "a")) + #expect(TestVersion(stringValue: "b") > TestVersion(stringValue: "a")) + #expect(TestVersion(stringValue: "a") >= TestVersion(stringValue: "a")) } - func testDatabaseWithSession() async { - await XCTAssertAsync(try await self.db.withSession { - XCTAssertNotNil($0) - return true - }) + @Test("database with session") + func databaseWithSession() async throws { + let db = TestDatabase() + + #expect(try await db.withSession { _ in true }) } - func testDialectDefaultImpls() { + @Test("dialect default implementations") + func dialectDefaultImplementations() { struct TestDialect: SQLDialect { var name: String { "test" } var identifierQuote: any SQLExpression { SQLRaw("`") } @@ -201,29 +216,31 @@ final class SQLKitTests: XCTestCase { func literalBoolean(_ value: Bool) -> any SQLExpression { SQLRaw("\(value)") } } - XCTAssertEqual((TestDialect().literalStringQuote as? SQLRaw)?.sql, "'") - XCTAssertNil(TestDialect().autoIncrementFunction) - XCTAssertEqual((TestDialect().literalDefault as? SQLRaw)?.sql, "DEFAULT") - XCTAssert(TestDialect().supportsIfExists) - XCTAssertEqual(TestDialect().enumSyntax, .unsupported) - XCTAssertFalse(TestDialect().supportsDropBehavior) - XCTAssertFalse(TestDialect().supportsReturning) - XCTAssertEqual(TestDialect().triggerSyntax.create, []) - XCTAssertEqual(TestDialect().triggerSyntax.drop, []) - XCTAssertNil(TestDialect().alterTableSyntax.alterColumnDefinitionClause) - XCTAssertNil(TestDialect().alterTableSyntax.alterColumnDefinitionTypeKeyword) - XCTAssert(TestDialect().alterTableSyntax.allowsBatch) - XCTAssertNil(TestDialect().customDataType(for: .int)) - XCTAssertEqual((TestDialect().normalizeSQLConstraint(identifier: SQLRaw("")) as? SQLRaw)?.sql, "") - XCTAssertEqual(TestDialect().upsertSyntax, .unsupported) - XCTAssertEqual(TestDialect().unionFeatures, [.union, .unionAll]) - XCTAssertNil(TestDialect().sharedSelectLockExpression) - XCTAssertNil(TestDialect().exclusiveSelectLockExpression) - XCTAssertNil(TestDialect().nestedSubpathExpression(in: SQLRaw(""), for: [""])) + #expect((TestDialect().literalStringQuote as? SQLRaw)?.sql == "'") + #expect(TestDialect().autoIncrementFunction == nil) + #expect((TestDialect().literalDefault as? SQLRaw)?.sql == "DEFAULT") + #expect(TestDialect().supportsIfExists) + #expect(TestDialect().enumSyntax == .unsupported) + #expect(!TestDialect().supportsDropBehavior) + #expect(!TestDialect().supportsReturning) + #expect(TestDialect().triggerSyntax.create == []) + #expect(TestDialect().triggerSyntax.drop == []) + #expect(TestDialect().alterTableSyntax.alterColumnDefinitionClause == nil) + #expect(TestDialect().alterTableSyntax.alterColumnDefinitionTypeKeyword == nil) + #expect(TestDialect().alterTableSyntax.allowsBatch) + #expect(TestDialect().customDataType(for: .int) == nil) + #expect((TestDialect().normalizeSQLConstraint(identifier: SQLRaw("")) as? SQLRaw)?.sql == "") + #expect(TestDialect().upsertSyntax == .unsupported) + #expect(TestDialect().unionFeatures == [.union, .unionAll]) + #expect(TestDialect().sharedSelectLockExpression == nil) + #expect(TestDialect().exclusiveSelectLockExpression == nil) + #expect(TestDialect().nestedSubpathExpression(in: SQLRaw(""), for: [""]) == nil) } - func testAdditionalStatementAPI() { - var serializer = SQLSerializer(database: self.db) + @Test("additional SQLStatement API") + func additionalSQLStatementAPI() { + let db = TestDatabase() + var serializer = SQLSerializer(database: db) serializer.statement { $0.append("a") $0.append(SQLRaw("a")) @@ -242,6 +259,6 @@ final class SQLKitTests: XCTestCase { $0.append("a", SQLRaw("b"), SQLRaw("c")) $0.append(SQLRaw("a"), SQLRaw("b"), SQLRaw("c")) } - XCTAssertEqual(serializer.sql, "a a a b a b a b a b a b c a b c a b c a b c a b c a b c a b c a b c") + #expect(serializer.sql == "a a a b a b a b a b a b c a b c a b c a b c a b c a b c a b c a b c") } } diff --git a/Tests/SQLKitTests/BasicQueryTests.swift b/Tests/SQLKitTests/BasicQueryTests.swift index 9b4d8bf..9a885ef 100644 --- a/Tests/SQLKitTests/BasicQueryTests.swift +++ b/Tests/SQLKitTests/BasicQueryTests.swift @@ -1,18 +1,16 @@ import SQLKit -import XCTest +import Testing -final class BasicQueryTests: XCTestCase { - var db = TestDatabase() - - override class func setUp() { - XCTAssert(isLoggingConfigured) - } - +@Suite("Basic query tests") +struct BasicQueryTests { // MARK: Select - func testSelect_unqualifiedColumns() { - XCTAssertSerialization( - of: self.db.select() + @Test("SELECT unqualified columns") + func selectUnqualifiedColumns() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .column("name") .column(SQLLiteral.all) @@ -29,9 +27,12 @@ final class BasicQueryTests: XCTestCase { ) } - func testSelect_columnAliasing() { - XCTAssertSerialization( - of: self.db.select() + @Test("SELECT column aliasing") + func selectColumnAliasing() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("name", as: "n") .column(SQLIdentifier("name"), as: "n") .column(SQLIdentifier("name"), as: SQLIdentifier("n")) @@ -42,54 +43,72 @@ final class BasicQueryTests: XCTestCase { ) } - func testSelect_fromAliasing() { - XCTAssertSerialization( - of: self.db.select() + @Test("SELECT FROM aliasing") + func selectFROMAliasing() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .from("planets", as: "p"), is: "SELECT FROM ``planets`` AS ``p``" ) - XCTAssertSerialization( - of: self.db.select() + try expectSerialization( + of: db.select() .from(SQLIdentifier("planets"), as: SQLIdentifier("p")), is: "SELECT FROM ``planets`` AS ``p``" ) } - func testSelect_tableAllCols() { - XCTAssertSerialization( - of: self.db.select().columns(["*"]).from("planets").where("name", .equal, SQLBind("Earth")), + @Test("SELECT all table columns") + func selectAllTableColumns() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().columns(["*"]).from("planets").where("name", .equal, SQLBind("Earth")), is: "SELECT * FROM ``planets`` WHERE ``name`` = &1" ) - XCTAssertSerialization( - of: self.db.select().column(SQLColumn(SQLLiteral.all, table: SQLIdentifier("planets"))).from("planets").where("name", .equal, SQLBind("Earth")), + try expectSerialization( + of: db.select().column(SQLColumn(SQLLiteral.all, table: SQLIdentifier("planets"))).from("planets").where("name", .equal, SQLBind("Earth")), is: "SELECT ``planets``.* FROM ``planets`` WHERE ``name`` = &1" ) } - func testSelect_whereEncodable() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets").where("name", .equal, "Earth").orWhere("name", .equal, "Mars"), + @Test("SELECT WHERE Encodable") + func selectWHEREEncodable() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets").where("name", .equal, "Earth").orWhere("name", .equal, "Mars"), is: "SELECT * FROM ``planets`` WHERE ``name`` = &1 OR ``name`` = &2" ) } - func testSelect_whereArrayEncodableWithString() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets").where("name", .in, ["Earth", "Mars"]).orWhere("name", .in, ["Venus", "Mercury"]), + @Test("SELECT WHERE array Encodable with string") + func selectWHEREArrayEncodableWithString() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets").where("name", .in, ["Earth", "Mars"]).orWhere("name", .in, ["Venus", "Mercury"]), is: "SELECT * FROM ``planets`` WHERE ``name`` IN (&1, &2) OR ``name`` IN (&3, &4)" ) } - func testSelect_whereArrayEncodableWithIdentifier() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets").where(SQLIdentifier("name"), .in, ["Earth", "Mars"]).orWhere(SQLIdentifier("name"), .in, ["Venus", "Mercury"]), + @Test("SELECT WHERE array Encodable with identifier") + func selectWHEREArrayEncodableWithIdentifier() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets").where(SQLIdentifier("name"), .in, ["Earth", "Mars"]).orWhere(SQLIdentifier("name"), .in, ["Venus", "Mercury"]), is: "SELECT * FROM ``planets`` WHERE ``name`` IN (&1, &2) OR ``name`` IN (&3, &4)" ) } - func testSelect_whereGroup() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets") + @Test("SELECT WHERE group") + func selectWHEREGroup() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets") .where { $0.where("name", .equal, "Earth").orWhere("name", .equal, "Mars") } .orWhere { $0.where("color", .notEqual, "yellow") } .where("color", .equal, "blue"), @@ -97,56 +116,65 @@ final class BasicQueryTests: XCTestCase { ) } - func testSelect_whereEmptyGroup() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets").where { $0 }.orWhere { $0 }.where("color", .equal, "blue"), + @Test("SELECT WHERE empty group") + func selectWHEREEmptyGroup() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets").where { $0 }.orWhere { $0 }.where("color", .equal, "blue"), is: "SELECT * FROM ``planets`` WHERE ``color`` = &1" ) } - func testSelect_whereColumn() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets").where("name", .notEqual, column: "color").orWhere("name", .equal, column: "greekName"), + @Test("SELECT WHERE column") + func selectWHEREColumn() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets").where("name", .notEqual, column: "color").orWhere("name", .equal, column: "greekName"), is: "SELECT * FROM ``planets`` WHERE ``name`` <> ``color`` OR ``name`` = ``greekName``" ) } - func testSelect_otherWheres() { - XCTAssertSerialization( - of: self.db.select() + @Test("SELECT other WHEREs") + func selectOtherWHEREs() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .where("name", .notEqual, SQLBind("color")) .orWhere("name", .notEqual, SQLBind("color")), is: "SELECT * FROM ``planets`` WHERE ``name`` <> &1 OR ``name`` <> &2" ) - XCTAssertSerialization( - of: self.db.select() + try expectSerialization( + of: db.select() .column("*") .from("planets") .where(SQLIdentifier("name"), .notEqual, column: SQLIdentifier("color")) .orWhere(SQLIdentifier("name"), .equal, column: SQLIdentifier("greekName")), is: "SELECT * FROM ``planets`` WHERE ``name`` <> ``color`` OR ``name`` = ``greekName``" ) - XCTAssertSerialization( - of: self.db.select() + try expectSerialization( + of: db.select() .column("*") .from("planets") .where(SQLIdentifier("name"), .notEqual, "color") .orWhere(SQLIdentifier("name"), .equal, "greekName"), is: "SELECT * FROM ``planets`` WHERE ``name`` <> &1 OR ``name`` = &2" ) - XCTAssertSerialization( - of: self.db.select() + try expectSerialization( + of: db.select() .column("*") .from("planets") .where(SQLIdentifier("name"), .notEqual, SQLBind("color")) .orWhere(SQLIdentifier("name"), .equal, SQLBind("greekName")), is: "SELECT * FROM ``planets`` WHERE ``name`` <> &1 OR ``name`` = &2" ) - XCTAssertSerialization( - of: self.db.select() + try expectSerialization( + of: db.select() .column("*") .from("planets") .orWhere(SQLIdentifier("name"), .equal, SQLBind("greekName")), @@ -154,30 +182,42 @@ final class BasicQueryTests: XCTestCase { ) } - func testSelect_havingEncodable() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets").having("name", .equal, "Earth").orHaving("name", .equal, "Mars"), + @Test("SELECT HAVING Encodable") + func selectHAVINGEncodable() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets").having("name", .equal, "Earth").orHaving("name", .equal, "Mars"), is: "SELECT * FROM ``planets`` HAVING ``name`` = &1 OR ``name`` = &2" ) } - func testSelect_havingArrayEncodableWithString() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets").having("name", .in, ["Earth", "Mars"]).orHaving("name", .in, ["Venus", "Mercury"]), + @Test("SELECT HAVING array Encodable with string") + func selectHAVINGArrayEncodableWithString() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets").having("name", .in, ["Earth", "Mars"]).orHaving("name", .in, ["Venus", "Mercury"]), is: "SELECT * FROM ``planets`` HAVING ``name`` IN (&1, &2) OR ``name`` IN (&3, &4)" ) } - func testSelect_havingArrayEncodableWithIdentifier() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets").having(SQLIdentifier("name"), .in, ["Earth", "Mars"]).orHaving(SQLIdentifier("name"), .in, ["Venus", "Mercury"]), + @Test("SELECT HAVING array Encodable with identifier") + func selectHAVINGArrayEncodableWithIdentifier() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets").having(SQLIdentifier("name"), .in, ["Earth", "Mars"]).orHaving(SQLIdentifier("name"), .in, ["Venus", "Mercury"]), is: "SELECT * FROM ``planets`` HAVING ``name`` IN (&1, &2) OR ``name`` IN (&3, &4)" ) } - func testSelect_havingGroup() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets") + @Test("SELECT HAVING group") + func selectHAVINGGroup() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets") .having { $0.having("name", .equal, "Earth").orHaving("name", .equal, "Mars") } .orHaving { $0.having("color", .notEqual, "yellow") } .having("color", .equal, "blue"), @@ -185,56 +225,65 @@ final class BasicQueryTests: XCTestCase { ) } - func testSelect_havingEmptyGroup() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets").having { $0 }.orHaving { $0 }.having("color", .equal, "blue"), + @Test("SELECT HAVING empty group") + func selectHAVINGEmptyGroup() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets").having { $0 }.orHaving { $0 }.having("color", .equal, "blue"), is: "SELECT * FROM ``planets`` HAVING ``color`` = &1" ) } - func testSelect_havingColumn() { - XCTAssertSerialization( - of: self.db.select().column("*").from("planets").having("name", .notEqual, column: "color").orHaving("name", .equal, column: "greekName"), + @Test("SELECT HAVING column") + func selectHAVINGColumn() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column("*").from("planets").having("name", .notEqual, column: "color").orHaving("name", .equal, column: "greekName"), is: "SELECT * FROM ``planets`` HAVING ``name`` <> ``color`` OR ``name`` = ``greekName``" ) } - func testSelect_otherHavings() { - XCTAssertSerialization( - of: self.db.select() + @Test("SELECT other HAVINGs") + func selectOtherHAVINGs() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .having("name", .notEqual, SQLBind("color")) .orHaving("name", .notEqual, SQLBind("color")), is: "SELECT * FROM ``planets`` HAVING ``name`` <> &1 OR ``name`` <> &2" ) - XCTAssertSerialization( - of: self.db.select() + try expectSerialization( + of: db.select() .column("*") .from("planets") .having(SQLIdentifier("name"), .notEqual, column: SQLIdentifier("color")) .orHaving(SQLIdentifier("name"), .equal, column: SQLIdentifier("greekName")), is: "SELECT * FROM ``planets`` HAVING ``name`` <> ``color`` OR ``name`` = ``greekName``" ) - XCTAssertSerialization( - of: self.db.select() + try expectSerialization( + of: db.select() .column("*") .from("planets") .having(SQLIdentifier("name"), .notEqual, "color") .orHaving(SQLIdentifier("name"), .equal, "greekName"), is: "SELECT * FROM ``planets`` HAVING ``name`` <> &1 OR ``name`` = &2" ) - XCTAssertSerialization( - of: self.db.select() + try expectSerialization( + of: db.select() .column("*") .from("planets") .having(SQLIdentifier("name"), .notEqual, SQLBind("color")) .orHaving(SQLIdentifier("name"), .equal, SQLBind("greekName")), is: "SELECT * FROM ``planets`` HAVING ``name`` <> &1 OR ``name`` = &2" ) - XCTAssertSerialization( - of: self.db.select() + try expectSerialization( + of: db.select() .column("*") .from("planets") .orHaving(SQLIdentifier("name"), .equal, SQLBind("greekName")), @@ -242,16 +291,22 @@ final class BasicQueryTests: XCTestCase { ) } - func testSelect_withoutFrom() { - XCTAssertSerialization( - of: self.db.select().column(SQLAlias(SQLFunction("LAST_INSERT_ID"), as: SQLIdentifier("id"))), + @Test("SELECT without FROM") + func selectWithoutFROM() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column(SQLAlias(SQLFunction("LAST_INSERT_ID"), as: SQLIdentifier("id"))), is: "SELECT LAST_INSERT_ID() AS ``id``" ) } - func testSelect_limitAndOrder() { - XCTAssertSerialization( - of: self.db.select() + @Test("SELECT limit and order") + func selectLimitAndOrder() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .limit(3) @@ -260,16 +315,19 @@ final class BasicQueryTests: XCTestCase { is: "SELECT * FROM ``planets`` ORDER BY ``name`` ASC LIMIT 3 OFFSET 5" ) - let builder = self.db.select().where(SQLLiteral.boolean(true)).limit(1).offset(2) - XCTAssertEqual(builder.limit, 1) - XCTAssertEqual(builder.offset, 2) + let builder = db.select().where(SQLLiteral.boolean(true)).limit(1).offset(2) + #expect(builder.limit == 1) + #expect(builder.offset == 2) } // MARK: Update/delete - func testUpdate() { - XCTAssertSerialization( - of: self.db.update("planets") + @Test("UPDATE") + func update() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.update("planets") .where("name", .equal, "Jpuiter") .set("name", to: "Jupiter") .set(SQLIdentifier("name"), to: "Jupiter") @@ -277,28 +335,34 @@ final class BasicQueryTests: XCTestCase { is: "UPDATE ``planets`` SET ``name`` = &1, ``name`` = &2, ``name`` = &3 WHERE ``name`` = &4" ) - let builder = self.db.update("planets") + let builder = db.update("planets") builder.returning = .init(.init("id")) - XCTAssertNotNil(builder.returning) + #expect(builder.returning != nil) } - func testDelete() { - XCTAssertSerialization( - of: self.db.delete(from: "planets") + @Test("DELETE") + func delete() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.delete(from: "planets") .where("name", .equal, "Jupiter"), is: "DELETE FROM ``planets`` WHERE ``name`` = &1" ) - let builder = self.db.delete(from: "planets") + let builder = db.delete(from: "planets") builder.returning = .init(.init("id")) - XCTAssertNotNil(builder.returning) + #expect(builder.returning != nil) } // MARK: Locking Clauses - func testLockingClause_forUpdate() { - XCTAssertSerialization( - of: self.db.select() + @Test("locking FOR UPDATE") + func lockingForUpdate() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .where("name", .equal, "Earth") @@ -307,9 +371,12 @@ final class BasicQueryTests: XCTestCase { ) } - func testLockingClause_forShare() { - XCTAssertSerialization( - of: self.db.select() + @Test("locking FOR SHARE") + func lockingForShare() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .where("name", .equal, "Earth") @@ -318,9 +385,12 @@ final class BasicQueryTests: XCTestCase { ) } - func testLockingClause_raw() { - XCTAssertSerialization( - of: self.db.select() + @Test("raw locking clause") + func rawLockingClause() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .where("name", .equal, "Earth") @@ -331,9 +401,12 @@ final class BasicQueryTests: XCTestCase { // MARK: Group By/Having - func testGroupByHaving() { - XCTAssertSerialization( - of: self.db.select() + @Test("GROUP BY with HAVING") + func groupByWithHAVING() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .groupBy("color") @@ -344,9 +417,12 @@ final class BasicQueryTests: XCTestCase { // MARK: Distinct - func testDistinct() { - XCTAssertSerialization( - of: self.db.select() + @Test("DISTINCT") + func distinct() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .groupBy("color") @@ -356,24 +432,30 @@ final class BasicQueryTests: XCTestCase { ) } - func testDistinctColumns() { - XCTAssertSerialization( - of: self.db.select() + @Test("DISTINCT columns") + func distinctColumns() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .distinct(on: "name", "color") .from("planets"), is: "SELECT DISTINCT ``name``, ``color`` FROM ``planets``" ) - XCTAssertSerialization( - of: self.db.select() + try expectSerialization( + of: db.select() .distinct(on: SQLIdentifier("name"), SQLIdentifier("color")) .from("planets"), is: "SELECT DISTINCT ``name``, ``color`` FROM ``planets``" ) } - func testDistinctExpression() { - XCTAssertSerialization( - of: self.db.select() + @Test("DISTINCT expression") + func distinctExpression() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column(SQLFunction("COUNT", args: SQLDistinct("name", "color"))) .from("planets"), is: "SELECT COUNT(DISTINCT ``name``, ``color``) FROM ``planets``" @@ -382,9 +464,12 @@ final class BasicQueryTests: XCTestCase { // MARK: Joins - func testSimpleJoin() { - XCTAssertSerialization( - of: self.db.select() + @Test("simple JOIN") + func simpleJOIN() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .join("moons", on: SQLColumn("planet_id", table: "moons"), .equal, SQLColumn("id", table: "planets")), @@ -392,9 +477,12 @@ final class BasicQueryTests: XCTestCase { ) } - func testSimpleJoinWithSingleExpr() { - XCTAssertSerialization( - of: self.db.select() + @Test("simple JOIN with single expression") + func simpleJOINWithSingleExpression() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .join("moons", on: "\(ident: "moons").\(ident: "planet_id")=\(ident: "planets").\(ident: "id")" as SQLQueryString), @@ -402,14 +490,17 @@ final class BasicQueryTests: XCTestCase { ) } - func testMessyJoin() { - XCTAssertSerialization( - of: self.db.select() + @Test("messy JOIN") + func messyJOIN() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("planets") .join( SQLAlias(SQLGroupExpression( - self.db.select().column("name").from("stars").where(SQLColumn("orion"), .equal, SQLIdentifier("please space")).select + db.select().column("name").from("stars").where(SQLColumn("orion"), .equal, SQLIdentifier("please space")).select ), as: SQLIdentifier("star")), method: SQLJoinMethod.inner, on: SQLColumn(SQLIdentifier("planet_id"), table: SQLIdentifier("moons")), SQLBinaryOperator.isNot, SQLRaw("%%%%%%") @@ -419,9 +510,12 @@ final class BasicQueryTests: XCTestCase { ) } - func testJoinWithUsingClause() { - XCTAssertSerialization( - of: self.db.select() + @Test("JOIN with USING clause") + func joinWithUSINGClause() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .column("*") .from("stars") .join(SQLIdentifier("black_holes"), using: SQLIdentifier("galaxy_id")), @@ -431,9 +525,12 @@ final class BasicQueryTests: XCTestCase { // MARK: - Subquery - func testBasicSubquery() { - XCTAssertSerialization( - of: self.db.select().column(SQLSubquery.select { $0.column("foo").from("bar").limit(1) }), + @Test("basic subquery") + func basicSubquery() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select().column(SQLSubquery.select { $0.column("foo").from("bar").limit(1) }), is: "SELECT (SELECT ``foo`` FROM ``bar`` LIMIT 1)" ) } diff --git a/Tests/SQLKitTests/DeprecatedTests.swift b/Tests/SQLKitTests/DeprecatedTests.swift index 83fef9c..2151b29 100644 --- a/Tests/SQLKitTests/DeprecatedTests.swift +++ b/Tests/SQLKitTests/DeprecatedTests.swift @@ -1,145 +1,167 @@ import SQLKit -import XCTest +import Testing -final class SQLDeprecatedTests: XCTestCase { - var db = TestDatabase() - - override class func setUp() { - XCTAssert(isLoggingConfigured) - } - +@Suite("Deprecated functionality tests") +struct DeprecatedTests { @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testConcatOperator() { - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.concatenate)"), is: "") + @Test("concat operator") + func concatOperator() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLBinaryOperator.concatenate)"), is: "") } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testSQLError() { + @Test("SQLError") + func sqlError() { struct RidiculousError: SQLError { var sqlErrorType: SQLErrorType } - XCTAssertThrowsError(try { throw RidiculousError(sqlErrorType: .constraint) }()) { XCTAssertEqual(($0 as? any SQLError)?.sqlErrorType, .constraint) } - XCTAssertThrowsError(try { throw RidiculousError(sqlErrorType: .io) }()) { XCTAssertEqual(($0 as? any SQLError)?.sqlErrorType, .io) } - XCTAssertThrowsError(try { throw RidiculousError(sqlErrorType: .permission) }()) { XCTAssertEqual(($0 as? any SQLError)?.sqlErrorType, .permission) } - XCTAssertThrowsError(try { throw RidiculousError(sqlErrorType: .syntax) }()) { XCTAssertEqual(($0 as? any SQLError)?.sqlErrorType, .syntax) } - XCTAssertThrowsError(try { throw RidiculousError(sqlErrorType: .unknown) }()) { XCTAssertEqual(($0 as? any SQLError)?.sqlErrorType, .unknown) } + #expect(((#expect(throws: (any Error).self) { throw RidiculousError(sqlErrorType: .constraint) }) as? any SQLError)?.sqlErrorType == .constraint) + #expect(((#expect(throws: (any Error).self) { throw RidiculousError(sqlErrorType: .io) }) as? any SQLError)?.sqlErrorType == .io) + #expect(((#expect(throws: (any Error).self) { throw RidiculousError(sqlErrorType: .permission) }) as? any SQLError)?.sqlErrorType == .permission) + #expect(((#expect(throws: (any Error).self) { throw RidiculousError(sqlErrorType: .syntax) }) as? any SQLError)?.sqlErrorType == .syntax) + #expect(((#expect(throws: (any Error).self) { throw RidiculousError(sqlErrorType: .unknown) }) as? any SQLError)?.sqlErrorType == .unknown) } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testOldTriggerTimingSpecifiers() { - XCTAssertEqual(SQLCreateTrigger.TimingSpecifier.initiallyImmediate, .deferrable) - XCTAssertEqual(SQLCreateTrigger.TimingSpecifier.initiallyDeferred, .deferredByDefault) + @Test("old trigger timing specifiers") + func oldTriggerTimingSpecifiers() { + #expect(SQLCreateTrigger.TimingSpecifier.initiallyImmediate == .deferrable) + #expect(SQLCreateTrigger.TimingSpecifier.initiallyDeferred == .deferredByDefault) } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testDataTypeType() { - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.type("FOO"))"), is: "``FOO``") + @Test("SQLDataType type") + func sqlDataTypeType() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLDataType.type("FOO"))"), is: "``FOO``") } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testOldCascadeProperties() { + @Test("old cascade properties") + func oldCascadeProperties() { var dropEnum = SQLDropEnum(name: SQLIdentifier("enum")) dropEnum.cascade = false - XCTAssertEqual(dropEnum.dropBehavior, .restrict) - XCTAssertEqual(dropEnum.cascade, false) + #expect(dropEnum.dropBehavior == .restrict) + #expect(dropEnum.cascade == false) dropEnum.cascade = true - XCTAssertEqual(dropEnum.dropBehavior, .cascade) - XCTAssertEqual(dropEnum.cascade, true) + #expect(dropEnum.dropBehavior == .cascade) + #expect(dropEnum.cascade == true) var dropTrigger = SQLDropTrigger(name: SQLIdentifier("trigger")) dropTrigger.cascade = false - XCTAssertEqual(dropTrigger.dropBehavior, .restrict) - XCTAssertEqual(dropTrigger.cascade, false) + #expect(dropTrigger.dropBehavior == .restrict) + #expect(dropTrigger.cascade == false) dropTrigger.cascade = true - XCTAssertEqual(dropTrigger.dropBehavior, .cascade) - XCTAssertEqual(dropTrigger.cascade, true) + #expect(dropTrigger.dropBehavior == .cascade) + #expect(dropTrigger.cascade == true) } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testOldQueryStringInterpolations() { - XCTAssertSerialization(of: self.db.raw("\(raw: "X") \("x")"), is: "X x") + @Test("old SQLQueryString interpolations") + func oldSQLQueryStringInterpolations() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(raw: "X") \("x")"), is: "X x") } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testRawBinds() { + @Test("raw binds") + func rawBinds() { let raw = SQLRaw("SQL", ["a", "b"]) - XCTAssertEqual(raw.sql, "SQL") - XCTAssertEqual(raw.binds[0] as? String, "a") - XCTAssertEqual(raw.binds[1] as? String, "b") + #expect(raw.sql == "SQL") + #expect(raw.binds[0] as? String == "a") + #expect(raw.binds[1] as? String == "b") } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testOldUnionJoiner() { - XCTAssertEqual(SQLUnionJoiner(all: true).type, .unionAll) - XCTAssertEqual(SQLUnionJoiner(all: false).type, .union) - XCTAssertEqual(SQLUnionJoiner(all: true).all, true) - XCTAssertEqual(SQLUnionJoiner(all: false).all, false) + @Test("old union joiner") + func oldUnionJoiner() { + #expect(SQLUnionJoiner(all: true).type == .unionAll) + #expect(SQLUnionJoiner(all: false).type == .union) + #expect(SQLUnionJoiner(all: true).all == true) + #expect(SQLUnionJoiner(all: false).all == false) var joiner1 = SQLUnionJoiner(type: .union) joiner1.all = true - XCTAssertEqual(joiner1.type, .unionAll) + #expect(joiner1.type == .unionAll) joiner1.all = true // This is not a copy-paste error; it adds coverage of the default case in the switch. joiner1.all = false - XCTAssertEqual(joiner1.type, .union) + #expect(joiner1.type == .union) var joiner2 = SQLUnionJoiner(type: .intersect) joiner2.all = true - XCTAssertEqual(joiner2.type, .intersectAll) + #expect(joiner2.type == .intersectAll) joiner2.all = false - XCTAssertEqual(joiner2.type, .intersect) + #expect(joiner2.type == .intersect) var joiner3 = SQLUnionJoiner(type: .except) joiner3.all = true - XCTAssertEqual(joiner3.type, .exceptAll) + #expect(joiner3.type == .exceptAll) joiner3.all = false - XCTAssertEqual(joiner3.type, .except) + #expect(joiner3.type == .except) } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testColumnWithTable() { - XCTAssertSerialization(of: self.db.select().column(table: "a", column: "b"), is: "SELECT ``a``.``b``") + @Test("column with table") + func columnWithTable() throws { + let db = TestDatabase() + + try expectSerialization(of: db.select().column(table: "a", column: "b"), is: "SELECT ``a``.``b``") } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testAlterTableBuilderColumns() { - XCTAssertNotNil(self.db.alter(table: "foo").column("a", type: .bigint).columns.first) - let builder = self.db.alter(table: "foo").column("a", type: .bigint) + @Test("ALTER TABLE builder columns") + func alterTableBuilderColumns() { + let db = TestDatabase() + + #expect(db.alter(table: "foo").column("a", type: .bigint).columns.first != nil) + let builder = db.alter(table: "foo").column("a", type: .bigint) builder.columns = [SQLColumnDefinition("a", dataType: .blob)] } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testCreateTriggerBuilderMethods() { - XCTAssertNotNil(self.db.create(trigger: "a", table: "b", when: .after, event: .delete).condition("a").body(["b"])) + @Test("CREATE TRIGGER builder methods") + func createTriggerBuilderMethods() { + let db = TestDatabase() + + #expect(db.create(trigger: "a", table: "b", when: .after, event: .delete).condition("a").body(["b"]).createTrigger.name is SQLIdentifier) } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testJoinBuilderMethod() { - XCTAssertNotNil(self.db.select().join("a", method: .inner, on: "a")) + @Test("JOIN builder method") + func joinBuilderMethod() { + let db = TestDatabase() + + #expect(!db.select().join("a", method: .inner, on: "a").select.joins.isEmpty) } @available(*, deprecated, message: "Contains tests of deprecated functionality") - func testObsoleteVersionComparators() { + @Test("obsolete version comparators") + func obsoleteVersionComparators() { struct TestVersion: SQLDatabaseReportedVersion { let stringValue: String } struct AnotherTestVersion: SQLDatabaseReportedVersion { let stringValue: String } /// `>` - XCTAssert(TestVersion(stringValue: "b").isNewer(than: TestVersion(stringValue: "a"))) - XCTAssertFalse(TestVersion(stringValue: "b").isNewer(than: AnotherTestVersion(stringValue: "a"))) + #expect(TestVersion(stringValue: "b").isNewer(than: TestVersion(stringValue: "a"))) + #expect(!TestVersion(stringValue: "b").isNewer(than: AnotherTestVersion(stringValue: "a"))) /// `<=` - XCTAssert(TestVersion(stringValue: "a").isNotNewer(than: TestVersion(stringValue: "b"))) - XCTAssert(TestVersion(stringValue: "a").isNotNewer(than: TestVersion(stringValue: "a"))) - XCTAssertFalse(TestVersion(stringValue: "a").isNotNewer(than: AnotherTestVersion(stringValue: "a"))) + #expect(TestVersion(stringValue: "a").isNotNewer(than: TestVersion(stringValue: "b"))) + #expect(TestVersion(stringValue: "a").isNotNewer(than: TestVersion(stringValue: "a"))) + #expect(!TestVersion(stringValue: "a").isNotNewer(than: AnotherTestVersion(stringValue: "a"))) /// `<` - XCTAssert(TestVersion(stringValue: "a").isOlder(than: TestVersion(stringValue: "b"))) - XCTAssertFalse(TestVersion(stringValue: "a").isOlder(than: AnotherTestVersion(stringValue: "b"))) + #expect(TestVersion(stringValue: "a").isOlder(than: TestVersion(stringValue: "b"))) + #expect(!TestVersion(stringValue: "a").isOlder(than: AnotherTestVersion(stringValue: "b"))) /// `>=` - XCTAssert(TestVersion(stringValue: "b").isNotOlder(than: TestVersion(stringValue: "a"))) - XCTAssert(TestVersion(stringValue: "a").isNotOlder(than: TestVersion(stringValue: "a"))) - XCTAssertFalse(TestVersion(stringValue: "b").isNotOlder(than: AnotherTestVersion(stringValue: "a"))) + #expect(TestVersion(stringValue: "b").isNotOlder(than: TestVersion(stringValue: "a"))) + #expect(TestVersion(stringValue: "a").isNotOlder(than: TestVersion(stringValue: "a"))) + #expect(!TestVersion(stringValue: "b").isNotOlder(than: AnotherTestVersion(stringValue: "a"))) } } diff --git a/Tests/SQLKitTests/SQLBetweenTests.swift b/Tests/SQLKitTests/SQLBetweenTests.swift index e799d33..763041c 100644 --- a/Tests/SQLKitTests/SQLBetweenTests.swift +++ b/Tests/SQLKitTests/SQLBetweenTests.swift @@ -1,77 +1,75 @@ import SQLKit -import XCTest +import Testing -final class SQLBetweenTests: XCTestCase { - var db = TestDatabase() +@Suite("SQLBetween tests") +struct SQLBetweenTests { + @Test("BETWEEN") + func between() throws { + let db = TestDatabase() - override class func setUp() { - XCTAssert(isLoggingConfigured) - } - - func testBetween() { - XCTAssertSerialization(of: self.db.select().where(SQLBetween("a", between: "a", and: "b")), is: "SELECT WHERE &1 BETWEEN &2 AND &3") - XCTAssertSerialization(of: self.db.select().where(SQLBetween("a", between: SQLIdentifier("a"), and: "b")), is: "SELECT WHERE &1 BETWEEN ``a`` AND &2") - XCTAssertSerialization(of: self.db.select().where(SQLBetween("a", between: "a", and: SQLIdentifier("b"))), is: "SELECT WHERE &1 BETWEEN &2 AND ``b``") - XCTAssertSerialization(of: self.db.select().where(SQLBetween("a", between: SQLIdentifier("a"), and: SQLIdentifier("b"))), is: "SELECT WHERE &1 BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().where(SQLBetween(SQLIdentifier("a"), between: "a", and: "b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") - XCTAssertSerialization(of: self.db.select().where(SQLBetween(SQLIdentifier("a"), between: SQLIdentifier("a"), and: "b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") - XCTAssertSerialization(of: self.db.select().where(SQLBetween(SQLIdentifier("a"), between: "a", and: SQLIdentifier("b"))), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") - XCTAssertSerialization(of: self.db.select().where(SQLBetween(operand: SQLIdentifier("a"), lowerBound: SQLIdentifier("a"), upperBound: SQLIdentifier("b"))), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().where(SQLBetween(column: "a", between: "a", and: "b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") - XCTAssertSerialization(of: self.db.select().where(SQLBetween(column: "a", between: SQLIdentifier("a"), and: "b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") - XCTAssertSerialization(of: self.db.select().where(SQLBetween(column: "a", between: "a", and: SQLIdentifier("b"))), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") - XCTAssertSerialization(of: self.db.select().where(SQLBetween(column: "a", between: SQLIdentifier("a"), and: SQLIdentifier("b"))), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().where(SQLBetween("a", between: "a", and: "b")), is: "SELECT WHERE &1 BETWEEN &2 AND &3") + try expectSerialization(of: db.select().where(SQLBetween("a", between: SQLIdentifier("a"), and: "b")), is: "SELECT WHERE &1 BETWEEN ``a`` AND &2") + try expectSerialization(of: db.select().where(SQLBetween("a", between: "a", and: SQLIdentifier("b"))), is: "SELECT WHERE &1 BETWEEN &2 AND ``b``") + try expectSerialization(of: db.select().where(SQLBetween("a", between: SQLIdentifier("a"), and: SQLIdentifier("b"))), is: "SELECT WHERE &1 BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().where(SQLBetween(SQLIdentifier("a"), between: "a", and: "b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") + try expectSerialization(of: db.select().where(SQLBetween(SQLIdentifier("a"), between: SQLIdentifier("a"), and: "b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") + try expectSerialization(of: db.select().where(SQLBetween(SQLIdentifier("a"), between: "a", and: SQLIdentifier("b"))), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") + try expectSerialization(of: db.select().where(SQLBetween(operand: SQLIdentifier("a"), lowerBound: SQLIdentifier("a"), upperBound: SQLIdentifier("b"))), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().where(SQLBetween(column: "a", between: "a", and: "b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") + try expectSerialization(of: db.select().where(SQLBetween(column: "a", between: SQLIdentifier("a"), and: "b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") + try expectSerialization(of: db.select().where(SQLBetween(column: "a", between: "a", and: SQLIdentifier("b"))), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") + try expectSerialization(of: db.select().where(SQLBetween(column: "a", between: SQLIdentifier("a"), and: SQLIdentifier("b"))), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().where("a", between: "a", and: "b"), is: "SELECT WHERE &1 BETWEEN &2 AND &3") - XCTAssertSerialization(of: self.db.select().where("a", between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE &1 BETWEEN ``a`` AND &2") - XCTAssertSerialization(of: self.db.select().where("a", between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE &1 BETWEEN &2 AND ``b``") - XCTAssertSerialization(of: self.db.select().where("a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE &1 BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().where(SQLIdentifier("a"), between: "a", and: "b"), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") - XCTAssertSerialization(of: self.db.select().where(SQLIdentifier("a"), between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") - XCTAssertSerialization(of: self.db.select().where(SQLIdentifier("a"), between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") - XCTAssertSerialization(of: self.db.select().where(SQLIdentifier("a"), between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().where(column: "a", between: "a", and: "b"), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") - XCTAssertSerialization(of: self.db.select().where(column: "a", between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") - XCTAssertSerialization(of: self.db.select().where(column: "a", between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") - XCTAssertSerialization(of: self.db.select().where(column: "a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().where("a", between: "a", and: "b"), is: "SELECT WHERE &1 BETWEEN &2 AND &3") + try expectSerialization(of: db.select().where("a", between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE &1 BETWEEN ``a`` AND &2") + try expectSerialization(of: db.select().where("a", between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE &1 BETWEEN &2 AND ``b``") + try expectSerialization(of: db.select().where("a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE &1 BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().where(SQLIdentifier("a"), between: "a", and: "b"), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") + try expectSerialization(of: db.select().where(SQLIdentifier("a"), between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") + try expectSerialization(of: db.select().where(SQLIdentifier("a"), between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") + try expectSerialization(of: db.select().where(SQLIdentifier("a"), between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().where(column: "a", between: "a", and: "b"), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") + try expectSerialization(of: db.select().where(column: "a", between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") + try expectSerialization(of: db.select().where(column: "a", between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") + try expectSerialization(of: db.select().where(column: "a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().orWhere("a", between: "a", and: "b"), is: "SELECT WHERE &1 BETWEEN &2 AND &3") - XCTAssertSerialization(of: self.db.select().orWhere("a", between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE &1 BETWEEN ``a`` AND &2") - XCTAssertSerialization(of: self.db.select().orWhere("a", between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE &1 BETWEEN &2 AND ``b``") - XCTAssertSerialization(of: self.db.select().orWhere("a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE &1 BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().orWhere(SQLIdentifier("a"), between: "a", and: "b"), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") - XCTAssertSerialization(of: self.db.select().orWhere(SQLIdentifier("a"), between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") - XCTAssertSerialization(of: self.db.select().orWhere(SQLIdentifier("a"), between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") - XCTAssertSerialization(of: self.db.select().orWhere(SQLIdentifier("a"), between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().orWhere(column: "a", between: "a", and: "b"), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") - XCTAssertSerialization(of: self.db.select().orWhere(column: "a", between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") - XCTAssertSerialization(of: self.db.select().orWhere(column: "a", between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") - XCTAssertSerialization(of: self.db.select().orWhere(column: "a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().orWhere("a", between: "a", and: "b"), is: "SELECT WHERE &1 BETWEEN &2 AND &3") + try expectSerialization(of: db.select().orWhere("a", between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE &1 BETWEEN ``a`` AND &2") + try expectSerialization(of: db.select().orWhere("a", between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE &1 BETWEEN &2 AND ``b``") + try expectSerialization(of: db.select().orWhere("a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE &1 BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().orWhere(SQLIdentifier("a"), between: "a", and: "b"), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") + try expectSerialization(of: db.select().orWhere(SQLIdentifier("a"), between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") + try expectSerialization(of: db.select().orWhere(SQLIdentifier("a"), between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") + try expectSerialization(of: db.select().orWhere(SQLIdentifier("a"), between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().orWhere(column: "a", between: "a", and: "b"), is: "SELECT WHERE ``a`` BETWEEN &1 AND &2") + try expectSerialization(of: db.select().orWhere(column: "a", between: SQLIdentifier("a"), and: "b"), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND &1") + try expectSerialization(of: db.select().orWhere(column: "a", between: "a", and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN &1 AND ``b``") + try expectSerialization(of: db.select().orWhere(column: "a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT WHERE ``a`` BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().having("a", between: "a", and: "b"), is: "SELECT HAVING &1 BETWEEN &2 AND &3") - XCTAssertSerialization(of: self.db.select().having("a", between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING &1 BETWEEN ``a`` AND &2") - XCTAssertSerialization(of: self.db.select().having("a", between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING &1 BETWEEN &2 AND ``b``") - XCTAssertSerialization(of: self.db.select().having("a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING &1 BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().having(SQLIdentifier("a"), between: "a", and: "b"), is: "SELECT HAVING ``a`` BETWEEN &1 AND &2") - XCTAssertSerialization(of: self.db.select().having(SQLIdentifier("a"), between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND &1") - XCTAssertSerialization(of: self.db.select().having(SQLIdentifier("a"), between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN &1 AND ``b``") - XCTAssertSerialization(of: self.db.select().having(SQLIdentifier("a"), between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().having(column: "a", between: "a", and: "b"), is: "SELECT HAVING ``a`` BETWEEN &1 AND &2") - XCTAssertSerialization(of: self.db.select().having(column: "a", between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND &1") - XCTAssertSerialization(of: self.db.select().having(column: "a", between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN &1 AND ``b``") - XCTAssertSerialization(of: self.db.select().having(column: "a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().having("a", between: "a", and: "b"), is: "SELECT HAVING &1 BETWEEN &2 AND &3") + try expectSerialization(of: db.select().having("a", between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING &1 BETWEEN ``a`` AND &2") + try expectSerialization(of: db.select().having("a", between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING &1 BETWEEN &2 AND ``b``") + try expectSerialization(of: db.select().having("a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING &1 BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().having(SQLIdentifier("a"), between: "a", and: "b"), is: "SELECT HAVING ``a`` BETWEEN &1 AND &2") + try expectSerialization(of: db.select().having(SQLIdentifier("a"), between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND &1") + try expectSerialization(of: db.select().having(SQLIdentifier("a"), between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN &1 AND ``b``") + try expectSerialization(of: db.select().having(SQLIdentifier("a"), between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().having(column: "a", between: "a", and: "b"), is: "SELECT HAVING ``a`` BETWEEN &1 AND &2") + try expectSerialization(of: db.select().having(column: "a", between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND &1") + try expectSerialization(of: db.select().having(column: "a", between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN &1 AND ``b``") + try expectSerialization(of: db.select().having(column: "a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().orHaving("a", between: "a", and: "b"), is: "SELECT HAVING &1 BETWEEN &2 AND &3") - XCTAssertSerialization(of: self.db.select().orHaving("a", between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING &1 BETWEEN ``a`` AND &2") - XCTAssertSerialization(of: self.db.select().orHaving("a", between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING &1 BETWEEN &2 AND ``b``") - XCTAssertSerialization(of: self.db.select().orHaving("a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING &1 BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().orHaving(SQLIdentifier("a"), between: "a", and: "b"), is: "SELECT HAVING ``a`` BETWEEN &1 AND &2") - XCTAssertSerialization(of: self.db.select().orHaving(SQLIdentifier("a"), between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND &1") - XCTAssertSerialization(of: self.db.select().orHaving(SQLIdentifier("a"), between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN &1 AND ``b``") - XCTAssertSerialization(of: self.db.select().orHaving(SQLIdentifier("a"), between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND ``b``") - XCTAssertSerialization(of: self.db.select().orHaving(column: "a", between: "a", and: "b"), is: "SELECT HAVING ``a`` BETWEEN &1 AND &2") - XCTAssertSerialization(of: self.db.select().orHaving(column: "a", between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND &1") - XCTAssertSerialization(of: self.db.select().orHaving(column: "a", between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN &1 AND ``b``") - XCTAssertSerialization(of: self.db.select().orHaving(column: "a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().orHaving("a", between: "a", and: "b"), is: "SELECT HAVING &1 BETWEEN &2 AND &3") + try expectSerialization(of: db.select().orHaving("a", between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING &1 BETWEEN ``a`` AND &2") + try expectSerialization(of: db.select().orHaving("a", between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING &1 BETWEEN &2 AND ``b``") + try expectSerialization(of: db.select().orHaving("a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING &1 BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().orHaving(SQLIdentifier("a"), between: "a", and: "b"), is: "SELECT HAVING ``a`` BETWEEN &1 AND &2") + try expectSerialization(of: db.select().orHaving(SQLIdentifier("a"), between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND &1") + try expectSerialization(of: db.select().orHaving(SQLIdentifier("a"), between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN &1 AND ``b``") + try expectSerialization(of: db.select().orHaving(SQLIdentifier("a"), between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND ``b``") + try expectSerialization(of: db.select().orHaving(column: "a", between: "a", and: "b"), is: "SELECT HAVING ``a`` BETWEEN &1 AND &2") + try expectSerialization(of: db.select().orHaving(column: "a", between: SQLIdentifier("a"), and: "b"), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND &1") + try expectSerialization(of: db.select().orHaving(column: "a", between: "a", and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN &1 AND ``b``") + try expectSerialization(of: db.select().orHaving(column: "a", between: SQLIdentifier("a"), and: SQLIdentifier("b")), is: "SELECT HAVING ``a`` BETWEEN ``a`` AND ``b``") } } diff --git a/Tests/SQLKitTests/SQLCodingTests.swift b/Tests/SQLKitTests/SQLCodingTests.swift index d9de825..1a18663 100644 --- a/Tests/SQLKitTests/SQLCodingTests.swift +++ b/Tests/SQLKitTests/SQLCodingTests.swift @@ -1,56 +1,72 @@ +#if canImport(FoundationEssentials) +import struct FoundationEssentials.UUID +import class FoundationEssentials.JSONDecoder +import class FoundationEssentials.JSONEncoder +#else +import struct Foundation.UUID +import class Foundation.JSONDecoder +import class Foundation.JSONEncoder +#endif import OrderedCollections -@testable @_spi(CodableUtilities) import SQLKit -import XCTest - -final class SQLCodingTests: XCTestCase { - var db = TestDatabase() - - override class func setUp() { - XCTAssert(isLoggingConfigured) - } +@testable @_spi(CodableUtilities) public import SQLKit +import Testing +@Suite("Codable tests") +struct CodableTests { // MARK: - Query encoder - func testCodableWithNillableColumnWithSomeValue() { - let output = XCTAssertNoThrowWithResult(try self.db - .insert(into: "gasses") - .model(Gas(name: "iodine", color: "purple")) - .advancedSerialize() - ) + @Test("Codable with nillable column with some value") + func codableWithNillableColumnWithSomeValue() { + let db = TestDatabase() + #expect(throws: Never.self) { + let output = try db + .insert(into: "gasses") + .model(Gas(name: "iodine", color: "purple")) + .advancedSerialize() - XCTAssertEqual(output?.sql, "INSERT INTO ``gasses`` (``name``, ``color``) VALUES (&1, &2)") - XCTAssertEqual(output?.binds.count, 2) - XCTAssertEqual(output?.binds[0] as? String, "iodine") - XCTAssertEqual(output?.binds[1] as? String, "purple") + #expect(output.sql == "INSERT INTO ``gasses`` (``name``, ``color``) VALUES (&1, &2)") + #expect(output.binds.count == 2) + #expect(output.binds[0] as? String == "iodine") + #expect(output.binds[1] as? String == "purple") + } } - func testCodableWithNillableColumnWithNilValueWithoutNilEncodingStrategy() throws { - let output = XCTAssertNoThrowWithResult(try self.db - .insert(into: "gasses") - .model(Gas(name: "oxygen", color: nil)) - .advancedSerialize() - ) + @Test("Codable with nillable column with nil value without NilEncodingStrategy") + func codableWithNillableColumnWithNilValueWithoutNilEncodingStrategy() throws { + let db = TestDatabase() + #expect(throws: Never.self) { + let output = try db + .insert(into: "gasses") + .model(Gas(name: "oxygen", color: nil)) + .advancedSerialize() - XCTAssertEqual(output?.sql, "INSERT INTO ``gasses`` (``name``) VALUES (&1)") - XCTAssertEqual(output?.binds.count, 1) - XCTAssertEqual(output?.binds[0] as? String, "oxygen") + #expect(output.sql == "INSERT INTO ``gasses`` (``name``) VALUES (&1)") + #expect(output.binds.count == 1) + #expect(output.binds[0] as? String == "oxygen") + } } - func testCodableWithNillableColumnWithNilValueAndNilEncodingStrategy() throws { - let output = XCTAssertNoThrowWithResult(try self.db - .insert(into: "gasses") - .model(Gas(name: "oxygen", color: nil), nilEncodingStrategy: .asNil) - .advancedSerialize() - ) + @Test("Codable with nillable column with nil value and NilEncodingStrategy") + func codableWithNillableColumnWithNilValueAndNilEncodingStrategy() throws { + let db = TestDatabase() + #expect(throws: Never.self) { + let output = try db + .insert(into: "gasses") + .model(Gas(name: "oxygen", color: nil), nilEncodingStrategy: .asNil) + .advancedSerialize() - XCTAssertEqual(output?.sql, "INSERT INTO ``gasses`` (``name``, ``color``) VALUES (&1, NULL)") - XCTAssertEqual(output?.binds.count, 1) - XCTAssertEqual(output?.binds[0] as? String, "oxygen") + #expect(output.sql == "INSERT INTO ``gasses`` (``name``, ``color``) VALUES (&1, NULL)") + #expect(output.binds.count == 1) + #expect(output.binds[0] as? String == "oxygen") + } } - + // MARK: - Models - - func testInsertWithEncodableModel() { + + @Test("INSERT with Encodable model") + func insertWithEncodableModel() throws { + let db = TestDatabase() + struct TestModelPlain: Codable { var id: Int? var serial_number: UUID @@ -69,61 +85,61 @@ final class SQLCodingTests: XCTestCase { var StarId: Int var LastKnownStatus: String } - + @Sendable func handleSuperCase(_ path: [any CodingKey]) -> any CodingKey { SomeCodingKey(stringValue: path.last!.stringValue.decapitalized.convertedToSnakeCase) } - + let snakeEncoder = SQLQueryEncoder(keyEncodingStrategy: .convertToSnakeCase, nilEncodingStrategy: .asNil) let superEncoder = SQLQueryEncoder(prefix: "p_", keyEncodingStrategy: .custom({ handleSuperCase($0) }), nilEncodingStrategy: .asNil) - + db._dialect.upsertSyntax = .standard - XCTAssertSerialization( - of: try self.db.insert(into: "jumpgates").model(TestModelPlain(serial_number: .init(), star_id: 0, last_known_status: ""), nilEncodingStrategy: .asNil), + try expectSerialization( + of: try db.insert(into: "jumpgates").model(TestModelPlain(serial_number: .init(), star_id: 0, last_known_status: ""), nilEncodingStrategy: .asNil), is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (NULL, &1, &2, &3)" ) - XCTAssertSerialization( - of: try self.db.insert(into: "jumpgates").model(TestModelSnakeCase(serialNumber: .init(), starId: 0, lastKnownStatus: ""), with: snakeEncoder), + try expectSerialization( + of: try db.insert(into: "jumpgates").model(TestModelSnakeCase(serialNumber: .init(), starId: 0, lastKnownStatus: ""), with: snakeEncoder), is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (NULL, &1, &2, &3)" ) - XCTAssertSerialization( - of: try self.db.insert(into: "jumpgates").model(TestModelSuperCase(SerialNumber: .init(), StarId: 0, LastKnownStatus: ""), with: superEncoder), + try expectSerialization( + of: try db.insert(into: "jumpgates").model(TestModelSuperCase(SerialNumber: .init(), StarId: 0, LastKnownStatus: ""), with: superEncoder), is: "INSERT INTO ``jumpgates`` (``p_id``, ``p_serial_number``, ``p_star_id``, ``p_last_known_status``) VALUES (NULL, &1, &2, &3)" ) - XCTAssertSerialization( - of: try self.db.insert(into: "jumpgates").model(TestModelPlain(serial_number: .init(), star_id: 0, last_known_status: ""), nilEncodingStrategy: .asNil).ignoringConflicts(with: "star_id"), + try expectSerialization( + of: try db.insert(into: "jumpgates").model(TestModelPlain(serial_number: .init(), star_id: 0, last_known_status: ""), nilEncodingStrategy: .asNil).ignoringConflicts(with: "star_id"), is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (NULL, &1, &2, &3) ON CONFLICT (``star_id``) DO NOTHING" ) - XCTAssertSerialization( - of: try self.db.insert(into: "jumpgates").model(TestModelSnakeCase(serialNumber: .init(), starId: 0, lastKnownStatus: ""), with: snakeEncoder).ignoringConflicts(with: "star_id"), + try expectSerialization( + of: try db.insert(into: "jumpgates").model(TestModelSnakeCase(serialNumber: .init(), starId: 0, lastKnownStatus: ""), with: snakeEncoder).ignoringConflicts(with: "star_id"), is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (NULL, &1, &2, &3) ON CONFLICT (``star_id``) DO NOTHING" ) - XCTAssertSerialization( - of: try self.db.insert(into: "jumpgates").model(TestModelSuperCase(SerialNumber: .init(), StarId: 0, LastKnownStatus: ""), with: superEncoder).ignoringConflicts(with: "star_id"), + try expectSerialization( + of: try db.insert(into: "jumpgates").model(TestModelSuperCase(SerialNumber: .init(), StarId: 0, LastKnownStatus: ""), with: superEncoder).ignoringConflicts(with: "star_id"), is: "INSERT INTO ``jumpgates`` (``p_id``, ``p_serial_number``, ``p_star_id``, ``p_last_known_status``) VALUES (NULL, &1, &2, &3) ON CONFLICT (``star_id``) DO NOTHING" ) - XCTAssertSerialization( - of: try self.db.insert(into: "jumpgates") + try expectSerialization( + of: try db.insert(into: "jumpgates") .model(TestModelPlain(serial_number: .init(), star_id: 0, last_known_status: ""), nilEncodingStrategy: .asNil) .onConflict(with: ["star_id"]) { try $0 .set(excludedContentOf: TestModelPlain(serial_number: .init(), star_id: 0, last_known_status: ""), nilEncodingStrategy: .asNil) }, is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (NULL, &1, &2, &3) ON CONFLICT (``star_id``) DO UPDATE SET ``id`` = EXCLUDED.``id``, ``serial_number`` = EXCLUDED.``serial_number``, ``star_id`` = EXCLUDED.``star_id``, ``last_known_status`` = EXCLUDED.``last_known_status``" ) - XCTAssertSerialization( - of: try self.db.insert(into: "jumpgates") + try expectSerialization( + of: try db.insert(into: "jumpgates") .model(TestModelSnakeCase(serialNumber: .init(), starId: 0, lastKnownStatus: ""), with: snakeEncoder) .onConflict(with: ["star_id"]) { try $0 .set(excludedContentOf: TestModelSnakeCase(serialNumber: .init(), starId: 0, lastKnownStatus: ""), with: snakeEncoder) }, is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (NULL, &1, &2, &3) ON CONFLICT (``star_id``) DO UPDATE SET ``id`` = EXCLUDED.``id``, ``serial_number`` = EXCLUDED.``serial_number``, ``star_id`` = EXCLUDED.``star_id``, ``last_known_status`` = EXCLUDED.``last_known_status``" ) - XCTAssertSerialization( - of: try self.db.insert(into: "jumpgates") + try expectSerialization( + of: try db.insert(into: "jumpgates") .model(TestModelSuperCase(SerialNumber: .init(), StarId: 0, LastKnownStatus: ""), with: superEncoder) .onConflict(with: ["p_star_id"]) { try $0 .set(excludedContentOf: TestModelSuperCase(SerialNumber: .init(), StarId: 0, LastKnownStatus: ""), with: superEncoder) @@ -132,7 +148,10 @@ final class SQLCodingTests: XCTestCase { ) } - func testInsertWithEncodableModels() { + @Test("INSERT with Encodable models") + func insertWithEncodableModels() throws { + let db = TestDatabase() + struct TestModel: Codable, Equatable { var id: Int? var serial_number: UUID @@ -140,30 +159,33 @@ final class SQLCodingTests: XCTestCase { var last_known_status: String } - XCTAssertSerialization( - of: try self.db.insert(into: "jumpgates") + try expectSerialization( + of: try db.insert(into: "jumpgates") .models([ TestModel(serial_number: .init(), star_id: 0, last_known_status: ""), TestModel(serial_number: .init(), star_id: 1, last_known_status: ""), ]), is: "INSERT INTO ``jumpgates`` (``serial_number``, ``star_id``, ``last_known_status``) VALUES (&1, &2, &3), (&4, &5, &6)" ) - + let models = [ TestModel(id: 0, serial_number: .init(), star_id: 0, last_known_status: ""), TestModel(serial_number: .init(), star_id: 1, last_known_status: ""), ] - - XCTAssertThrowsError(try self.db.insert(into: "jumpgates").models(models)) { - guard case let .invalidValue(value, context) = $0 as? EncodingError else { - return XCTFail("Expected EncodingError.invalidValue, but got \(String(reflecting: $0))") - } - XCTAssertEqual(value as? TestModel, models[1]) - XCTAssert(context.codingPath.isEmpty) + + let error = #expect(throws: EncodingError.self) { try db.insert(into: "jumpgates").models(models) } + guard case let .invalidValue(value, context) = error else { + Issue.record("Expected EncodingError.invalidValue, but got \(String(reflecting: error))") + return } + #expect(value as? TestModel == models[1]) + #expect(context.codingPath.isEmpty) } - - func testUpdateWithEncodableModel() { + + @Test("UPDATE with Encodable model") + func updateWithEncodableModel() throws { + let db = TestDatabase() + struct TestModelPlain: Codable { var id: Int? var serial_number: UUID @@ -182,70 +204,72 @@ final class SQLCodingTests: XCTestCase { var StarId: Int var LastKnownStatus: String } - + @Sendable func handleSuperCase(_ path: [any CodingKey]) -> any CodingKey { SomeCodingKey(stringValue: path.last!.stringValue.decapitalized.convertedToSnakeCase) } - + let snakeEncoder = SQLQueryEncoder(keyEncodingStrategy: .convertToSnakeCase, nilEncodingStrategy: .asNil) let superEncoder = SQLQueryEncoder(prefix: "p_", keyEncodingStrategy: .custom({ handleSuperCase($0) }), nilEncodingStrategy: .asNil) - - XCTAssertSerialization( - of: try self.db.update("jumpgates").set(model: TestModelPlain(serial_number: .init(), star_id: 0, last_known_status: "")), + + try expectSerialization( + of: try db.update("jumpgates").set(model: TestModelPlain(serial_number: .init(), star_id: 0, last_known_status: "")), is: "UPDATE ``jumpgates`` SET ``serial_number`` = &1, ``star_id`` = &2, ``last_known_status`` = &3" ) - XCTAssertSerialization( - of: try self.db.update("jumpgates").set(model: TestModelPlain(serial_number: .init(), star_id: 0, last_known_status: ""), nilEncodingStrategy: .asNil), + try expectSerialization( + of: try db.update("jumpgates").set(model: TestModelPlain(serial_number: .init(), star_id: 0, last_known_status: ""), nilEncodingStrategy: .asNil), is: "UPDATE ``jumpgates`` SET ``id`` = NULL, ``serial_number`` = &1, ``star_id`` = &2, ``last_known_status`` = &3" ) - XCTAssertSerialization( - of: try self.db.update("jumpgates").set(model: TestModelSnakeCase(serialNumber: .init(), starId: 0, lastKnownStatus: ""), with: snakeEncoder), + try expectSerialization( + of: try db.update("jumpgates").set(model: TestModelSnakeCase(serialNumber: .init(), starId: 0, lastKnownStatus: ""), with: snakeEncoder), is: "UPDATE ``jumpgates`` SET ``id`` = NULL, ``serial_number`` = &1, ``star_id`` = &2, ``last_known_status`` = &3" ) - XCTAssertSerialization( - of: try self.db.update("jumpgates").set(model: TestModelSuperCase(SerialNumber: .init(), StarId: 0, LastKnownStatus: ""), with: superEncoder), + try expectSerialization( + of: try db.update("jumpgates").set(model: TestModelSuperCase(SerialNumber: .init(), StarId: 0, LastKnownStatus: ""), with: superEncoder), is: "UPDATE ``jumpgates`` SET ``p_id`` = NULL, ``p_serial_number`` = &1, ``p_star_id`` = &2, ``p_last_known_status`` = &3" ) } - - func testRowModelDecode() { + + @Test("per-row model decode") + func rowModelDecode() throws { struct Foo: Codable, Equatable { let a: String } let row = TestRow(data: ["a": "b"]) - XCTAssertEqual(try row.decode(model: Foo.self, keyDecodingStrategy: .useDefaultKeys), Foo(a: "b")) + #expect(try row.decode(model: Foo.self, keyDecodingStrategy: .useDefaultKeys) == Foo(a: "b")) } - func testHandleCodeCoverageCompleteness() { + @Test("code coverage completeness") + func handleCodeCoverageCompleteness() { /// There are certain code paths which can never be executed under any meaningful circumstances, but the /// compiler cannot determine this statically. This test performs deliberately pointless operations in order /// to mark those paths as covered by tests. - XCTAssertNil(NeverKey.init(stringValue: "")) - XCTAssertNil(NeverKey.init(intValue: 0)) - XCTAssert(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).codingPath.isEmpty) - XCTAssert(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).userInfo.isEmpty) - XCTAssertEqual(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).count, 0) - XCTAssertThrowsError(try FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).encodeNil()) - XCTAssertThrowsError(try FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).encodeNil(forKey: .init(stringValue: ""))) - XCTAssertNotNil(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).superEncoder()) - XCTAssertNotNil(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).superEncoder(forKey: .init(stringValue: ""))) - XCTAssertNotNil(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).unkeyedContainer()) - XCTAssertNotNil(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).nestedUnkeyedContainer()) - XCTAssertNotNil(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).nestedUnkeyedContainer(forKey: .init(stringValue: ""))) - XCTAssertNotNil(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).container(keyedBy: NeverKey.self)) - XCTAssertNotNil(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).nestedContainer(keyedBy: NeverKey.self)) - XCTAssertNotNil(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).nestedContainer(keyedBy: NeverKey.self, forKey: .init(stringValue: ""))) - XCTAssertNotNil(DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "")).under(path: [])) - XCTAssertNotNil(DecodingError.typeMismatch(Void.self, .init(codingPath: [], debugDescription: "")).under(path: [])) - XCTAssertNotNil(DecodingError.keyNotFound(SomeCodingKey(stringValue: ""), .init(codingPath: [], debugDescription: "")).under(path: [])) - XCTAssertNoThrow(try JSONDecoder().decode(FakeSendableCodable.self, from: JSONEncoder().encode(FakeSendableCodable(true)))) - XCTAssertNotEqual(FakeSendableCodable(true), FakeSendableCodable(false)) - XCTAssertFalse(Set([FakeSendableCodable(true)]).isEmpty) - XCTAssertEqual(FakeSendableCodable(true).description, true.description) - XCTAssertEqual(FakeSendableCodable("").debugDescription, "".debugDescription) - XCTAssertFalse(SQLCodingError.unsupportedOperation("", codingPath: [SomeCodingKey(stringValue: "")]).description.isEmpty) - XCTAssertEqual(SomeCodingKey(intValue: 0).intValue, 0) + #expect(NeverKey.init(stringValue: "") == nil) + #expect(NeverKey.init(intValue: 0) == nil) + #expect(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).codingPath.isEmpty) + #expect(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).userInfo.isEmpty) + #expect(FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).count == 0) + #expect(throws: (any Error).self) { try FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).encodeNil() } + #expect(throws: (any Error).self) { try FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).encodeNil(forKey: .init(stringValue: "")) } + #expect(throws: Never.self) { FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).superEncoder() } + #expect(throws: Never.self) { FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).superEncoder(forKey: .init(stringValue: "")) } + #expect(throws: Never.self) { FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).unkeyedContainer() } + #expect(throws: Never.self) { FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).nestedUnkeyedContainer() } + #expect(throws: Never.self) { FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).nestedUnkeyedContainer(forKey: .init(stringValue: "")) } + #expect(throws: Never.self) { FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).container(keyedBy: NeverKey.self) } + #expect(throws: Never.self) { FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).nestedContainer(keyedBy: NeverKey.self) } + #expect(throws: Never.self) { FailureEncoder(SQLCodingError.unsupportedOperation("", codingPath: [])).nestedContainer(keyedBy: NeverKey.self, forKey: .init(stringValue: "")) } + #expect(throws: Never.self) { DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "")).under(path: []) } + #expect(throws: Never.self) { DecodingError.typeMismatch(Void.self, .init(codingPath: [], debugDescription: "")).under(path: []) } + #expect(throws: Never.self) { DecodingError.keyNotFound(SomeCodingKey(stringValue: ""), .init(codingPath: [], debugDescription: "")).under(path: []) } + #expect(throws: Never.self) { try JSONDecoder().decode(FakeSendableCodable.self, from: JSONEncoder().encode(FakeSendableCodable(true))) } + #expect(FakeSendableCodable(true) != FakeSendableCodable(false)) + #expect(!Set([FakeSendableCodable(true)]).isEmpty) + #expect(FakeSendableCodable(true).description == true.description) + #expect(FakeSendableCodable("").debugDescription == "".debugDescription) + #expect(!SQLCodingError.unsupportedOperation("", codingPath: [SomeCodingKey(stringValue: "")]).description.isEmpty) + #expect(SomeCodingKey(intValue: 0).intValue == 0) } } diff --git a/Tests/SQLKitTests/SQLCommonTableExpressionTests.swift b/Tests/SQLKitTests/SQLCommonTableExpressionTests.swift index 24adcd4..969a4e2 100644 --- a/Tests/SQLKitTests/SQLCommonTableExpressionTests.swift +++ b/Tests/SQLKitTests/SQLCommonTableExpressionTests.swift @@ -1,252 +1,270 @@ import SQLKit -import XCTest +import Testing -final class SQLCommonTableExpressionTests: XCTestCase { - var db = TestDatabase() +@Suite("CTE tests") +struct CommonTableExpressionTests { + @Test("SELECT query with CTE") + func selectQueryWithCTE() throws { + let db = TestDatabase() - override class func setUp() { - XCTAssert(isLoggingConfigured) - } - - func testSelectQueryWithCTE() { - XCTAssertSerialization( - of: self.db.select().with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.select().with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.select().with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.select().with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.select().with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.select().with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.select().with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.select().with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.select().with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.select().with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.select().with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.select().with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.select().with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.select().with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.select().with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.select().with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``" ) } - func testUpdateQueryWithCTE() { - XCTAssertSerialization( - of: self.db.update("t").with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), + @Test("UPDATE query with CTE") + func updateQueryWithCTE() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.update("t").with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.update("t").with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), + try expectSerialization( + of: db.update("t").with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.update("t").with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), + try expectSerialization( + of: db.update("t").with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.update("t").with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), + try expectSerialization( + of: db.update("t").with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.update("t").with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), + try expectSerialization( + of: db.update("t").with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.update("t").with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), + try expectSerialization( + of: db.update("t").with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.update("t").with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), + try expectSerialization( + of: db.update("t").with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.update("t").with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), + try expectSerialization( + of: db.update("t").with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``" ) } - func testInsertQueryWithCTE() { - XCTAssertSerialization( - of: self.db.insert(into: "t").with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), + @Test("INSERT query with CTE") + func insertQueryWithCTE() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.insert(into: "t").with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.insert(into: "t").with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.insert(into: "t").with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.insert(into: "t").with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.insert(into: "t").with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.insert(into: "t").with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.insert(into: "t").with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.insert(into: "t").with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.insert(into: "t").with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.insert(into: "t").with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.insert(into: "t").with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.insert(into: "t").with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.insert(into: "t").with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.insert(into: "t").with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), + try expectSerialization( + of: db.insert(into: "t").with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)" ) } - func testDeleteQueryWithCTE() { - XCTAssertSerialization( - of: self.db.delete(from: "t").with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), + @Test("DELETE query with CTE") + func deleteQueryWithCTE() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.delete(from: "t").with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.delete(from: "t").with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), + try expectSerialization( + of: db.delete(from: "t").with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.delete(from: "t").with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), + try expectSerialization( + of: db.delete(from: "t").with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.delete(from: "t").with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), + try expectSerialization( + of: db.delete(from: "t").with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.delete(from: "t").with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), + try expectSerialization( + of: db.delete(from: "t").with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.delete(from: "t").with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), + try expectSerialization( + of: db.delete(from: "t").with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.delete(from: "t").with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), + try expectSerialization( + of: db.delete(from: "t").with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``" ) - XCTAssertSerialization( - of: self.db.delete(from: "t").with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), + try expectSerialization( + of: db.delete(from: "t").with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``" ) } - func testUnionQueryWithCTE() { - self.db._dialect.unionFeatures = [.union, .unionAll, .intersect, .intersectAll, .except, .exceptAll, .parenthesizedSubqueries] + @Test("UNION query with CTE") + func unionQueryWithCTE() throws { + let db = TestDatabase() + + db._dialect.unionFeatures = [.union, .unionAll, .intersect, .intersectAll, .except, .exceptAll, .parenthesizedSubqueries] - XCTAssertSerialization( - of: self.db.union { $0 }.with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), + try expectSerialization( + of: db.union { $0 }.with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), is: "WITH ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.union { $0 }.with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), + try expectSerialization( + of: db.union { $0 }.with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), is: "WITH ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.union { $0 }.with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), + try expectSerialization( + of: db.union { $0 }.with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), is: "WITH ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.union { $0 }.with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), + try expectSerialization( + of: db.union { $0 }.with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), is: "WITH ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.union { $0 }.with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), + try expectSerialization( + of: db.union { $0 }.with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.union { $0 }.with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), + try expectSerialization( + of: db.union { $0 }.with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.union { $0 }.with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), + try expectSerialization( + of: db.union { $0 }.with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)" ) - XCTAssertSerialization( - of: self.db.union { $0 }.with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), + try expectSerialization( + of: db.union { $0 }.with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }), is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)" ) } - - func testMultipleCTEs() { - XCTAssertSerialization( - of: self.db.select() + + @Test("multiple CTEs") + func multipleCTEs() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.select() .with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }) .with("d", columns: ["e"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }) .column(SQLColumn("b", table: "a")), is: "WITH ``a`` (``b``) AS (SELECT 1), ``d`` (``e``) AS (SELECT 1) SELECT ``a``.``b``" ) } - - func testCodeCoverage() { - var query = self.db.select().column(SQLColumn("b", table: "a")).select + + @Test("code coverage") + func codeCoverage() throws { + let db = TestDatabase() + var query = db.select().column(SQLColumn("b", table: "a")).select query.tableExpressionGroup = .init(tableExpressions: [ SQLCommonTableExpression(alias: SQLIdentifier("x"), query: SQLRaw("VALUES(``1``)")), SQLCommonTableExpression(alias: SQLIdentifier("x"), query: SQLGroupExpression(SQLRaw("VALUES(``1``)"))), SQLGroupExpression(SQLRaw("FOO")) ]) - - XCTAssertSerialization(of: self.db.raw("\(query)"), is: "WITH ``x`` AS (VALUES(``1``)), ``x`` AS (VALUES(``1``)), (FOO) SELECT ``a``.``b``") + + try expectSerialization(of: db.raw("\(query)"), is: "WITH ``x`` AS (VALUES(``1``)), ``x`` AS (VALUES(``1``)), (FOO) SELECT ``a``.``b``") } - - func testMoreRealisticCTEs() { + + @Test("more realistic CTEs") + func moreRealisticCTEs() throws { + let db = TestDatabase() + // Simple sub-SELECT avoidance // Taken from https://www.postgresql.org/docs/16/queries-with.html#QUERIES-WITH-SELECT - self.db._dialect.identifierQuote = SQLRaw("\"") - XCTAssertSerialization( - of: self.db.select() + db._dialect.identifierQuote = SQLRaw("\"") + try expectSerialization( + of: db.select() .with("regional_sales", as: SQLSubquery.select { $0 .column("region").column(SQLFunction("SUM", args: SQLColumn("amount")), as: "total_sales").from("orders").groupBy("region") }) @@ -271,13 +289,13 @@ final class SQLCommonTableExpressionTests: XCTestCase { GROUP BY "region", "product" """.replacing("\n", with: " ") ) - + // Fibonacci series generator // Taken from https://dev.mysql.com/doc/refman/8.4/en/with.html#common-table-expressions-recursive-fibonacci-series - self.db._dialect.identifierQuote = SQLRaw("`") - self.db._dialect.unionFeatures = [.union, .unionAll] - XCTAssertSerialization( - of: self.db.select() + db._dialect.identifierQuote = SQLRaw("`") + db._dialect.unionFeatures = [.union, .unionAll] + try expectSerialization( + of: db.select() .with(recursive: "fibonacci", columns: ["n", "fib_n", "next_fib_n"], as: SQLSubquery.union { $0 .columns(SQLLiteral.numeric("1"), SQLLiteral.numeric("0"), SQLLiteral.numeric("1")) }.union(all: { $0 diff --git a/Tests/SQLKitTests/SQLCreateDropTriggerTests.swift b/Tests/SQLKitTests/SQLCreateDropTriggerTests.swift index c65f58a..534f25a 100644 --- a/Tests/SQLKitTests/SQLCreateDropTriggerTests.swift +++ b/Tests/SQLKitTests/SQLCreateDropTriggerTests.swift @@ -1,65 +1,72 @@ import SQLKit -import XCTest +import Testing -final class SQLCreateDropTriggerTests: XCTestCase { +@Suite("CREATE/DROP TRIGGER tests") +struct CreateDropTriggerTests { private let body = [ "IF NEW.amount < 0 THEN", "SET NEW.amount = 0;", "END IF;", ] - private var db = TestDatabase() + @Test("DROP TRIGGER options") + func dropTriggerOptions() throws { + let db = TestDatabase() - override class func setUp() { - XCTAssert(isLoggingConfigured) - } + db._dialect.triggerSyntax = .init(drop: [.supportsCascade, .supportsTableName]) + try expectSerialization(of: db.drop(trigger: "foo").table("planets"), is: "DROP TRIGGER ``foo`` ON ``planets`` RESTRICT") + try expectSerialization(of: db.drop(trigger: "foo").table("planets").ifExists(), is: "DROP TRIGGER IF EXISTS ``foo`` ON ``planets`` RESTRICT") + try expectSerialization(of: db.drop(trigger: "foo").table("planets").ifExists().restrict(), is: "DROP TRIGGER IF EXISTS ``foo`` ON ``planets`` RESTRICT") + try expectSerialization(of: db.drop(trigger: "foo").table("planets").ifExists().cascade(), is: "DROP TRIGGER IF EXISTS ``foo`` ON ``planets`` CASCADE") + + db._dialect.supportsIfExists = false + try expectSerialization(of: db.drop(trigger: "foo").table("planets").ifExists(), is: "DROP TRIGGER ``foo`` ON ``planets`` RESTRICT") - func testDropTriggerOptions() { - self.db._dialect.triggerSyntax = .init(drop: [.supportsCascade, .supportsTableName]) - XCTAssertSerialization(of: self.db.drop(trigger: "foo").table("planets"), is: "DROP TRIGGER ``foo`` ON ``planets`` RESTRICT") - XCTAssertSerialization(of: self.db.drop(trigger: "foo").table("planets").ifExists(), is: "DROP TRIGGER IF EXISTS ``foo`` ON ``planets`` RESTRICT") - XCTAssertSerialization(of: self.db.drop(trigger: "foo").table("planets").ifExists().restrict(), is: "DROP TRIGGER IF EXISTS ``foo`` ON ``planets`` RESTRICT") - XCTAssertSerialization(of: self.db.drop(trigger: "foo").table("planets").ifExists().cascade(), is: "DROP TRIGGER IF EXISTS ``foo`` ON ``planets`` CASCADE") - - self.db._dialect.supportsIfExists = false - XCTAssertSerialization(of: self.db.drop(trigger: "foo").table("planets").ifExists(), is: "DROP TRIGGER ``foo`` ON ``planets`` RESTRICT") - - self.db._dialect.triggerSyntax.drop = .supportsCascade - XCTAssertSerialization(of: self.db.drop(trigger: "foo").table("planets"), is: "DROP TRIGGER ``foo`` RESTRICT") - - self.db._dialect.triggerSyntax.drop = [] - XCTAssertSerialization(of: self.db.drop(trigger: "foo").table("planets").ifExists().restrict(), is: "DROP TRIGGER ``foo``") - XCTAssertSerialization(of: self.db.drop(trigger: "foo").table("planets").ifExists().cascade(), is: "DROP TRIGGER ``foo``") + db._dialect.triggerSyntax.drop = .supportsCascade + try expectSerialization(of: db.drop(trigger: "foo").table("planets"), is: "DROP TRIGGER ``foo`` RESTRICT") + + db._dialect.triggerSyntax.drop = [] + try expectSerialization(of: db.drop(trigger: "foo").table("planets").ifExists().restrict(), is: "DROP TRIGGER ``foo``") + try expectSerialization(of: db.drop(trigger: "foo").table("planets").ifExists().cascade(), is: "DROP TRIGGER ``foo``") } - func testMySqlTriggerCreates() { - self.db._dialect.triggerSyntax = .init(create: [.supportsBody, .supportsOrder, .supportsDefiner, .requiresForEachRow]) + @Test("CREATE TRIGGER for MySQL") + func mySqlTriggerCreates() throws { + let db = TestDatabase() + + db._dialect.triggerSyntax = .init(create: [.supportsBody, .supportsOrder, .supportsDefiner, .requiresForEachRow]) - let builder = self.db.create(trigger: "foo", table: "planet", when: .before, event: .insert) + let builder = db.create(trigger: "foo", table: "planet", when: .before, event: .insert) .body(self.body.map { SQLRaw($0) }) .order(precedence: .precedes, otherTriggerName: "other") builder.createTrigger.definer = SQLLiteral.string("foo@bar") - - XCTAssertSerialization( + + try expectSerialization( of: builder, is: "CREATE DEFINER = 'foo@bar' TRIGGER ``foo`` BEFORE INSERT ON ``planet`` FOR EACH ROW PRECEDES ``other`` BEGIN \(self.body.joined(separator: " ")) END;" ) } - func testSqliteTriggerCreates() { - self.db._dialect.triggerSyntax = .init(create: [.supportsBody, .supportsCondition]) - XCTAssertSerialization( - of: self.db.create(trigger: "foo", table: "planet", when: .before, event: .insert) + @Test("CREATE TRIGGER for SQLite") + func sqliteTriggerCreates() throws { + let db = TestDatabase() + + db._dialect.triggerSyntax = .init(create: [.supportsBody, .supportsCondition]) + try expectSerialization( + of: db.create(trigger: "foo", table: "planet", when: .before, event: .insert) .body(self.body.map { SQLRaw($0) }) .condition("\(ident: "foo") = \(ident: "bar")" as SQLQueryString), is: "CREATE TRIGGER ``foo`` BEFORE INSERT ON ``planet`` WHEN ``foo`` = ``bar`` BEGIN \(self.body.joined(separator: " ")) END;" ) } - func testPostgreSqlTriggerCreates() { - self.db._dialect.triggerSyntax = .init(create: [.supportsForEach, .postgreSQLChecks, .supportsCondition, .conditionRequiresParentheses, .supportsConstraints]) - XCTAssertSerialization( - of: self.db.create(trigger: "foo", table: "planet", when: .after, event: .insert) + @Test("CREATE TRIGGER for PostgreSQL") + func postgreSqlTriggerCreates() throws { + let db = TestDatabase() + + db._dialect.triggerSyntax = .init(create: [.supportsForEach, .postgreSQLChecks, .supportsCondition, .conditionRequiresParentheses, .supportsConstraints]) + try expectSerialization( + of: db.create(trigger: "foo", table: "planet", when: .after, event: .insert) .each(.row) .isConstraint() .timing(.deferredByDefault) @@ -68,24 +75,27 @@ final class SQLCreateDropTriggerTests: XCTestCase { .referencedTable("galaxies"), is: "CREATE CONSTRAINT TRIGGER ``foo`` AFTER INSERT ON ``planet`` FROM ``galaxies`` DEFERRABLE INITIALLY DEFERRED FOR EACH ROW WHEN (``foo`` = ``bar``) EXECUTE PROCEDURE ``qwer``" ) - XCTAssertSerialization( - of: self.db.create(trigger: "foo", table: "planet", when: .instead, event: .insert) + try expectSerialization( + of: db.create(trigger: "foo", table: "planet", when: .instead, event: .insert) .each(.row) .procedure("qwer"), is: "CREATE TRIGGER ``foo`` INSTEAD OF INSERT ON ``planet`` FOR EACH ROW EXECUTE PROCEDURE ``qwer``" ) - XCTAssertSerialization( - of: self.db.create(trigger: "foo", table: "planet", when: .instead, event: .update) + try expectSerialization( + of: db.create(trigger: "foo", table: "planet", when: .instead, event: .update) .each(.row) .procedure("qwer"), is: "CREATE TRIGGER ``foo`` INSTEAD OF UPDATE ON ``planet`` FOR EACH ROW EXECUTE PROCEDURE ``qwer``" ) } - - func testPostgreSqlTriggerCreateWithColumns() { - self.db._dialect.triggerSyntax = .init(create: [.supportsForEach, .postgreSQLChecks, .supportsCondition, .conditionRequiresParentheses, .supportsConstraints, .supportsUpdateColumns]) - XCTAssertSerialization( - of: self.db.create(trigger: "foo", table: "planet", when: .after, event: .update) + + @Test("CREATE TRIGGER with columns for PostgreSQL") + func postgreSqlTriggerCreateWithColumns() throws { + let db = TestDatabase() + + db._dialect.triggerSyntax = .init(create: [.supportsForEach, .postgreSQLChecks, .supportsCondition, .conditionRequiresParentheses, .supportsConstraints, .supportsUpdateColumns]) + try expectSerialization( + of: db.create(trigger: "foo", table: "planet", when: .after, event: .update) .each(.row) .columns(["foo"]) .condition("\(ident: "foo") = \(ident: "bar")" as SQLQueryString) @@ -93,50 +103,56 @@ final class SQLCreateDropTriggerTests: XCTestCase { .referencedTable("galaxies"), is: "CREATE TRIGGER ``foo`` AFTER UPDATE OF ``foo`` ON ``planet`` FROM ``galaxies`` FOR EACH ROW WHEN (``foo`` = ``bar``) EXECUTE PROCEDURE ``qwer``" ) - XCTAssertSerialization( - of: self.db.create(trigger: "foo", table: "planet", when: .after, event: .insert) + try expectSerialization( + of: db.create(trigger: "foo", table: "planet", when: .after, event: .insert) .each(.row) .procedure("qwer"), is: "CREATE TRIGGER ``foo`` AFTER INSERT ON ``planet`` FOR EACH ROW EXECUTE PROCEDURE ``qwer``" ) } - - func testAdditionalInitializer() { - self.db._dialect.triggerSyntax = .init(create: [.supportsBody, .supportsCondition]) + + @Test("additional initializer") + func additionalInitializer() throws { + let db = TestDatabase() + + db._dialect.triggerSyntax = .init(create: [.supportsBody, .supportsCondition]) var query = SQLCreateTrigger(trigger: "t", table: "tab", when: .after, event: .delete) query.body = self.body.map { SQLRaw($0) } - XCTAssertSerialization(of: self.db.raw("\(query)"), is: "CREATE TRIGGER ``t`` AFTER DELETE ON ``tab`` BEGIN IF NEW.amount < 0 THEN SET NEW.amount = 0; END IF; END;") + try expectSerialization(of: db.raw("\(query)"), is: "CREATE TRIGGER ``t`` AFTER DELETE ON ``tab`` BEGIN IF NEW.amount < 0 THEN SET NEW.amount = 0; END IF; END;") } - - func testInvalidTriggerCreates() { - self.db._dialect.triggerSyntax = .init(create: [.postgreSQLChecks, .supportsUpdateColumns, .supportsCondition, .supportsConstraints], drop: []) - XCTAssertSerialization( - of: self.db.create(trigger: "foo", table: "planet", when: .instead, event: .update).columns(["foo"]).timing(.deferrable), + + @Test("invalid CREATE TRIGGERs") + func invalidTriggerCreates() throws { + let db = TestDatabase() + + db._dialect.triggerSyntax = .init(create: [.postgreSQLChecks, .supportsUpdateColumns, .supportsCondition, .supportsConstraints], drop: []) + try expectSerialization( + of: db.create(trigger: "foo", table: "planet", when: .instead, event: .update).columns(["foo"]).timing(.deferrable), is: "CREATE TRIGGER ``foo`` INSTEAD OF UPDATE OF ``foo`` ON ``planet`` DEFERRABLE INITIALLY IMMEDIATE" ) - XCTAssertSerialization( - of: self.db.create(trigger: "foo", table: "planet", when: .instead, event: .insert).columns(["foo"]).condition(SQLLiteral.boolean(true)), + try expectSerialization( + of: db.create(trigger: "foo", table: "planet", when: .instead, event: .insert).columns(["foo"]).condition(SQLLiteral.boolean(true)), is: "CREATE TRIGGER ``foo`` INSTEAD OF INSERT OF ``foo`` ON ``planet`` WHEN TROO" ) - XCTAssertSerialization( - of: self.db.create(trigger: "foo", table: "planet", when: .before, event: .update).isConstraint().each(.statement), + try expectSerialization( + of: db.create(trigger: "foo", table: "planet", when: .before, event: .update).isConstraint().each(.statement), is: "CREATE CONSTRAINT TRIGGER ``foo`` BEFORE UPDATE ON ``planet``" ) - let builder = self.db.create(trigger: "foo", table: "planet", when: .before, event: .insert) + let builder = db.create(trigger: "foo", table: "planet", when: .before, event: .insert) .body(self.body.map { SQLRaw($0) }) .order(precedence: .precedes, otherTriggerName: "other") builder.createTrigger.definer = SQLLiteral.string("foo@bar") - - XCTAssertSerialization( + + try expectSerialization( of: builder, is: "CREATE TRIGGER ``foo`` BEFORE INSERT ON ``planet``" ) - - self.db._dialect.triggerSyntax.create.insert(.supportsBody) - XCTAssertSerialization( - of: self.db.create(trigger: "foo", table: "planet", when: .before, event: .update).isConstraint().each(.statement).procedure("foo"), + + db._dialect.triggerSyntax.create.insert(.supportsBody) + try expectSerialization( + of: db.create(trigger: "foo", table: "planet", when: .before, event: .update).isConstraint().each(.statement).procedure("foo"), is: "CREATE CONSTRAINT TRIGGER ``foo`` BEFORE UPDATE ON ``planet`` EXECUTE PROCEDURE ``foo``" ) } diff --git a/Tests/SQLKitTests/SQLCreateTableTests.swift b/Tests/SQLKitTests/SQLCreateTableTests.swift index 272e20d..b90d8a6 100644 --- a/Tests/SQLKitTests/SQLCreateTableTests.swift +++ b/Tests/SQLKitTests/SQLCreateTableTests.swift @@ -1,18 +1,16 @@ import SQLKit -import XCTest +import Testing -final class SQLCreateTableTests: XCTestCase { - var db = TestDatabase() - - override class func setUp() { - XCTAssert(isLoggingConfigured) - } - +@Suite("CREATE TABLE tests") +struct CreateTableTests { // MARK: Table Creation - func testColumnConstraints() { - XCTAssertSerialization( - of: self.db.create(table: "planets") + @Test("column constraints") + func columnConstraints() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets") .column("id", type: .bigint, .primaryKey) .column("name", type: .text, .default("unnamed")) .column("galaxy_id", type: .bigint, .references("galaxies", "id")) @@ -26,105 +24,126 @@ final class SQLCreateTableTests: XCTestCase { """ ) } - - func testConstraintLengthNormalization() { + + @Test("constraint length normalization") + func constraintLengthNormalization() { + let db = TestDatabase() + // Default impl is to leave as-is - XCTAssertEqual( - (db.dialect.normalizeSQLConstraint(identifier: SQLIdentifier("fk:obnoxiously_long_table_name.other_table_name_id+other_table_name.id")) as! SQLIdentifier).string, + #expect( + (db.dialect.normalizeSQLConstraint(identifier: SQLIdentifier("fk:obnoxiously_long_table_name.other_table_name_id+other_table_name.id")) as! SQLIdentifier).string == SQLIdentifier("fk:obnoxiously_long_table_name.other_table_name_id+other_table_name.id").string ) } - func testMultipleColumnConstraintsPerRow() { - XCTAssertSerialization( - of: self.db.create(table: "planets").column("id", type: .bigint, .notNull, .primaryKey), + @Test("multiple column constraints per row") + func multipleColumnConstraintsPerRow() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets").column("id", type: .bigint, .notNull, .primaryKey), is: "CREATE TABLE ``planets`` (``id`` BIGINT NOT NULL PRIMARY KEY AWWTOEINCREMENT)" ) } - func testPrimaryKeyColumnConstraintVariants() { - XCTAssertSerialization( - of: self.db.create(table: "planets1").column("id", type: .bigint, .primaryKey), + @Test("PRIMARY KEY column constraint variants") + func primaryKeyColumnConstraintVariants() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets1").column("id", type: .bigint, .primaryKey), is: "CREATE TABLE ``planets1`` (``id`` BIGINT PRIMARY KEY AWWTOEINCREMENT)" ) - XCTAssertSerialization( - of: self.db.create(table: "planets2").column("id", type: .bigint, .primaryKey(autoIncrement: false)), + try expectSerialization( + of: db.create(table: "planets2").column("id", type: .bigint, .primaryKey(autoIncrement: false)), is: "CREATE TABLE ``planets2`` (``id`` BIGINT PRIMARY KEY)" ) } - func testPrimaryKeyAutoIncrementVariants() { - self.db._dialect.supportsAutoIncrement = false + @Test("PRIMARY KEY auto-increment variants") + func primaryKeyAutoIncrementVariants() throws { + let db = TestDatabase() + + db._dialect.supportsAutoIncrement = false - XCTAssertSerialization( - of: self.db.create(table: "planets1").column("id", type: .bigint, .primaryKey), + try expectSerialization( + of: db.create(table: "planets1").column("id", type: .bigint, .primaryKey), is: "CREATE TABLE ``planets1`` (``id`` BIGINT PRIMARY KEY)" ) - XCTAssertSerialization( - of: self.db.create(table: "planets2").column("id", type: .bigint, .primaryKey(autoIncrement: false)), + try expectSerialization( + of: db.create(table: "planets2").column("id", type: .bigint, .primaryKey(autoIncrement: false)), is: "CREATE TABLE ``planets2`` (``id`` BIGINT PRIMARY KEY)" ) - self.db._dialect.supportsAutoIncrement = true + db._dialect.supportsAutoIncrement = true - XCTAssertSerialization( - of: self.db.create(table: "planets3").column("id", type: .bigint, .primaryKey), + try expectSerialization( + of: db.create(table: "planets3").column("id", type: .bigint, .primaryKey), is: "CREATE TABLE ``planets3`` (``id`` BIGINT PRIMARY KEY AWWTOEINCREMENT)" ) - XCTAssertSerialization( - of: self.db.create(table: "planets4").column("id", type: .bigint, .primaryKey(autoIncrement: false)), + try expectSerialization( + of: db.create(table: "planets4").column("id", type: .bigint, .primaryKey(autoIncrement: false)), is: "CREATE TABLE ``planets4`` (``id`` BIGINT PRIMARY KEY)" ) - - self.db._dialect.autoIncrementFunction = SQLRaw("NEXTUNIQUE") - XCTAssertSerialization( - of: self.db.create(table: "planets5").column("id", type: .bigint, .primaryKey), + db._dialect.autoIncrementFunction = SQLRaw("NEXTUNIQUE") + + try expectSerialization( + of: db.create(table: "planets5").column("id", type: .bigint, .primaryKey), is: "CREATE TABLE ``planets5`` (``id`` BIGINT DEFAULT NEXTUNIQUE PRIMARY KEY)" ) - XCTAssertSerialization( - of: self.db.create(table: "planets6").column("id", type: .bigint, .primaryKey(autoIncrement: false)), + try expectSerialization( + of: db.create(table: "planets6").column("id", type: .bigint, .primaryKey(autoIncrement: false)), is: "CREATE TABLE ``planets6`` (``id`` BIGINT PRIMARY KEY)" ) } - func testDefaultColumnConstraintVariants() { - XCTAssertSerialization( - of: self.db.create(table: "planets1").column("name", type: .text, .default("unnamed")), + @Test("DEFAULT column constraint variants") + func defaultColumnConstraintVariants() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets1").column("name", type: .text, .default("unnamed")), is: "CREATE TABLE ``planets1`` (``name`` TEXT DEFAULT 'unnamed')" ) - XCTAssertSerialization( - of: self.db.create(table: "planets2").column("diameter", type: .int, .default(10)), + try expectSerialization( + of: db.create(table: "planets2").column("diameter", type: .int, .default(10)), is: "CREATE TABLE ``planets2`` (``diameter`` INTEGER DEFAULT 10)" ) - XCTAssertSerialization( - of: self.db.create(table: "planets3").column("diameter", type: .real, .default(11.5)), + try expectSerialization( + of: db.create(table: "planets3").column("diameter", type: .real, .default(11.5)), is: "CREATE TABLE ``planets3`` (``diameter`` REAL DEFAULT 11.5)" ) - XCTAssertSerialization( - of: self.db.create(table: "planets4").column("current", type: .custom(SQLRaw("BOOLEAN")), .default(false)), + try expectSerialization( + of: db.create(table: "planets4").column("current", type: .custom(SQLRaw("BOOLEAN")), .default(false)), is: "CREATE TABLE ``planets4`` (``current`` BOOLEAN DEFAULT FAALS)" ) - XCTAssertSerialization( - of: self.db.create(table: "planets5").column("current", type: .custom(SQLRaw("BOOLEAN")), .default(SQLLiteral.boolean(true))), + try expectSerialization( + of: db.create(table: "planets5").column("current", type: .custom(SQLRaw("BOOLEAN")), .default(SQLLiteral.boolean(true))), is: "CREATE TABLE ``planets5`` (``current`` BOOLEAN DEFAULT TROO)" ) } - func testForeignKeyColumnConstraintVariants() { - XCTAssertSerialization( - of: self.db.create(table: "planets1").column("galaxy_id", type: .bigint, .references("galaxies", "id")), + @Test("FOREIGN KEY column constraint variants") + func foreignKeyColumnConstraintVariants() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets1").column("galaxy_id", type: .bigint, .references("galaxies", "id")), is: "CREATE TABLE ``planets1`` (``galaxy_id`` BIGINT REFERENCES ``galaxies`` (``id``))" ) - XCTAssertSerialization( - of: self.db.create(table: "planets2").column("galaxy_id", type: .bigint, .references("galaxies", "id", onDelete: .cascade, onUpdate: .restrict)), + try expectSerialization( + of: db.create(table: "planets2").column("galaxy_id", type: .bigint, .references("galaxies", "id", onDelete: .cascade, onUpdate: .restrict)), is: "CREATE TABLE ``planets2`` (``galaxy_id`` BIGINT REFERENCES ``galaxies`` (``id``) ON DELETE CASCADE ON UPDATE RESTRICT)" ) } - func testTableConstraints() { - XCTAssertSerialization( - of: self.db.create(table: "planets") + @Test("table constraints") + func tableConstraints() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets") .column("id", type: .bigint) .column("name", type: .text) .column("diameter", type: .int) @@ -144,44 +163,56 @@ final class SQLCreateTableTests: XCTestCase { ) } - func testCompositePrimaryKeyTableConstraint() { - XCTAssertSerialization( - of: self.db.create(table: "planets1").column("id1", type: .bigint).column("id2", type: .bigint).primaryKey("id1", "id2"), + @Test("composite PRIMARY KEY table constraint") + func compositePrimaryKeyTableConstraint() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets1").column("id1", type: .bigint).column("id2", type: .bigint).primaryKey("id1", "id2"), is: "CREATE TABLE ``planets1`` (``id1`` BIGINT, ``id2`` BIGINT, PRIMARY KEY (``id1``, ``id2``))" ) } - func testCompositeUniqueTableConstraint() { - XCTAssertSerialization( - of: self.db.create(table: "planets1").column("id1", type: .bigint).column("id2", type: .bigint).unique("id1", "id2"), + @Test("composite UNIQUE table constraint") + func compositeUniqueTableConstraint() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets1").column("id1", type: .bigint).column("id2", type: .bigint).unique("id1", "id2"), is: "CREATE TABLE ``planets1`` (``id1`` BIGINT, ``id2`` BIGINT, UNIQUE (``id1``, ``id2``))" ) } - func testPrimaryKeyTableConstraintVariants() { - XCTAssertSerialization( - of: self.db.create(table: "planets1").column("galaxy_name", type: .text) + @Test("PRIMARY KEY table constraint variants") + func primaryKeyTableConstraintVariants() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets1").column("galaxy_name", type: .text) .column("galaxy_id", type: .bigint) .foreignKey(["galaxy_id", "galaxy_name"], references: "galaxies", ["id", "name"]), is: "CREATE TABLE ``planets1`` (``galaxy_name`` TEXT, ``galaxy_id`` BIGINT, FOREIGN KEY (``galaxy_id``, ``galaxy_name``) REFERENCES ``galaxies`` (``id``, ``name``))" ) - XCTAssertSerialization( - of: self.db.create(table: "planets2") + try expectSerialization( + of: db.create(table: "planets2") .column("galaxy_id", type: .bigint) .foreignKey(["galaxy_id"], references: "galaxies", ["id"]), is: "CREATE TABLE ``planets2`` (``galaxy_id`` BIGINT, FOREIGN KEY (``galaxy_id``) REFERENCES ``galaxies`` (``id``))" ) - XCTAssertSerialization( - of: self.db.create(table: "planets3") + try expectSerialization( + of: db.create(table: "planets3") .column("galaxy_id", type: .bigint) .foreignKey(["galaxy_id"], references: "galaxies", ["id"], onDelete: .restrict, onUpdate: .cascade), is: "CREATE TABLE ``planets3`` (``galaxy_id`` BIGINT, FOREIGN KEY (``galaxy_id``) REFERENCES ``galaxies`` (``id``) ON DELETE RESTRICT ON UPDATE CASCADE)" ) } - - func testCreateTableAsSelectQuery() { - XCTAssertSerialization( - of: self.db.create(table: "normalized_planet_names") + + @Test("CREATE TABLE AS SELECT query") + func createTableAsSelectQuery() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "normalized_planet_names") .column("id", type: .bigint, .primaryKey(autoIncrement: false), .notNull) .column("name", type: .text, .unique, .notNull) .select { $0 @@ -194,26 +225,35 @@ final class SQLCreateTableTests: XCTestCase { is: "CREATE TABLE ``normalized_planet_names`` (``id`` BIGINT PRIMARY KEY NOT NULL, ``name`` TEXT UNIQUE NOT NULL) AS SELECT DISTINCT ``id`` AS ``id``, LOWER(``name``) AS ``name`` FROM ``planets`` WHERE ``galaxy_id`` = &1" ) } - - func testCreateTableWithVariantMethods() { - XCTAssertSerialization( - of: self.db .create(table: "planets") + + @Test("CREATE TABLE with variant methods") + func createTableWithVariantMethods() throws { + let db = TestDatabase() + + try expectSerialization( + of: db .create(table: "planets") .column(definitions: [.init("id", dataType: .bigint)]) .column(SQLIdentifier("id2"), type: SQLDataType.bigint, SQLColumnConstraintAlgorithm.notNull), is: "CREATE TABLE ``planets`` (``id`` BIGINT, ``id2`` BIGINT NOT NULL)" ) } - - func testCreateTemporaryTable() { - XCTAssertSerialization( - of: self.db.create(table: "planets").temporary().column("id", type: .bigint), + + @Test("CREATE TEMPORARY TABLE query") + func createTemporaryTable() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets").temporary().column("id", type: .bigint), is: "CREATE TEMPORARY TABLE ``planets`` (``id`` BIGINT)" ) } - - func testCreateTableWithNamedConstraints() { - XCTAssertSerialization( - of: self.db.create(table: "planets") + + @Test("CREATE TABLE with named constraints") + func createTableWithNamedConstraints() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.create(table: "planets") .column("id", type: .bigint) .primaryKey(["id"], named: "PRIMARY") .unique("id", named: "unique") diff --git a/Tests/SQLKitTests/SQLDialectFeatureTests.swift b/Tests/SQLKitTests/SQLDialectFeatureTests.swift index 23f05b8..1983b54 100644 --- a/Tests/SQLKitTests/SQLDialectFeatureTests.swift +++ b/Tests/SQLKitTests/SQLDialectFeatureTests.swift @@ -1,159 +1,172 @@ import SQLKit -import XCTest +import Testing -final class SQLDialectFeatureTests: XCTestCase { - var db = TestDatabase() - - override class func setUp() { - XCTAssert(isLoggingConfigured) - } - +@Suite("Dialect feature tests") +struct DialectFeatureTests { // MARK: Dialect-Specific Behaviors - func testIfExists() { - self.db._dialect.supportsIfExists = true - XCTAssertSerialization(of: self.db.create(table: "planets").ifNotExists(), is: "CREATE TABLE IF NOT EXISTS ``planets``") - XCTAssertSerialization(of: self.db.drop(table: "planets").ifExists(), is: "DROP TABLE IF EXISTS ``planets``") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx").ifExists(), is: "DROP INDEX IF EXISTS ``planets_name_idx``") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types").ifExists(), is: "DROP TYPE IF EXISTS ``planet_types`` RESTRICT") - - self.db._dialect.supportsIfExists = false - XCTAssertSerialization(of: self.db.create(table: "planets").ifNotExists(), is: "CREATE TABLE ``planets``") - XCTAssertSerialization(of: self.db.drop(table: "planets").ifExists(), is: "DROP TABLE ``planets``") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx").ifExists(), is: "DROP INDEX ``planets_name_idx``") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types").ifExists(), is: "DROP TYPE ``planet_types`` RESTRICT") + @Test("IF EXISTS") + func ifExists() throws { + let db = TestDatabase() + + db._dialect.supportsIfExists = true + try expectSerialization(of: db.create(table: "planets").ifNotExists(), is: "CREATE TABLE IF NOT EXISTS ``planets``") + try expectSerialization(of: db.drop(table: "planets").ifExists(), is: "DROP TABLE IF EXISTS ``planets``") + try expectSerialization(of: db.drop(index: "planets_name_idx").ifExists(), is: "DROP INDEX IF EXISTS ``planets_name_idx``") + try expectSerialization(of: db.drop(enum: "planet_types").ifExists(), is: "DROP TYPE IF EXISTS ``planet_types`` RESTRICT") + + db._dialect.supportsIfExists = false + try expectSerialization(of: db.create(table: "planets").ifNotExists(), is: "CREATE TABLE ``planets``") + try expectSerialization(of: db.drop(table: "planets").ifExists(), is: "DROP TABLE ``planets``") + try expectSerialization(of: db.drop(index: "planets_name_idx").ifExists(), is: "DROP INDEX ``planets_name_idx``") + try expectSerialization(of: db.drop(enum: "planet_types").ifExists(), is: "DROP TYPE ``planet_types`` RESTRICT") } - func testDropBehavior() { - self.db._dialect.supportsDropBehavior = false - XCTAssertSerialization(of: self.db.drop(table: "planets"), is: "DROP TABLE ``planets``") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx"), is: "DROP INDEX ``planets_name_idx``") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types"), is: "DROP TYPE ``planet_types``") - XCTAssertSerialization(of: self.db.drop(table: "planets").behavior(.cascade), is: "DROP TABLE ``planets``") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx").behavior(.cascade), is: "DROP INDEX ``planets_name_idx``") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types").behavior(.cascade), is: "DROP TYPE ``planet_types``") - XCTAssertSerialization(of: self.db.drop(table: "planets").behavior(.restrict), is: "DROP TABLE ``planets``") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx").behavior(.restrict), is: "DROP INDEX ``planets_name_idx``") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types").behavior(.restrict), is: "DROP TYPE ``planet_types``") - XCTAssertSerialization(of: self.db.drop(table: "planets").cascade(), is: "DROP TABLE ``planets``") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx").cascade(), is: "DROP INDEX ``planets_name_idx``") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types").cascade(), is: "DROP TYPE ``planet_types``") - XCTAssertSerialization(of: self.db.drop(table: "planets").restrict(), is: "DROP TABLE ``planets``") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx").restrict(), is: "DROP INDEX ``planets_name_idx``") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types").restrict(), is: "DROP TYPE ``planet_types``") - - self.db._dialect.supportsDropBehavior = true - XCTAssertSerialization(of: self.db.drop(table: "planets"), is: "DROP TABLE ``planets``") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx"), is: "DROP INDEX ``planets_name_idx``") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types"), is: "DROP TYPE ``planet_types`` RESTRICT") - XCTAssertSerialization(of: self.db.drop(table: "planets").behavior(.cascade), is: "DROP TABLE ``planets`` CASCADE") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx").behavior(.cascade), is: "DROP INDEX ``planets_name_idx`` CASCADE") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types").behavior(.cascade), is: "DROP TYPE ``planet_types`` CASCADE") - XCTAssertSerialization(of: self.db.drop(table: "planets").behavior(.restrict), is: "DROP TABLE ``planets`` RESTRICT") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx").behavior(.restrict), is: "DROP INDEX ``planets_name_idx`` RESTRICT") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types").behavior(.restrict), is: "DROP TYPE ``planet_types`` RESTRICT") - XCTAssertSerialization(of: self.db.drop(table: "planets").cascade(), is: "DROP TABLE ``planets`` CASCADE") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx").cascade(), is: "DROP INDEX ``planets_name_idx`` CASCADE") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types").cascade(), is: "DROP TYPE ``planet_types`` CASCADE") - XCTAssertSerialization(of: self.db.drop(table: "planets").restrict(), is: "DROP TABLE ``planets`` RESTRICT") - XCTAssertSerialization(of: self.db.drop(index: "planets_name_idx").restrict(), is: "DROP INDEX ``planets_name_idx`` RESTRICT") - XCTAssertSerialization(of: self.db.drop(enum: "planet_types").restrict(), is: "DROP TYPE ``planet_types`` RESTRICT") + @Test("DROP behavior") + func dropBehavior() throws { + let db = TestDatabase() + + db._dialect.supportsDropBehavior = false + try expectSerialization(of: db.drop(table: "planets"), is: "DROP TABLE ``planets``") + try expectSerialization(of: db.drop(index: "planets_name_idx"), is: "DROP INDEX ``planets_name_idx``") + try expectSerialization(of: db.drop(enum: "planet_types"), is: "DROP TYPE ``planet_types``") + try expectSerialization(of: db.drop(table: "planets").behavior(.cascade), is: "DROP TABLE ``planets``") + try expectSerialization(of: db.drop(index: "planets_name_idx").behavior(.cascade), is: "DROP INDEX ``planets_name_idx``") + try expectSerialization(of: db.drop(enum: "planet_types").behavior(.cascade), is: "DROP TYPE ``planet_types``") + try expectSerialization(of: db.drop(table: "planets").behavior(.restrict), is: "DROP TABLE ``planets``") + try expectSerialization(of: db.drop(index: "planets_name_idx").behavior(.restrict), is: "DROP INDEX ``planets_name_idx``") + try expectSerialization(of: db.drop(enum: "planet_types").behavior(.restrict), is: "DROP TYPE ``planet_types``") + try expectSerialization(of: db.drop(table: "planets").cascade(), is: "DROP TABLE ``planets``") + try expectSerialization(of: db.drop(index: "planets_name_idx").cascade(), is: "DROP INDEX ``planets_name_idx``") + try expectSerialization(of: db.drop(enum: "planet_types").cascade(), is: "DROP TYPE ``planet_types``") + try expectSerialization(of: db.drop(table: "planets").restrict(), is: "DROP TABLE ``planets``") + try expectSerialization(of: db.drop(index: "planets_name_idx").restrict(), is: "DROP INDEX ``planets_name_idx``") + try expectSerialization(of: db.drop(enum: "planet_types").restrict(), is: "DROP TYPE ``planet_types``") + + db._dialect.supportsDropBehavior = true + try expectSerialization(of: db.drop(table: "planets"), is: "DROP TABLE ``planets``") + try expectSerialization(of: db.drop(index: "planets_name_idx"), is: "DROP INDEX ``planets_name_idx``") + try expectSerialization(of: db.drop(enum: "planet_types"), is: "DROP TYPE ``planet_types`` RESTRICT") + try expectSerialization(of: db.drop(table: "planets").behavior(.cascade), is: "DROP TABLE ``planets`` CASCADE") + try expectSerialization(of: db.drop(index: "planets_name_idx").behavior(.cascade), is: "DROP INDEX ``planets_name_idx`` CASCADE") + try expectSerialization(of: db.drop(enum: "planet_types").behavior(.cascade), is: "DROP TYPE ``planet_types`` CASCADE") + try expectSerialization(of: db.drop(table: "planets").behavior(.restrict), is: "DROP TABLE ``planets`` RESTRICT") + try expectSerialization(of: db.drop(index: "planets_name_idx").behavior(.restrict), is: "DROP INDEX ``planets_name_idx`` RESTRICT") + try expectSerialization(of: db.drop(enum: "planet_types").behavior(.restrict), is: "DROP TYPE ``planet_types`` RESTRICT") + try expectSerialization(of: db.drop(table: "planets").cascade(), is: "DROP TABLE ``planets`` CASCADE") + try expectSerialization(of: db.drop(index: "planets_name_idx").cascade(), is: "DROP INDEX ``planets_name_idx`` CASCADE") + try expectSerialization(of: db.drop(enum: "planet_types").cascade(), is: "DROP TYPE ``planet_types`` CASCADE") + try expectSerialization(of: db.drop(table: "planets").restrict(), is: "DROP TABLE ``planets`` RESTRICT") + try expectSerialization(of: db.drop(index: "planets_name_idx").restrict(), is: "DROP INDEX ``planets_name_idx`` RESTRICT") + try expectSerialization(of: db.drop(enum: "planet_types").restrict(), is: "DROP TYPE ``planet_types`` RESTRICT") } - - func testDropTemporary() { - XCTAssertSerialization( - of: self.db.drop(table: "normalized_planet_names").temporary(), + + @Test("DROP TEMPORARY") + func dropTemporary() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.drop(table: "normalized_planet_names").temporary(), is: "DROP TEMPORARY TABLE ``normalized_planet_names``" ) } - - func testOwnerObjectsForDropIndex() { - XCTAssertSerialization( - of: self.db.drop(index: "some_crummy_mysql_index").on("some_darn_mysql_table"), + + @Test("owner objects for DROP INDEX") + func ownerObjectsForDropIndex() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.drop(index: "some_crummy_mysql_index").on("some_darn_mysql_table"), is: "DROP INDEX ``some_crummy_mysql_index`` ON ``some_darn_mysql_table``" ) } - func testAlterTableSyntax() { + @Test("ALTER TABLE syntax") + func alterTableSyntax() throws { + let db = TestDatabase() + // SINGLE - XCTAssertSerialization( - of: self.db.alter(table: "alterable").column("hello", type: .text), + try expectSerialization( + of: db.alter(table: "alterable").column("hello", type: .text), is: "ALTER TABLE ``alterable`` ADD ``hello`` TEXT" ) - XCTAssertSerialization( - of: self.db.alter(table: "alterable").column(SQLIdentifier("hello"), type: SQLDataType.text), + try expectSerialization( + of: db.alter(table: "alterable").column(SQLIdentifier("hello"), type: SQLDataType.text), is: "ALTER TABLE ``alterable`` ADD ``hello`` TEXT" ) - XCTAssertSerialization( - of: self.db.alter(table: "alterable").dropColumn("hello"), + try expectSerialization( + of: db.alter(table: "alterable").dropColumn("hello"), is: "ALTER TABLE ``alterable`` DROP ``hello``" ) - XCTAssertSerialization( - of: self.db.alter(table: "alterable").modifyColumn("hello", type: .text), + try expectSerialization( + of: db.alter(table: "alterable").modifyColumn("hello", type: .text), is: "ALTER TABLE ``alterable`` MOODIFY ``hello`` TEXT" ) - XCTAssertSerialization( - of: self.db.alter(table: "alterable").modifyColumn(SQLIdentifier("hello"), type: SQLDataType.text), + try expectSerialization( + of: db.alter(table: "alterable").modifyColumn(SQLIdentifier("hello"), type: SQLDataType.text), is: "ALTER TABLE ``alterable`` MOODIFY ``hello`` TEXT" ) // BATCH - XCTAssertSerialization( - of: self.db.alter(table: "alterable").column("hello", type: .text).column("there", type: .text), + try expectSerialization( + of: db.alter(table: "alterable").column("hello", type: .text).column("there", type: .text), is: "ALTER TABLE ``alterable`` ADD ``hello`` TEXT , ADD ``there`` TEXT" ) - XCTAssertSerialization( - of: self.db.alter(table: "alterable").dropColumn("hello").dropColumn("there"), + try expectSerialization( + of: db.alter(table: "alterable").dropColumn("hello").dropColumn("there"), is: "ALTER TABLE ``alterable`` DROP ``hello`` , DROP ``there``" ) - XCTAssertSerialization( - of: self.db.alter(table: "alterable").update(column: "hello", type: .text).update(column: "there", type: .text), + try expectSerialization( + of: db.alter(table: "alterable").update(column: "hello", type: .text).update(column: "there", type: .text), is: "ALTER TABLE ``alterable`` MOODIFY ``hello`` TEXT , MOODIFY ``there`` TEXT" ) // MIXED - XCTAssertSerialization( - of: self.db.alter(table: "alterable").column("hello", type: .text).dropColumn("there").update(column: "again", type: .text), + try expectSerialization( + of: db.alter(table: "alterable").column("hello", type: .text).dropColumn("there").update(column: "again", type: .text), is: "ALTER TABLE ``alterable`` ADD ``hello`` TEXT , DROP ``there`` , MOODIFY ``again`` TEXT" ) // Table renaming - XCTAssertSerialization( - of: self.db.alter(table: "alterable").rename(to: "new_alterable"), + try expectSerialization( + of: db.alter(table: "alterable").rename(to: "new_alterable"), is: "ALTER TABLE ``alterable`` RENAME TO ``new_alterable``" ) } - + // MARK: Returning - func testReturning() { - self.db._dialect.supportsReturning = true + @Test("RETURNING") + func returning() throws { + let db = TestDatabase() + + db._dialect.supportsReturning = true - XCTAssertSerialization( - of: self.db.insert(into: "planets").columns("name").values("Jupiter").returning("id", "name"), + try expectSerialization( + of: db.insert(into: "planets").columns("name").values("Jupiter").returning("id", "name"), is: "INSERT INTO ``planets`` (``name``) VALUES (&1) RETURNING ``id``, ``name``" ) - XCTAssertSerialization( - of: self.db.update("planets").set("name", to: "Jupiter").returning(SQLColumn("name", table: "planets")), + try expectSerialization( + of: db.update("planets").set("name", to: "Jupiter").returning(SQLColumn("name", table: "planets")), is: "UPDATE ``planets`` SET ``name`` = &1 RETURNING ``planets``.``name``" ) - XCTAssertSerialization( - of: self.db.delete(from: "planets").returning("*"), + try expectSerialization( + of: db.delete(from: "planets").returning("*"), is: "DELETE FROM ``planets`` RETURNING *" ) - self.db._dialect.supportsReturning = false + db._dialect.supportsReturning = false - XCTAssertSerialization( - of: self.db.insert(into: "planets").columns("name").values("Jupiter").returning("id", "name"), + try expectSerialization( + of: db.insert(into: "planets").columns("name").values("Jupiter").returning("id", "name"), is: "INSERT INTO ``planets`` (``name``) VALUES (&1)" ) - XCTAssertSerialization( - of: self.db.update("planets").set("name", to: "Jupiter").returning(SQLColumn("name", table: "planets")), + try expectSerialization( + of: db.update("planets").set("name", to: "Jupiter").returning(SQLColumn("name", table: "planets")), is: "UPDATE ``planets`` SET ``name`` = &1" ) - XCTAssertSerialization( - of: self.db.delete(from: "planets").returning("*"), + try expectSerialization( + of: db.delete(from: "planets").returning("*"), is: "DELETE FROM ``planets``" ) } diff --git a/Tests/SQLKitTests/SQLExpressionTests.swift b/Tests/SQLKitTests/SQLExpressionTests.swift index 6e51740..f6479bb 100644 --- a/Tests/SQLKitTests/SQLExpressionTests.swift +++ b/Tests/SQLKitTests/SQLExpressionTests.swift @@ -1,205 +1,241 @@ @testable import SQLKit -import XCTest +import Testing -final class SQLExpressionTests: XCTestCase { - var db = TestDatabase() +@Suite("Expression tests") +struct ExpressionTests { + @Test("data types") + func dataTypes() throws { + let db = TestDatabase() - override class func setUp() { - XCTAssert(isLoggingConfigured) + try expectSerialization(of: db.raw("\(SQLDataType.smallint)"), is: "SMALLINT") + try expectSerialization(of: db.raw("\(SQLDataType.int)"), is: "INTEGER") + try expectSerialization(of: db.raw("\(SQLDataType.bigint)"), is: "BIGINT") + try expectSerialization(of: db.raw("\(SQLDataType.real)"), is: "REAL") + try expectSerialization(of: db.raw("\(SQLDataType.text)"), is: "TEXT") + try expectSerialization(of: db.raw("\(SQLDataType.blob)"), is: "BLOB") + try expectSerialization(of: db.raw("\(SQLDataType.timestamp)"), is: "TIMESTAMP") + try expectSerialization(of: db.raw("\(SQLDataType.custom(SQLRaw("STANDARD")))"), is: "CUSTOM") } - - func testDataTypes() { - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.smallint)"), is: "SMALLINT") - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.int)"), is: "INTEGER") - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.bigint)"), is: "BIGINT") - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.real)"), is: "REAL") - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.text)"), is: "TEXT") - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.blob)"), is: "BLOB") - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.timestamp)"), is: "TIMESTAMP") - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.custom(SQLRaw("STANDARD")))"), is: "CUSTOM") - } - - func testDirectionalities() { - XCTAssertSerialization(of: self.db.raw("\(SQLDirection.ascending)"), is: "ASC") - XCTAssertSerialization(of: self.db.raw("\(SQLDirection.descending)"), is: "DESC") - XCTAssertSerialization(of: self.db.raw("\(SQLDirection.null)"), is: "NULL") - XCTAssertSerialization(of: self.db.raw("\(SQLDirection.notNull)"), is: "NOT NULL") + + @Test("directionalities") + func directionalities() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLDirection.ascending)"), is: "ASC") + try expectSerialization(of: db.raw("\(SQLDirection.descending)"), is: "DESC") + try expectSerialization(of: db.raw("\(SQLDirection.null)"), is: "NULL") + try expectSerialization(of: db.raw("\(SQLDirection.notNull)"), is: "NOT NULL") } - - func testDistinctExpr() { - XCTAssertSerialization(of: self.db.raw("\(SQLDistinct(Array()))"), is: "") - XCTAssertSerialization(of: self.db.raw("\(SQLDistinct.all)"), is: "DISTINCT *") - XCTAssertSerialization(of: self.db.raw("\(SQLDistinct("a", "b"))"), is: "DISTINCT ``a``, ``b``") - XCTAssertSerialization(of: self.db.raw("\(SQLDistinct(["a", "b"]))"), is: "DISTINCT ``a``, ``b``") - XCTAssertSerialization(of: self.db.raw("\(SQLDistinct(SQLIdentifier("a"), SQLIdentifier("b")))"), is: "DISTINCT ``a``, ``b``") - XCTAssertSerialization(of: self.db.raw("\(SQLDistinct([SQLIdentifier("a"), SQLIdentifier("b")]))"), is: "DISTINCT ``a``, ``b``") + + @Test("DISTINCT expr") + func distinctExpr() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLDistinct(Array()))"), is: "") + try expectSerialization(of: db.raw("\(SQLDistinct.all)"), is: "DISTINCT *") + try expectSerialization(of: db.raw("\(SQLDistinct("a", "b"))"), is: "DISTINCT ``a``, ``b``") + try expectSerialization(of: db.raw("\(SQLDistinct(["a", "b"]))"), is: "DISTINCT ``a``, ``b``") + try expectSerialization(of: db.raw("\(SQLDistinct(SQLIdentifier("a"), SQLIdentifier("b")))"), is: "DISTINCT ``a``, ``b``") + try expectSerialization(of: db.raw("\(SQLDistinct([SQLIdentifier("a"), SQLIdentifier("b")]))"), is: "DISTINCT ``a``, ``b``") } - - func testForeignKeyActions() { - XCTAssertSerialization(of: self.db.raw("\(SQLForeignKeyAction.noAction)"), is: "NO ACTION") - XCTAssertSerialization(of: self.db.raw("\(SQLForeignKeyAction.restrict)"), is: "RESTRICT") - XCTAssertSerialization(of: self.db.raw("\(SQLForeignKeyAction.cascade)"), is: "CASCADE") - XCTAssertSerialization(of: self.db.raw("\(SQLForeignKeyAction.setNull)"), is: "SET NULL") - XCTAssertSerialization(of: self.db.raw("\(SQLForeignKeyAction.setDefault)"), is: "SET DEFAULT") + + @Test("FORIEGN KEY actions") + func foreignKeyActions() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLForeignKeyAction.noAction)"), is: "NO ACTION") + try expectSerialization(of: db.raw("\(SQLForeignKeyAction.restrict)"), is: "RESTRICT") + try expectSerialization(of: db.raw("\(SQLForeignKeyAction.cascade)"), is: "CASCADE") + try expectSerialization(of: db.raw("\(SQLForeignKeyAction.setNull)"), is: "SET NULL") + try expectSerialization(of: db.raw("\(SQLForeignKeyAction.setDefault)"), is: "SET DEFAULT") } - - func testQualifiedTable() { - XCTAssertSerialization(of: self.db.raw("\(SQLQualifiedTable("a", space: "b"))"), is: "``b``.``a``") - XCTAssertSerialization(of: self.db.raw("\(SQLQualifiedTable(SQLIdentifier("a"), space: SQLIdentifier("b")))"), is: "``b``.``a``") + + @Test("qualified table") + func qualifiedTable() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLQualifiedTable("a", space: "b"))"), is: "``b``.``a``") + try expectSerialization(of: db.raw("\(SQLQualifiedTable(SQLIdentifier("a"), space: SQLIdentifier("b")))"), is: "``b``.``a``") } - - func testAlterColumnDefinitionType() { - self.db._dialect.alterTableSyntax.alterColumnDefinitionTypeKeyword = nil - XCTAssertSerialization(of: self.db.raw("\(SQLAlterColumnDefinitionType(column: .init("a"), dataType: .int))"), is: "``a`` INTEGER") - XCTAssertSerialization(of: self.db.raw("\(SQLAlterColumnDefinitionType(column: SQLRaw("a"), dataType: SQLDataType.int))"), is: "a INTEGER") - - self.db._dialect.alterTableSyntax.alterColumnDefinitionTypeKeyword = SQLRaw("SET TYPE") - XCTAssertSerialization(of: self.db.raw("\(SQLAlterColumnDefinitionType(column: .init("a"), dataType: .int))"), is: "``a`` SET TYPE INTEGER") - XCTAssertSerialization(of: self.db.raw("\(SQLAlterColumnDefinitionType(column: SQLRaw("a"), dataType: SQLDataType.int))"), is: "a SET TYPE INTEGER") + + @Test("alter column definition type") + func alterColumnDefinitionType() throws { + let db = TestDatabase() + + db._dialect.alterTableSyntax.alterColumnDefinitionTypeKeyword = nil + try expectSerialization(of: db.raw("\(SQLAlterColumnDefinitionType(column: .init("a"), dataType: .int))"), is: "``a`` INTEGER") + try expectSerialization(of: db.raw("\(SQLAlterColumnDefinitionType(column: SQLRaw("a"), dataType: SQLDataType.int))"), is: "a INTEGER") + + db._dialect.alterTableSyntax.alterColumnDefinitionTypeKeyword = SQLRaw("SET TYPE") + try expectSerialization(of: db.raw("\(SQLAlterColumnDefinitionType(column: .init("a"), dataType: .int))"), is: "``a`` SET TYPE INTEGER") + try expectSerialization(of: db.raw("\(SQLAlterColumnDefinitionType(column: SQLRaw("a"), dataType: SQLDataType.int))"), is: "a SET TYPE INTEGER") } - - func testColumnAssignment() { - XCTAssertSerialization(of: self.db.raw("\(SQLColumnAssignment(setting: "a", to: "b"))"), is: "``a`` = &1") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnAssignment(setting: "a", to: SQLIdentifier("b")))"), is: "``a`` = ``b``") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnAssignment(setting: SQLIdentifier("a"), to: "b"))"), is: "``a`` = &1") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnAssignment(setting: SQLIdentifier("a"), to: SQLIdentifier("b")))"), is: "``a`` = ``b``") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnAssignment(settingExcludedValueFor: "a"))"), is: "``a`` = EXCLUDED.``a``") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnAssignment(settingExcludedValueFor: SQLIdentifier("a")))"), is: "``a`` = EXCLUDED.``a``") + + @Test("column assignment") + func columnAssignment() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLColumnAssignment(setting: "a", to: "b"))"), is: "``a`` = &1") + try expectSerialization(of: db.raw("\(SQLColumnAssignment(setting: "a", to: SQLIdentifier("b")))"), is: "``a`` = ``b``") + try expectSerialization(of: db.raw("\(SQLColumnAssignment(setting: SQLIdentifier("a"), to: "b"))"), is: "``a`` = &1") + try expectSerialization(of: db.raw("\(SQLColumnAssignment(setting: SQLIdentifier("a"), to: SQLIdentifier("b")))"), is: "``a`` = ``b``") + try expectSerialization(of: db.raw("\(SQLColumnAssignment(settingExcludedValueFor: "a"))"), is: "``a`` = EXCLUDED.``a``") + try expectSerialization(of: db.raw("\(SQLColumnAssignment(settingExcludedValueFor: SQLIdentifier("a")))"), is: "``a`` = EXCLUDED.``a``") } - - func testColumnConstraintAlgorithm() { - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.primaryKey(autoIncrement: false))"), is: "PRIMARY KEY") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.primaryKey(autoIncrement: true))"), is: "PRIMARY KEY AWWTOEINCREMENT") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.primaryKey)"), is: "PRIMARY KEY AWWTOEINCREMENT") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.notNull)"), is: "NOT NULL") + @Test("column constraint algorithm") + func columnConstraintAlgorithm() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.primaryKey(autoIncrement: false))"), is: "PRIMARY KEY") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.primaryKey(autoIncrement: true))"), is: "PRIMARY KEY AWWTOEINCREMENT") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.primaryKey)"), is: "PRIMARY KEY AWWTOEINCREMENT") + + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.notNull)"), is: "NOT NULL") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.unique)"), is: "UNIQUE") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.unique)"), is: "UNIQUE") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.check(SQLRaw("CHECK")))"), is: "CHECK (CHECK)") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.check(SQLRaw("CHECK")))"), is: "CHECK (CHECK)") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.collate(name: "ascii"))"), is: "COLLATE ``ascii``") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.collate(name: SQLIdentifier("ascii")))"), is: "COLLATE ``ascii``") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.collate(name: "ascii"))"), is: "COLLATE ``ascii``") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.collate(name: SQLIdentifier("ascii")))"), is: "COLLATE ``ascii``") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.default("a"))"), is: "DEFAULT 'a'") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.default(1))"), is: "DEFAULT 1") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.default(1.0))"), is: "DEFAULT 1.0") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.default(true))"), is: "DEFAULT TROO") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.default("a"))"), is: "DEFAULT 'a'") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.default(1))"), is: "DEFAULT 1") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.default(1.0))"), is: "DEFAULT 1.0") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.default(true))"), is: "DEFAULT TROO") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.references("a", "b", onDelete: .cascade, onUpdate: .cascade))"), is: "REFERENCES ``a`` (``b``) ON DELETE CASCADE ON UPDATE CASCADE") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.references(SQLIdentifier("a"), SQLIdentifier("b"), onDelete: SQLForeignKeyAction.cascade, onUpdate: SQLForeignKeyAction.cascade))"), is: "REFERENCES ``a`` (``b``) ON DELETE CASCADE ON UPDATE CASCADE") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.foreignKey(references: SQLForeignKey(table: SQLIdentifier("a"), columns: [SQLIdentifier("b")], onDelete: SQLForeignKeyAction.cascade, onUpdate: SQLForeignKeyAction.cascade)))"), is: "REFERENCES ``a`` (``b``) ON DELETE CASCADE ON UPDATE CASCADE") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.references("a", "b", onDelete: .cascade, onUpdate: .cascade))"), is: "REFERENCES ``a`` (``b``) ON DELETE CASCADE ON UPDATE CASCADE") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.references(SQLIdentifier("a"), SQLIdentifier("b"), onDelete: SQLForeignKeyAction.cascade, onUpdate: SQLForeignKeyAction.cascade))"), is: "REFERENCES ``a`` (``b``) ON DELETE CASCADE ON UPDATE CASCADE") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.foreignKey(references: SQLForeignKey(table: SQLIdentifier("a"), columns: [SQLIdentifier("b")], onDelete: SQLForeignKeyAction.cascade, onUpdate: SQLForeignKeyAction.cascade)))"), is: "REFERENCES ``a`` (``b``) ON DELETE CASCADE ON UPDATE CASCADE") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.generated(SQLRaw("value")))"), is: "GENERATED ALWAYS AS (value) STORED") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.generated(SQLRaw("value")))"), is: "GENERATED ALWAYS AS (value) STORED") - XCTAssertSerialization(of: self.db.raw("\(SQLColumnConstraintAlgorithm.custom(SQLRaw("whatever")))"), is: "whatever") + try expectSerialization(of: db.raw("\(SQLColumnConstraintAlgorithm.custom(SQLRaw("whatever")))"), is: "whatever") } - - func testConflictResolutionStrategy() { - self.db._dialect.upsertSyntax = .standard - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction))"), is: "ON CONFLICT (``a``) DO NOTHING") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(targets: ["a"], action: .noAction))"), is: "ON CONFLICT (``a``) DO NOTHING") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: SQLIdentifier("a"), action: .noAction))"), is: "ON CONFLICT (``a``) DO NOTHING") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(targets: [SQLIdentifier("a")], action: .noAction))"), is: "ON CONFLICT (``a``) DO NOTHING") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: self.db) ?? SQLRaw(""))"), is: "") - XCTAssertSerialization( - of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))))"), + + @Test("conflict resolution strategy") + func conflictResolutionStrategy() throws { + let db = TestDatabase() + + db._dialect.upsertSyntax = .standard + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction))"), is: "ON CONFLICT (``a``) DO NOTHING") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(targets: ["a"], action: .noAction))"), is: "ON CONFLICT (``a``) DO NOTHING") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: SQLIdentifier("a"), action: .noAction))"), is: "ON CONFLICT (``a``) DO NOTHING") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(targets: [SQLIdentifier("a")], action: .noAction))"), is: "ON CONFLICT (``a``) DO NOTHING") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: db) ?? SQLRaw(""))"), is: "") + try expectSerialization( + of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))))"), is: "ON CONFLICT (``a``) DO UPDATE SET ``a`` = &1 WHERE ``a`` = ``b``" ) - XCTAssertSerialization( - of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))).queryModifier(for: self.db) ?? SQLRaw(""))"), + try expectSerialization( + of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))).queryModifier(for: db) ?? SQLRaw(""))"), is: "" ) - self.db._dialect.upsertSyntax = .mysqlLike - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction))"), is: "") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(targets: ["a"], action: .noAction))"), is: "") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: SQLIdentifier("a"), action: .noAction))"), is: "") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(targets: [SQLIdentifier("a")], action: .noAction))"), is: "") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: self.db) ?? SQLRaw(""))"), is: "IGNORE") - XCTAssertSerialization( - of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))))"), + db._dialect.upsertSyntax = .mysqlLike + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction))"), is: "") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(targets: ["a"], action: .noAction))"), is: "") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: SQLIdentifier("a"), action: .noAction))"), is: "") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(targets: [SQLIdentifier("a")], action: .noAction))"), is: "") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: db) ?? SQLRaw(""))"), is: "IGNORE") + try expectSerialization( + of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))))"), is: "ON DUPLICATE KEY UPDATE ``a`` = &1" ) - XCTAssertSerialization( - of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))).queryModifier(for: self.db) ?? SQLRaw(""))"), + try expectSerialization( + of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))).queryModifier(for: db) ?? SQLRaw(""))"), is: "" ) - self.db._dialect.upsertSyntax = .unsupported - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction))"), is: "") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(targets: ["a"], action: .noAction))"), is: "") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: SQLIdentifier("a"), action: .noAction))"), is: "") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(targets: [SQLIdentifier("a")], action: .noAction))"), is: "") - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: self.db) ?? SQLRaw(""))"), is: "") - XCTAssertSerialization( - of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))))"), + db._dialect.upsertSyntax = .unsupported + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction))"), is: "") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(targets: ["a"], action: .noAction))"), is: "") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: SQLIdentifier("a"), action: .noAction))"), is: "") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(targets: [SQLIdentifier("a")], action: .noAction))"), is: "") + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: db) ?? SQLRaw(""))"), is: "") + try expectSerialization( + of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))))"), is: "" ) - XCTAssertSerialization( - of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))).queryModifier(for: self.db) ?? SQLRaw(""))"), + try expectSerialization( + of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .update(assignments: [SQLColumnAssignment(setting: "a", to: "b")], predicate: SQLBinaryExpression(SQLIdentifier("a"), .equal, SQLIdentifier("b")))).queryModifier(for: db) ?? SQLRaw(""))"), is: "" ) - - self.db._dialect.upsertSyntax = .mysqlLike - var serializer1 = SQLSerializer(database: self.db) - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: serializer1) ?? SQLRaw(""))"), is: "IGNORE") + + db._dialect.upsertSyntax = .mysqlLike + var serializer1 = SQLSerializer(database: db) + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: serializer1) ?? SQLRaw(""))"), is: "IGNORE") serializer1.statement { - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: $0) ?? SQLRaw(""))"), is: "IGNORE") + #expect((try? expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: $0) ?? SQLRaw(""))"), is: "IGNORE")) != nil) } - self.db._dialect.upsertSyntax = .unsupported - let serializer2 = SQLSerializer(database: self.db) - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: serializer2) ?? SQLRaw(""))"), is: "") + db._dialect.upsertSyntax = .unsupported + let serializer2 = SQLSerializer(database: db) + try expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: serializer2) ?? SQLRaw(""))"), is: "") serializer1.statement { - XCTAssertSerialization(of: self.db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: $0) ?? SQLRaw(""))"), is: "") + #expect((try? expectSerialization(of: db.raw("\(SQLConflictResolutionStrategy(target: "a", action: .noAction).queryModifier(for: $0) ?? SQLRaw(""))"), is: "")) != nil) } } - - func testEnumDataType() { - self.db._dialect.enumSyntax = .inline - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.enum("a", "b"))"), is: "ENUM ('a', 'b')") - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.enum(["a", "b"]))"), is: "ENUM ('a', 'b')") - XCTAssertSerialization(of: self.db.raw("\(SQLDataType.enum([SQLLiteral.string("a"), SQLLiteral.string("b")]))"), is: "ENUM ('a', 'b')") - XCTAssertSerialization(of: self.db.raw("\(SQLEnumDataType(cases: ["a", "b"]))"), is: "ENUM ('a', 'b')") - XCTAssertSerialization(of: self.db.raw("\(SQLEnumDataType(cases: [SQLLiteral.string("a"), SQLLiteral.string("b")]))"), is: "ENUM ('a', 'b')") - self.db._dialect.enumSyntax = .typeName - XCTAssertSerialization(of: self.db.raw("\(SQLEnumDataType(cases: ["a", "b"]))"), is: "TEXT") - XCTAssertSerialization(of: self.db.raw("\(SQLEnumDataType(cases: [SQLLiteral.string("a"), SQLLiteral.string("b")]))"), is: "TEXT") - self.db._dialect.enumSyntax = .unsupported - XCTAssertSerialization(of: self.db.raw("\(SQLEnumDataType(cases: ["a", "b"]))"), is: "TEXT") - XCTAssertSerialization(of: self.db.raw("\(SQLEnumDataType(cases: [SQLLiteral.string("a"), SQLLiteral.string("b")]))"), is: "TEXT") + + @Test("enum data type") + func enumDataType() throws { + let db = TestDatabase() + + db._dialect.enumSyntax = .inline + try expectSerialization(of: db.raw("\(SQLDataType.enum("a", "b"))"), is: "ENUM ('a', 'b')") + try expectSerialization(of: db.raw("\(SQLDataType.enum(["a", "b"]))"), is: "ENUM ('a', 'b')") + try expectSerialization(of: db.raw("\(SQLDataType.enum([SQLLiteral.string("a"), SQLLiteral.string("b")]))"), is: "ENUM ('a', 'b')") + try expectSerialization(of: db.raw("\(SQLEnumDataType(cases: ["a", "b"]))"), is: "ENUM ('a', 'b')") + try expectSerialization(of: db.raw("\(SQLEnumDataType(cases: [SQLLiteral.string("a"), SQLLiteral.string("b")]))"), is: "ENUM ('a', 'b')") + db._dialect.enumSyntax = .typeName + try expectSerialization(of: db.raw("\(SQLEnumDataType(cases: ["a", "b"]))"), is: "TEXT") + try expectSerialization(of: db.raw("\(SQLEnumDataType(cases: [SQLLiteral.string("a"), SQLLiteral.string("b")]))"), is: "TEXT") + db._dialect.enumSyntax = .unsupported + try expectSerialization(of: db.raw("\(SQLEnumDataType(cases: ["a", "b"]))"), is: "TEXT") + try expectSerialization(of: db.raw("\(SQLEnumDataType(cases: [SQLLiteral.string("a"), SQLLiteral.string("b")]))"), is: "TEXT") } - - func testExcludedColumn() { - self.db._dialect.upsertSyntax = .standard - XCTAssertSerialization(of: self.db.raw("\(SQLExcludedColumn("a"))"), is: "EXCLUDED.``a``") - XCTAssertSerialization(of: self.db.raw("\(SQLExcludedColumn(SQLIdentifier("a")))"), is: "EXCLUDED.``a``") - self.db._dialect.upsertSyntax = .mysqlLike - XCTAssertSerialization(of: self.db.raw("\(SQLExcludedColumn("a"))"), is: "VALUES(``a``)") - XCTAssertSerialization(of: self.db.raw("\(SQLExcludedColumn(SQLIdentifier("a")))"), is: "VALUES(``a``)") - self.db._dialect.upsertSyntax = .unsupported - XCTAssertSerialization(of: self.db.raw("\(SQLExcludedColumn("a"))"), is: "") - XCTAssertSerialization(of: self.db.raw("\(SQLExcludedColumn(SQLIdentifier("a")))"), is: "") + + @Test("excluded column") + func excludedColumn() throws { + let db = TestDatabase() + + db._dialect.upsertSyntax = .standard + try expectSerialization(of: db.raw("\(SQLExcludedColumn("a"))"), is: "EXCLUDED.``a``") + try expectSerialization(of: db.raw("\(SQLExcludedColumn(SQLIdentifier("a")))"), is: "EXCLUDED.``a``") + db._dialect.upsertSyntax = .mysqlLike + try expectSerialization(of: db.raw("\(SQLExcludedColumn("a"))"), is: "VALUES(``a``)") + try expectSerialization(of: db.raw("\(SQLExcludedColumn(SQLIdentifier("a")))"), is: "VALUES(``a``)") + db._dialect.upsertSyntax = .unsupported + try expectSerialization(of: db.raw("\(SQLExcludedColumn("a"))"), is: "") + try expectSerialization(of: db.raw("\(SQLExcludedColumn(SQLIdentifier("a")))"), is: "") } - + @available(*, deprecated, message: "Tests deprecated functionality") - func testJoinMethod() { - XCTAssertSerialization(of: self.db.raw("\(SQLJoinMethod.inner)"), is: "INNER") - XCTAssertSerialization(of: self.db.raw("\(SQLJoinMethod.outer)"), is: "OUTER") - XCTAssertSerialization(of: self.db.raw("\(SQLJoinMethod.left)"), is: "LEFT") - XCTAssertSerialization(of: self.db.raw("\(SQLJoinMethod.right)"), is: "RIGHT") + @Test("join method") + func joinMethod() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLJoinMethod.inner)"), is: "INNER") + try expectSerialization(of: db.raw("\(SQLJoinMethod.outer)"), is: "OUTER") + try expectSerialization(of: db.raw("\(SQLJoinMethod.left)"), is: "LEFT") + try expectSerialization(of: db.raw("\(SQLJoinMethod.right)"), is: "RIGHT") } - - func testReturningExpr() { - XCTAssertSerialization(of: self.db.raw("\(SQLReturning(SQLColumn("a")))"), is: "RETURNING ``a``") - XCTAssertSerialization(of: self.db.raw("\(SQLReturning([SQLColumn("a")]))"), is: "RETURNING ``a``") - XCTAssertSerialization(of: self.db.raw("\(SQLReturning([]))"), is: "") + + @Test("RETURNING expr") + func returningExpr() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLReturning(SQLColumn("a")))"), is: "RETURNING ``a``") + try expectSerialization(of: db.raw("\(SQLReturning([SQLColumn("a")]))"), is: "RETURNING ``a``") + try expectSerialization(of: db.raw("\(SQLReturning([]))"), is: "") } - - func testAlterTableQuery() { + + @Test("ALTER TABLE query") + func alterTableQuery() throws { + let db = TestDatabase() var query = SQLAlterTable(name: SQLIdentifier("table")) - + query.renameTo = SQLIdentifier("table2") query.addColumns = [SQLColumnDefinition("a", dataType: .bigint)] query.modifyColumns = [SQLAlterColumnDefinitionType(column: "b", dataType: .blob)] @@ -207,71 +243,88 @@ final class SQLExpressionTests: XCTestCase { query.addTableConstraints = [SQLTableConstraintAlgorithm.unique(columns: [SQLColumn("d")])] query.dropTableConstraints = [SQLIdentifier("e")] - self.db._dialect.alterTableSyntax.allowsBatch = false - self.db._dialect.alterTableSyntax.alterColumnDefinitionClause = nil - XCTAssertSerialization(of: self.db.raw("\(query)"), is: "ALTER TABLE ``table`` RENAME TO ``table2`` ADD ``a`` BIGINT , ADD UNIQUE (``d``) , DROP ``c`` , DROP ``e`` , __INVALID__ ``b`` BLOB") + db._dialect.alterTableSyntax.allowsBatch = false + db._dialect.alterTableSyntax.alterColumnDefinitionClause = nil + try expectSerialization(of: db.raw("\(query)"), is: "ALTER TABLE ``table`` RENAME TO ``table2`` ADD ``a`` BIGINT , ADD UNIQUE (``d``) , DROP ``c`` , DROP ``e`` , __INVALID__ ``b`` BLOB") - self.db._dialect.alterTableSyntax.allowsBatch = true - self.db._dialect.alterTableSyntax.alterColumnDefinitionClause = SQLRaw("MODIFY") - XCTAssertSerialization(of: self.db.raw("\(query)"), is: "ALTER TABLE ``table`` RENAME TO ``table2`` ADD ``a`` BIGINT , ADD UNIQUE (``d``) , DROP ``c`` , DROP ``e`` , MODIFY ``b`` BLOB") + db._dialect.alterTableSyntax.allowsBatch = true + db._dialect.alterTableSyntax.alterColumnDefinitionClause = SQLRaw("MODIFY") + try expectSerialization(of: db.raw("\(query)"), is: "ALTER TABLE ``table`` RENAME TO ``table2`` ADD ``a`` BIGINT , ADD UNIQUE (``d``) , DROP ``c`` , DROP ``e`` , MODIFY ``b`` BLOB") } - - func testCreateIndexQuery() { + + @Test("CREATE INDEX query") + func createIndexQuery() throws { + let db = TestDatabase() var query = SQLCreateIndex(name: SQLIdentifier("index")) - + query.table = SQLIdentifier("table") query.modifier = SQLColumnConstraintAlgorithm.unique query.columns = [SQLIdentifier("a"), SQLIdentifier("b")] query.predicate = SQLBinaryExpression(SQLIdentifier("c"), .equal, SQLIdentifier("d")) - XCTAssertSerialization(of: self.db.raw("\(query)"), is: "CREATE UNIQUE INDEX ``index`` ON ``table`` (``a``, ``b``) WHERE ``c`` = ``d``") + try expectSerialization(of: db.raw("\(query)"), is: "CREATE UNIQUE INDEX ``index`` ON ``table`` (``a``, ``b``) WHERE ``c`` = ``d``") } - - func testBinaryOperators() { - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.equal)"), is: "=") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.notEqual)"), is: "<>") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.greaterThan)"), is: ">") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.lessThan)"), is: "<") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.greaterThanOrEqual)"), is: ">=") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.lessThanOrEqual)"), is: "<=") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.like)"), is: "LIKE") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.notLike)"), is: "NOT LIKE") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.in)"), is: "IN") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.notIn)"), is: "NOT IN") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.and)"), is: "AND") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.or)"), is: "OR") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.multiply)"), is: "*") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.divide)"), is: "/") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.modulo)"), is: "%") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.add)"), is: "+") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.subtract)"), is: "-") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.is)"), is: "IS") - XCTAssertSerialization(of: self.db.raw("\(SQLBinaryOperator.isNot)"), is: "IS NOT") + + @Test("binary operators") + func binaryOperators() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLBinaryOperator.equal)"), is: "=") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.notEqual)"), is: "<>") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.greaterThan)"), is: ">") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.lessThan)"), is: "<") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.greaterThanOrEqual)"), is: ">=") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.lessThanOrEqual)"), is: "<=") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.like)"), is: "LIKE") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.notLike)"), is: "NOT LIKE") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.in)"), is: "IN") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.notIn)"), is: "NOT IN") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.and)"), is: "AND") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.or)"), is: "OR") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.multiply)"), is: "*") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.divide)"), is: "/") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.modulo)"), is: "%") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.add)"), is: "+") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.subtract)"), is: "-") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.is)"), is: "IS") + try expectSerialization(of: db.raw("\(SQLBinaryOperator.isNot)"), is: "IS NOT") } - - func testFunctionInitializers() { - XCTAssertSerialization(of: self.db.raw("\(SQLFunction("test", args: "a", "b"))"), is: "test(``a``, ``b``)") - XCTAssertSerialization(of: self.db.raw("\(SQLFunction("test", args: ["a", "b"]))"), is: "test(``a``, ``b``)") - XCTAssertSerialization(of: self.db.raw("\(SQLFunction("test", args: SQLIdentifier("a"), SQLIdentifier("b")))"), is: "test(``a``, ``b``)") - XCTAssertSerialization(of: self.db.raw("\(SQLFunction("test", args: [SQLIdentifier("a"), SQLIdentifier("b")]))"), is: "test(``a``, ``b``)") + + @Test("function initializers") + func functionInitializers() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLFunction("test", args: "a", "b"))"), is: "test(``a``, ``b``)") + try expectSerialization(of: db.raw("\(SQLFunction("test", args: ["a", "b"]))"), is: "test(``a``, ``b``)") + try expectSerialization(of: db.raw("\(SQLFunction("test", args: SQLIdentifier("a"), SQLIdentifier("b")))"), is: "test(``a``, ``b``)") + try expectSerialization(of: db.raw("\(SQLFunction("test", args: [SQLIdentifier("a"), SQLIdentifier("b")]))"), is: "test(``a``, ``b``)") } - - func testCoalesceFunction() { - XCTAssertSerialization(of: self.db.raw("\(SQLFunction.coalesce(SQLIdentifier("a"), SQLIdentifier("b")))"), is: "COALESCE(``a``, ``b``)") - XCTAssertSerialization(of: self.db.raw("\(SQLFunction.coalesce([SQLIdentifier("a"), SQLIdentifier("b")]))"), is: "COALESCE(``a``, ``b``)") + + @Test("COALESCE function") + func coalesceFunction() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLFunction.coalesce(SQLIdentifier("a"), SQLIdentifier("b")))"), is: "COALESCE(``a``, ``b``)") + try expectSerialization(of: db.raw("\(SQLFunction.coalesce([SQLIdentifier("a"), SQLIdentifier("b")]))"), is: "COALESCE(``a``, ``b``)") } - - func testWeirdQuoting() { - self.db._dialect.identifierQuote = SQLQueryString("_") - self.db._dialect.literalStringQuote = SQLQueryString("~") - XCTAssertSerialization(of: self.db.raw("\(ident: "hello") \(literal: "there")"), is: "_hello_ ~there~") + + @Test("weird quoting") + func weirdQuoting() throws { + let db = TestDatabase() + + db._dialect.identifierQuote = SQLQueryString("_") + db._dialect.literalStringQuote = SQLQueryString("~") + try expectSerialization(of: db.raw("\(ident: "hello") \(literal: "there")"), is: "_hello_ ~there~") } - - func testColumns() { - XCTAssertSerialization(of: self.db.raw("\(SQLColumn("*"))"), is: "*") - XCTAssertSerialization(of: self.db.raw("\(SQLColumn(SQLIdentifier("*")))"), is: "``*``") - XCTAssertSerialization(of: self.db.raw("\(SQLColumn(SQLLiteral.all))"), is: "*") - XCTAssertSerialization(of: self.db.raw("\(SQLColumn("*", table: "foo"))"), is: "``foo``.*") - XCTAssertSerialization(of: self.db.raw("\(SQLColumn(SQLIdentifier("*"), table: SQLIdentifier("foo")))"), is: "``foo``.``*``") - XCTAssertSerialization(of: self.db.raw("\(SQLColumn(SQLLiteral.all, table: SQLIdentifier("foo")))"), is: "``foo``.*") + + @Test("columns") + func columns() throws { + let db = TestDatabase() + + try expectSerialization(of: db.raw("\(SQLColumn("*"))"), is: "*") + try expectSerialization(of: db.raw("\(SQLColumn(SQLIdentifier("*")))"), is: "``*``") + try expectSerialization(of: db.raw("\(SQLColumn(SQLLiteral.all))"), is: "*") + try expectSerialization(of: db.raw("\(SQLColumn("*", table: "foo"))"), is: "``foo``.*") + try expectSerialization(of: db.raw("\(SQLColumn(SQLIdentifier("*"), table: SQLIdentifier("foo")))"), is: "``foo``.``*``") + try expectSerialization(of: db.raw("\(SQLColumn(SQLLiteral.all, table: SQLIdentifier("foo")))"), is: "``foo``.*") } } diff --git a/Tests/SQLKitTests/SQLInsertUpsertTests.swift b/Tests/SQLKitTests/SQLInsertUpsertTests.swift index 6a1f3d6..8125c91 100644 --- a/Tests/SQLKitTests/SQLInsertUpsertTests.swift +++ b/Tests/SQLKitTests/SQLInsertUpsertTests.swift @@ -1,38 +1,44 @@ +#if canImport(FoundationEssentials) +import struct FoundationEssentials.UUID +#else +import struct Foundation.UUID +#endif @testable import SQLKit -import XCTest +import Testing -final class SQLInsertUpsertTests: XCTestCase { - var db = TestDatabase() - - override class func setUp() { - XCTAssert(isLoggingConfigured) - } - +@Suite("UPSERT tests") +struct InsertUpsertTests { // MARK: - Insert - - func testInsert() { - XCTAssertSerialization( - of: self.db.insert(into: "planets") + + @Test("INSERT") + func insert() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.insert(into: "planets") .columns("id", "name") .values(SQLLiteral.default, SQLBind("hello")), is: "INSERT INTO ``planets`` (``id``, ``name``) VALUES (DEFALLT, &1)" ) - - XCTAssertSerialization( - of: self.db.insert(into: "planets") + + try expectSerialization( + of: db.insert(into: "planets") .columns(SQLIdentifier("id"), SQLIdentifier("name")) .values(SQLLiteral.default, SQLBind("hello")), is: "INSERT INTO ``planets`` (``id``, ``name``) VALUES (DEFALLT, &1)" ) - let builder = self.db.insert(into: "planets") + let builder = db.insert(into: "planets") builder.returning = .init(.init("id")) - XCTAssertNotNil(builder.returning) + #expect(builder.returning != nil) } - - func testInsertSelect() { - XCTAssertSerialization( - of: self.db.insert(into: "planets") + + @Test("INSERT ... SELECT") + func insertSelect() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.insert(into: "planets") .columns("id", "name") .select { $0 .columns("id", "name") @@ -42,17 +48,20 @@ final class SQLInsertUpsertTests: XCTestCase { ) } - func testInsertValuesEncodable() throws { + @Test("INSERT ... VALUES (Encodable)") + func insertValuesEncodable() throws { + let db = TestDatabase() + // Test variadic values method - XCTAssertSerialization( - of: self.db.insert(into: "planets") + try expectSerialization( + of: db.insert(into: "planets") .columns(["name", "color"]) .values("Jupiter", "orange"), is: "INSERT INTO ``planets`` (``name``, ``color``) VALUES (&1, &2)" ) - + // Test array values method - XCTAssertSerialization( + try expectSerialization( of: db.insert(into: "planets") .columns(["name", "color"]) .values(["Jupiter", "orange"]), @@ -60,59 +69,62 @@ final class SQLInsertUpsertTests: XCTestCase { ) // Test nested array values method - XCTAssertSerialization( + try expectSerialization( of: db.insert(into: "planets") .columns(["name", "color"]) .values([["Jupiter", "orange"],["Mars", "red"]]), is: "INSERT INTO ``planets`` (``name``, ``color``) VALUES (&1, &2)" ) - + // Test multiple values calls make multiple rows - XCTAssertSerialization( - of: self.db.insert(into: "planets") + try expectSerialization( + of: db.insert(into: "planets") .columns(["name", "color"]) .values(["Jupiter", "orange"]) .values(["Mars", "red"]), is: "INSERT INTO ``planets`` (``name``, ``color``) VALUES (&1, &2), (&3, &4)" ) - + // Test single-value input method - XCTAssertSerialization( - of: self.db.insert(into: "planets") + try expectSerialization( + of: db.insert(into: "planets") .columns(["name"]) .values(["Jupiter"]), is: "INSERT INTO ``planets`` (``name``) VALUES (&1)" ) } - - func testInsertValuesExpression() throws { + + @Test("INSERT ... VALUES (Expression)") + func insertValuesExpression() throws { + let db = TestDatabase() + // Test variadic values method - XCTAssertSerialization( - of: self.db.insert(into: "planets") + try expectSerialization( + of: db.insert(into: "planets") .columns(["name", "color"]) .values(SQLBind("Jupiter"), SQLBind("orange")), is: "INSERT INTO ``planets`` (``name``, ``color``) VALUES (&1, &2)" ) // Test array values method - XCTAssertSerialization( - of: self.db.insert(into: "planets") + try expectSerialization( + of: db.insert(into: "planets") .columns(["name", "color"]) .values([SQLBind("Jupiter"), SQLBind("orange")]), is: "INSERT INTO ``planets`` (``name``, ``color``) VALUES (&1, &2)" ) // Test nested array values method - XCTAssertSerialization( - of: self.db.insert(into: "planets") + try expectSerialization( + of: db.insert(into: "planets") .columns(["name", "color"]) .rows([[SQLBind("Jupiter"), SQLBind("orange")],[SQLBind("Mars"), SQLBind("red")]]), is: "INSERT INTO ``planets`` (``name``, ``color``) VALUES (&1, &2), (&3, &4)" ) // Test multiple values calls make multiple rows - XCTAssertSerialization( - of: self.db.insert(into: "planets") + try expectSerialization( + of: db.insert(into: "planets") .columns(["name", "color"]) .values(["Jupiter", "orange"]) .values([SQLBind("Mars"), SQLBind("red")]), @@ -120,33 +132,35 @@ final class SQLInsertUpsertTests: XCTestCase { ) // Test single-value input method - XCTAssertSerialization( - of: self.db.insert(into: "planets") + try expectSerialization( + of: db.insert(into: "planets") .columns(["name"]) .values([SQLBind("Jupiter")]), is: "INSERT INTO ``planets`` (``name``) VALUES (&1)" ) } - + // MARK: - Upsert - - func testMySQLLikeUpsert() { + + @Test("MySQL-like UPSERT") + func mySQLLikeUpsert() throws { + let db = TestDatabase() let cols = ["id", "serial_number", "star_id", "last_known_status"] let vals = { (s: String) -> [any SQLExpression] in [SQLLiteral.default, SQLBind(UUID()), SQLBind(1), SQLBind(s)] } - + // Test the thoroughly underpowered and inconvenient MySQL syntax db._dialect.upsertSyntax = .mysqlLike - - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates").columns(cols).values(vals("calibration")), + + try expectSerialization( + of: db.insert(into: "jumpgates").columns(cols).values(vals("calibration")), is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (DEFALLT, &1, &2, &3)" ) - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates").columns(cols).values(vals("unicorn dust application")).ignoringConflicts(), + try expectSerialization( + of: db.insert(into: "jumpgates").columns(cols).values(vals("unicorn dust application")).ignoringConflicts(), is: "INSERT IGNORE INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (DEFALLT, &1, &2, &3)" ) - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates") + try expectSerialization( + of: db.insert(into: "jumpgates") .columns(cols).values(vals("planet-size snake oil jar purchasing")) .onConflict() { $0 .set("last_known_status", to: "Hooloovoo engineer refraction") @@ -155,47 +169,49 @@ final class SQLInsertUpsertTests: XCTestCase { is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (DEFALLT, &1, &2, &3) ON DUPLICATE KEY UPDATE ``last_known_status`` = &4, ``serial_number`` = VALUES(``serial_number``)" ) } - - func testStandardUpsert() { + + @Test("standard UPSERT") + func standardUpsert() throws { + let db = TestDatabase() let cols = ["id", "serial_number", "star_id", "last_known_status"] let vals = { (s: String) -> [any SQLExpression] in [SQLLiteral.default, SQLBind(UUID()), SQLBind(1), SQLBind(s)] } // Test the standard SQL syntax db._dialect.upsertSyntax = .standard - - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates").columns(cols).values(vals("calibration")), + + try expectSerialization( + of: db.insert(into: "jumpgates").columns(cols).values(vals("calibration")), is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (DEFALLT, &1, &2, &3)" ) - - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates").columns(cols).values(vals("unicorn dust application")).ignoringConflicts(), + + try expectSerialization( + of: db.insert(into: "jumpgates").columns(cols).values(vals("unicorn dust application")).ignoringConflicts(), is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (DEFALLT, &1, &2, &3) ON CONFLICT DO NOTHING" ) - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates") + try expectSerialization( + of: db.insert(into: "jumpgates") .columns(cols).values(vals("Vorlon pinching")) .ignoringConflicts(with: ["serial_number", "star_id"]), is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (DEFALLT, &1, &2, &3) ON CONFLICT (``serial_number``, ``star_id``) DO NOTHING" ) - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates") + try expectSerialization( + of: db.insert(into: "jumpgates") .columns(cols).values(vals("planet-size snake oil jar purchasing")) .onConflict() { $0 .set("last_known_status", to: "Hooloovoo engineer refraction").set(excludedValueOf: "serial_number") }, is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (DEFALLT, &1, &2, &3) ON CONFLICT DO UPDATE SET ``last_known_status`` = &4, ``serial_number`` = EXCLUDED.``serial_number``" ) - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates") + try expectSerialization( + of: db.insert(into: "jumpgates") .columns(cols).values(vals("slashfic writing")) .onConflict(with: ["serial_number"]) { $0 .set("last_known_status", to: "tachyon antitelephone dialing the").set(excludedValueOf: "star_id") }, is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (DEFALLT, &1, &2, &3) ON CONFLICT (``serial_number``) DO UPDATE SET ``last_known_status`` = &4, ``star_id`` = EXCLUDED.``star_id``" ) - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates") + try expectSerialization( + of: db.insert(into: "jumpgates") .columns(cols).values(vals("protection racket payoff")) .onConflict(with: "id") { $0 .set("last_known_status", to: "insurance fraud planning") @@ -205,28 +221,30 @@ final class SQLInsertUpsertTests: XCTestCase { ) } - func testGenericNonstandardUpsert() { + @Test("generic/nonstandard UPSERT") + func genericNonstandardUpsert() throws { + let db = TestDatabase() let cols = ["id", "serial_number", "star_id", "last_known_status"] let vals = { (s: String) -> [any SQLExpression] in [SQLLiteral.default, SQLBind(UUID()), SQLBind(1), SQLBind(s)] } db._dialect.upsertSyntax = .standard - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates") + try expectSerialization( + of: db.insert(into: "jumpgates") .columns(cols).values(vals("Vorlon pinching")) .ignoringConflicts(withThing: SQLIdentifier("serial_number")), is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (DEFALLT, &1, &2, &3) ON CONFLICT ON CONSTRAINT ``serial_number`` DO NOTHING" ) - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates") + try expectSerialization( + of: db.insert(into: "jumpgates") .columns(cols).values(vals("slashfic writing")) .onConflict(withThing: SQLIdentifier("serial_number")) { $0 .set("last_known_status", to: "tachyon antitelephone dialing the").set(excludedValueOf: "star_id") }, is: "INSERT INTO ``jumpgates`` (``id``, ``serial_number``, ``star_id``, ``last_known_status``) VALUES (DEFALLT, &1, &2, &3) ON CONFLICT ON CONSTRAINT ``serial_number`` DO UPDATE SET ``last_known_status`` = &4, ``star_id`` = EXCLUDED.``star_id``" ) - XCTAssertSerialization( - of: self.db.insert(into: "jumpgates") + try expectSerialization( + of: db.insert(into: "jumpgates") .columns(cols).values(vals("protection racket payoff")) .onConflict(withThing: SQLIdentifier("id")) { $0 .set("last_known_status", to: "insurance fraud planning") diff --git a/Tests/SQLKitTests/SQLQueryEncoderTests.swift b/Tests/SQLKitTests/SQLQueryEncoderTests.swift index 16d4daf..310d252 100644 --- a/Tests/SQLKitTests/SQLQueryEncoderTests.swift +++ b/Tests/SQLKitTests/SQLQueryEncoderTests.swift @@ -1,8 +1,15 @@ +#if canImport(FoundationEssentials) +import struct FoundationEssentials.Date +#else +import struct Foundation.Date +#endif @testable @_spi(CodableUtilities) import SQLKit -import XCTest +import Testing -final class SQLQueryEncoderTests: XCTestCase { - func testQueryEncoderBasicConfigurations() { +@Suite("Query encoder tests") +struct QueryEncoderTests { + @Test("SQLQueryEncoder basic configurations") + func queryEncoderBasicConfigurations() throws { let model1 = BasicEncModel( boolValue: true, optBoolValue: nil, stringValue: "hello", optStringValue: "olleh", doubleValue: 1.0, optDoubleValue: nil, floatValue: 1.0, optFloatValue: 0.1, @@ -23,92 +30,93 @@ final class SQLQueryEncoderTests: XCTestCase { ) // Model 1 with key strategies - XCTAssertEncoding( + try expectEncoding( model1, using: SQLQueryEncoder(keyEncodingStrategy: .useDefaultKeys), outputs: model1.plainColumns(nulls: false), model1.valueExpressions(nulls: false) ) - XCTAssertEncoding( + try expectEncoding( model1, using: SQLQueryEncoder(keyEncodingStrategy: .convertToSnakeCase), outputs: model1.snakeColumns(nulls: false), model1.valueExpressions(nulls: false) ) - XCTAssertEncoding( + try expectEncoding( model1, using: SQLQueryEncoder(keyEncodingStrategy: .custom({ superCase($0) })), outputs: model1.supercaseColumns(nulls: false), model1.valueExpressions(nulls: false) ) - + // Model 1 with key and nil strategies - XCTAssertEncoding( + try expectEncoding( model1, using: SQLQueryEncoder(keyEncodingStrategy: .useDefaultKeys, nilEncodingStrategy: .asNil), outputs: model1.plainColumns(nulls: true), model1.valueExpressions(nulls: true) ) - XCTAssertEncoding( + try expectEncoding( model1, using: SQLQueryEncoder(keyEncodingStrategy: .convertToSnakeCase, nilEncodingStrategy: .asNil), outputs: model1.snakeColumns(nulls: true), model1.valueExpressions(nulls: true) ) - XCTAssertEncoding( + try expectEncoding( model1, using: SQLQueryEncoder(keyEncodingStrategy: .custom({ superCase($0) }), nilEncodingStrategy: .asNil), outputs: model1.supercaseColumns(nulls: true), model1.valueExpressions(nulls: true) ) // Model 1 with prefix and key strategies - XCTAssertEncoding( + try expectEncoding( model1, using: SQLQueryEncoder(prefix: "p_", keyEncodingStrategy: .useDefaultKeys), outputs: model1.plainColumns(nulls: false).map { "p_\($0)" }, model1.valueExpressions(nulls: false) ) - XCTAssertEncoding( + try expectEncoding( model1, using: SQLQueryEncoder(prefix: "p_", keyEncodingStrategy: .convertToSnakeCase), outputs: model1.snakeColumns(nulls: false).map { "p_\($0)" }, model1.valueExpressions(nulls: false) ) - XCTAssertEncoding( + try expectEncoding( model1, using: SQLQueryEncoder(prefix: "p_", keyEncodingStrategy: .custom({ superCase($0) })), outputs: model1.supercaseColumns(nulls: false).map { "p_\($0)" }, model1.valueExpressions(nulls: false) ) // Model 2 with key strategies - XCTAssertEncoding( + try expectEncoding( model2, using: SQLQueryEncoder(keyEncodingStrategy: .useDefaultKeys), outputs: model2.plainColumns(nulls: false), model2.valueExpressions(nulls: false) ) - XCTAssertEncoding( + try expectEncoding( model2, using: SQLQueryEncoder(keyEncodingStrategy: .convertToSnakeCase), outputs: model2.snakeColumns(nulls: false), model2.valueExpressions(nulls: false) ) - XCTAssertEncoding( + try expectEncoding( model2, using: SQLQueryEncoder(keyEncodingStrategy: .custom({ superCase($0) })), outputs: model2.supercaseColumns(nulls: false), model2.valueExpressions(nulls: false) ) // Model 2 with key and nil strategies - XCTAssertEncoding( + try expectEncoding( model2, using: SQLQueryEncoder(keyEncodingStrategy: .useDefaultKeys, nilEncodingStrategy: .asNil), outputs: model2.plainColumns(nulls: true), model2.valueExpressions(nulls: true) ) - XCTAssertEncoding( + try expectEncoding( model2, using: SQLQueryEncoder(keyEncodingStrategy: .convertToSnakeCase, nilEncodingStrategy: .asNil), outputs: model2.snakeColumns(nulls: true), model2.valueExpressions(nulls: true) ) - XCTAssertEncoding( + try expectEncoding( model2, using: SQLQueryEncoder(keyEncodingStrategy: .custom({ superCase($0) }), nilEncodingStrategy: .asNil), outputs: model2.supercaseColumns(nulls: true), model2.valueExpressions(nulls: true) ) // Model 2 with prefix and key strategies - XCTAssertEncoding( + try expectEncoding( model2, using: SQLQueryEncoder(prefix: "p_", keyEncodingStrategy: .useDefaultKeys), outputs: model2.plainColumns(nulls: false).map { "p_\($0)" }, model2.valueExpressions(nulls: false) ) - XCTAssertEncoding( + try expectEncoding( model2, using: SQLQueryEncoder(prefix: "p_", keyEncodingStrategy: .convertToSnakeCase), outputs: model2.snakeColumns(nulls: false).map { "p_\($0)" }, model2.valueExpressions(nulls: false) ) - XCTAssertEncoding( + try expectEncoding( model2, using: SQLQueryEncoder(prefix: "p_", keyEncodingStrategy: .custom({ superCase($0) })), outputs: model2.supercaseColumns(nulls: false).map { "p_\($0)" }, model2.valueExpressions(nulls: false) ) } - - func testEncodeTopLevelOptional() { + + @Test("encode top-level optional") + func encodeTopLevelOptional() throws { let model1: BasicEncModel? = .some(BasicEncModel( boolValue: true, optBoolValue: nil, stringValue: "hello", optStringValue: "olleh", doubleValue: 1.0, optDoubleValue: nil, floatValue: 1.0, optFloatValue: 0.1, @@ -119,58 +127,60 @@ final class SQLQueryEncoderTests: XCTestCase { intValue: 13, optIntValue: nil, uintValue: 14, optUintValue: 15 )) let model2: BasicEncModel? = nil - - XCTAssertEncoding(model1, using: SQLQueryEncoder(), outputs: model1?.plainColumns(nulls: false) ?? [], model1?.valueExpressions(nulls: false) ?? []) - XCTAssertThrowsError(try SQLQueryEncoder().encode(model2)) { - XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") - } + + try expectEncoding(model1, using: SQLQueryEncoder(), outputs: model1?.plainColumns(nulls: false) ?? [], model1?.valueExpressions(nulls: false) ?? []) + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(model2) } } - - func testEncodeUnkeyedValues() { - XCTAssertThrowsError(try SQLQueryEncoder().encode([true])) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } + + @Test("encode unkeyed values") + func encodeUnkeyedValues() { + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode([true]) } } - - func testEncodeTopLevelValues() { - XCTAssertThrowsError(try SQLQueryEncoder().encode(true)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode("hello")) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1.0)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1.0 as Float)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1 as Int8)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1 as Int16)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1 as Int32)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1 as Int64)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1 as UInt8)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1 as UInt16)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1 as UInt32)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1 as UInt64)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1 as Int)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(1 as UInt)) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } + + @Test("encode top-level values") + func encodeTopLevelValues() { + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(true) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode("hello") } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1.0) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1.0 as Float) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1 as Int8) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1 as Int16) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1 as Int32) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1 as Int64) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1 as UInt8) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1 as UInt16) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1 as UInt32) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1 as UInt64) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1 as Int) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(1 as UInt) } } - - func testEncodeNestedKeyedValues() { - XCTAssertNoThrow(try SQLQueryEncoder().encode(TestEncodableIfPresent(foo: .init()))) - XCTAssertNoThrow(try SQLQueryEncoder().encode(TestEncodableIfPresent(foo: nil))) - XCTAssertNoThrow(try SQLQueryEncoder(nilEncodingStrategy: .asNil).encode(TestEncodableIfPresent(foo: nil))) - XCTAssertNoThrow(try SQLQueryEncoder().encode(["a": ["b": "c"]])) - XCTAssertNoThrow(try SQLQueryEncoder().encode(["a": ["b", "c"]])) - XCTAssertThrowsError(try SQLQueryEncoder().encode(TestEncNestedKeyedContainers(foo: (1, 1)))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(TestEncNestedUnkeyedContainer(foo: true))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLQueryEncoder().encode(TestKeylessSuperEncoder(foo: true))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertNoThrow(try SQLQueryEncoder().encode(TestEncNestedSingleValueContainer(foo: (1, 1)))) - XCTAssertNoThrow(try SQLQueryEncoder().encode(TestEncNestedSingleValueContainer(foo: nil))) - XCTAssertNoThrow(try SQLQueryEncoder(nilEncodingStrategy: .asNil).encode(TestEncNestedSingleValueContainer(foo: nil))) - XCTAssertThrowsError(try SQLQueryEncoder().encode(TestEncEnum.foo(bar: true))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } + + @Test("encode nested keyed values") + func encodeNestedKeyedValues() { + #expect(throws: Never.self) { try SQLQueryEncoder().encode(TestEncodableIfPresent(foo: .init())) } + #expect(throws: Never.self) { try SQLQueryEncoder().encode(TestEncodableIfPresent(foo: nil)) } + #expect(throws: Never.self) { try SQLQueryEncoder(nilEncodingStrategy: .asNil).encode(TestEncodableIfPresent(foo: nil)) } + #expect(throws: Never.self) { try SQLQueryEncoder().encode(["a": ["b": "c"]]) } + #expect(throws: Never.self) { try SQLQueryEncoder().encode(["a": ["b", "c"]]) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(TestEncNestedKeyedContainers(foo: (1, 1))) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(TestEncNestedUnkeyedContainer(foo: true)) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(TestKeylessSuperEncoder(foo: true)) } + #expect(throws: Never.self) { try SQLQueryEncoder().encode(TestEncNestedSingleValueContainer(foo: (1, 1))) } + #expect(throws: Never.self) { try SQLQueryEncoder().encode(TestEncNestedSingleValueContainer(foo: nil)) } + #expect(throws: Never.self) { try SQLQueryEncoder(nilEncodingStrategy: .asNil).encode(TestEncNestedSingleValueContainer(foo: nil)) } + #expect(throws: SQLCodingError.self) { try SQLQueryEncoder().encode(TestEncEnum.foo(bar: true)) } } - - func testEncodeOfDirectlyCodableSQLExpression() { - XCTAssertEncoding(TestEncExpr(value: .foo), using: .init(), outputs: ["value"], [TestEncExpr.Enm.foo]) + + @Test("encode of directly Codable SQLExpression") + func encodeOfDirectlyCodableSQLExpression() throws { + try expectEncoding(TestEncExpr(value: .foo), using: .init(), outputs: ["value"], [TestEncExpr.Enm.foo]) } } struct TestEncExpr: Codable { enum Enm: Codable, SQLExpression, Equatable { case foo - + func serialize(to serializer: inout SQLSerializer) { serializer.write("FOO") } @@ -190,9 +200,9 @@ struct TestEncodableIfPresent: Encodable { struct TestKeylessSuperEncoder: Encodable { let foo: Bool - + func encode(to encoder: any Encoder) throws { - XCTAssertNil(encoder.userInfo[.init(rawValue: "a")!]) // for completeness of code coverage + #expect(encoder.userInfo[.init(rawValue: "a")!] == nil) // for completeness of code coverage var container = encoder.container(keyedBy: SomeCodingKey.self) let superEncoder = container.superEncoder() var subcontainer = superEncoder.singleValueContainer() @@ -202,7 +212,7 @@ struct TestKeylessSuperEncoder: Encodable { struct TestEncNestedUnkeyedContainer: Encodable { let foo: Bool - + func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: SomeCodingKey.self) var subcontainer = container.nestedUnkeyedContainer(forKey: .init(stringValue: "foo")) @@ -212,12 +222,12 @@ struct TestEncNestedUnkeyedContainer: Encodable { struct TestEncNestedKeyedContainers: Encodable { let foo: (Int, Int) - + func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: SomeCodingKey.self) let superEncoder = container.superEncoder(forKey: .init(stringValue: "foo")) var subcontainer = superEncoder.container(keyedBy: SomeCodingKey.self) - + try subcontainer.encode(self.foo.0, forKey: .init(stringValue: "_0")) try subcontainer.encode(self.foo.1, forKey: .init(stringValue: "_1")) } @@ -225,12 +235,12 @@ struct TestEncNestedKeyedContainers: Encodable { struct TestEncNestedSingleValueContainer: Encodable { let foo: (Int, Int)? - + func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: SomeCodingKey.self) let superEncoder = container.superEncoder(forKey: .init(stringValue: "foo")) var subcontainer = superEncoder.singleValueContainer() - + if let foo = self.foo { try subcontainer.encode(["_0": foo.0, "_1": foo.1]) } else { @@ -247,7 +257,7 @@ struct BasicEncModel: Codable { var uint8Value: UInt8, optUint8Value: UInt8?, uint16Value: UInt16, optUint16Value: UInt16? var uint32Value: UInt32, optUint32Value: UInt32?, uint64Value: UInt64, optUint64Value: UInt64? var intValue: Int, optIntValue: Int?, uintValue: UInt, optUintValue: UInt? - + func plainColumns(nulls: Bool) -> [String] { [ "boolValue", nulls || self.optBoolValue != nil ? "optBoolValue" : nil, "stringValue", nulls || self.optStringValue != nil ? "optStringValue" : nil, "doubleValue", nulls || self.optDoubleValue != nil ? "optDoubleValue" : nil, "floatValue", nulls || self.optFloatValue != nil ? "optFloatValue" : nil, @@ -267,7 +277,7 @@ struct BasicEncModel: Codable { "uint32_value", nulls || self.optUint32Value != nil ? "opt_uint32_value" : nil, "uint64_value", nulls || self.optUint64Value != nil ? "opt_uint64_value" : nil, "int_value", nulls || self.optIntValue != nil ? "opt_int_value" : nil, "uint_value", nulls || self.optUintValue != nil ? "opt_uint_value" : nil, ].compactMap { $0 } } - + func supercaseColumns(nulls: Bool) -> [String] { [ "BoolValue", nulls || self.optBoolValue != nil ? "OptBoolValue" : nil, "StringValue", nulls || self.optStringValue != nil ? "OptStringValue" : nil, "DoubleValue", nulls || self.optDoubleValue != nil ? "OptDoubleValue" : nil, "FloatValue", nulls || self.optFloatValue != nil ? "OptFloatValue" : nil, @@ -277,7 +287,7 @@ struct BasicEncModel: Codable { "Uint32Value", nulls || self.optUint32Value != nil ? "OptUint32Value" : nil, "Uint64Value", nulls || self.optUint64Value != nil ? "OptUint64Value" : nil, "IntValue", nulls || self.optIntValue != nil ? "OptIntValue" : nil, "UintValue", nulls || self.optUintValue != nil ? "OptUintValue" : nil, ].compactMap { $0 } } - + func valueExpressions(nulls: Bool) -> [any SQLExpression] { [ SQLBind(self.boolValue), (self.optBoolValue.map { SQLBind($0) } ?? SQLLiteral.null) as any SQLExpression, SQLBind(self.stringValue), (self.optStringValue.map { SQLBind($0) } ?? SQLLiteral.null) as any SQLExpression, diff --git a/Tests/SQLKitTests/SQLQueryStringTests.swift b/Tests/SQLKitTests/SQLQueryStringTests.swift index 84bdcce..8e882e3 100644 --- a/Tests/SQLKitTests/SQLQueryStringTests.swift +++ b/Tests/SQLKitTests/SQLQueryStringTests.swift @@ -1,51 +1,61 @@ import SQLKit -import XCTest +import Testing -final class SQLQueryStringTests: XCTestCase { - var db = TestDatabase() +@Suite("SQLQueryString tests") +struct QueryStringTests { + @Test("with literal string") + func withLiteralString() throws { + let db = TestDatabase() - override class func setUp() { - XCTAssert(isLoggingConfigured) + try expectSerialization(of: db.raw("TEST"), is: "TEST") } - - func testWithLiteralString() { - XCTAssertSerialization(of: self.db.raw("TEST"), is: "TEST") - } - - func testRawCustomStringConvertible() { + + @Test("raw CustomStringConvertible") + func rawCustomStringConvertible() throws { + let db = TestDatabase() let field = "name" - XCTAssertSerialization(of: self.db.raw("SELECT \(unsafeRaw: field) FROM users"), is: "SELECT name FROM users") + try expectSerialization(of: db.raw("SELECT \(unsafeRaw: field) FROM users"), is: "SELECT name FROM users") } - func testRawQueryStringInterpolation() { + @Test("raw query string interpolation") + func rawQueryStringInterpolation() { + let db = TestDatabase() let (table, planet) = ("planets", "Earth") - let output = XCTAssertNoThrowWithResult(self.db.raw("SELECT * FROM \(ident: table) WHERE name = \(bind: planet)").advancedSerialize()) - - XCTAssertEqual(output?.sql, "SELECT * FROM ``planets`` WHERE name = &1") - XCTAssertEqual(output?.binds.first as? String, planet) + let output = db.raw("SELECT * FROM \(ident: table) WHERE name = \(bind: planet)").advancedSerialize() + + #expect(output.sql == "SELECT * FROM ``planets`` WHERE name = &1") + #expect(output.binds.first as? String == planet) } - - func testRawQueryStringWithNonliteral() throws { + + @Test("raw query string with nonliteral") + func rawQueryStringWithNonliteral() throws { + let db = TestDatabase() let (table, planet) = ("planets", "Earth") - XCTAssertSerialization( - of: self.db.raw(.init("SELECT * FROM \(table) WHERE name = \(planet)")), + try expectSerialization( + of: db.raw(.init("SELECT * FROM \(table) WHERE name = \(planet)")), is: "SELECT * FROM planets WHERE name = Earth" ) - XCTAssertSerialization( - of: self.db.raw(.init(String("|||SELECT * FROM staticTable WHERE name = uselessUnboundValue|||".dropFirst(3).dropLast(3)))), + try expectSerialization( + of: db.raw(.init(String("|||SELECT * FROM staticTable WHERE name = uselessUnboundValue|||".dropFirst(3).dropLast(3)))), is: "SELECT * FROM staticTable WHERE name = uselessUnboundValue" ) } - func testMakeQueryStringWithoutRawBuilder() { + @Test("make query string without raw builder") + func makeQueryStringWithoutRawBuilder() throws { + let db = TestDatabase() let queryString = SQLQueryString("query with \(ident: "identifier") and stuff") - XCTAssertSerialization(of: self.db.raw(queryString), is: "query with ``identifier`` and stuff") + + try expectSerialization(of: db.raw(queryString), is: "query with ``identifier`` and stuff") } - - func testAllQueryStringInterpolationTypes() { - XCTAssertSerialization( - of: self.db.raw(""" + + @Test("all query string interpolation types") + func allQueryStringInterpolationTypes() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.raw(""" Query string embeds: \(unsafeRaw: "plain string embed") \(bind: "single bind embed") @@ -76,10 +86,13 @@ final class SQLQueryStringTests: XCTestCase { """ ) } - - func testAppendingQueryStringByOperatorPlus() { - XCTAssertSerialization( - of: self.db.raw( + + @Test("appending query string by operator+") + func appendingQueryStringByOperatorPlus() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.raw( "INSERT INTO \(ident: "anything") " as SQLQueryString + "(\(idents: ["col1", "col2", "col3"], joinedBy: ",")) " as SQLQueryString + "VALUES (\(binds: [1, 2, 3]))" as SQLQueryString @@ -87,23 +100,28 @@ final class SQLQueryStringTests: XCTestCase { is: "INSERT INTO ``anything`` (``col1``,``col2``,``col3``) VALUES (&1, &2, &3)" ) } - - func testAppendingQueryStringByOperatorPlusEquals() { + + @Test("appending query string by operator+=") + func appendingQueryStringByOperatorPlusEquals() throws { + let db = TestDatabase() var query = "INSERT INTO \(ident: "anything") " as SQLQueryString query += "(\(idents: ["col1", "col2", "col3"], joinedBy: ",")) " as SQLQueryString query += "VALUES (\(binds: [1, 2, 3]))" as SQLQueryString - - XCTAssertSerialization(of: self.db.raw(query), is: "INSERT INTO ``anything`` (``col1``,``col2``,``col3``) VALUES (&1, &2, &3)") + + try expectSerialization(of: db.raw(query), is: "INSERT INTO ``anything`` (``col1``,``col2``,``col3``) VALUES (&1, &2, &3)") } - - func testQueryStringArrayJoin() { - XCTAssertSerialization( - of: self.db.raw( + + @Test("query string array join") + func queryStringArrayJoin() throws { + let db = TestDatabase() + + try expectSerialization( + of: db.raw( "INSERT INTO \(ident: "anything") " as SQLQueryString + ((0..<5).map { "\(literal: "\($0)")" as SQLQueryString }).joined(separator: "..") ), is: "INSERT INTO ``anything`` '0'..'1'..'2'..'3'..'4'" ) - XCTAssertSerialization(of: self.db.raw(Array().joined()), is: "") + try expectSerialization(of: db.raw(Array().joined()), is: "") } } diff --git a/Tests/SQLKitTests/SQLRowDecoderTests.swift b/Tests/SQLKitTests/SQLRowDecoderTests.swift index df1fdc9..ba472c3 100644 --- a/Tests/SQLKitTests/SQLRowDecoderTests.swift +++ b/Tests/SQLKitTests/SQLRowDecoderTests.swift @@ -1,9 +1,16 @@ +#if canImport(FoundationEssentials) +import struct FoundationEssentials.Date +#else +import struct Foundation.Date +#endif import OrderedCollections @testable @_spi(CodableUtilities) import SQLKit -import XCTest +import Testing -final class SQLRowDecoderTests: XCTestCase { - func testRowDecoderBasicConfigurations() { +@Suite("SQLRowDecoder tests") +struct RowDecoderTests { + @Test("row decoder basic configurations") + func rowDecoderBasicConfigurations() throws { func row1(nulls: Bool, xform: Bool?, prefix: String = "") -> TestRow { let raw: [String: (any Codable & Sendable)?] = [ "boolValue": true, "optBoolValue": Bool?.none, "stringValue": "hello", "optStringValue": "olleh", @@ -59,119 +66,112 @@ final class SQLRowDecoderTests: XCTestCase { ) // Model 1 with key strategies - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: false, xform: nil), using: SQLRowDecoder(keyDecodingStrategy: .useDefaultKeys), outputs: model1) - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: true, xform: nil), using: SQLRowDecoder(keyDecodingStrategy: .useDefaultKeys), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: false, xform: nil), using: SQLRowDecoder(keyDecodingStrategy: .useDefaultKeys), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: true, xform: nil), using: SQLRowDecoder(keyDecodingStrategy: .useDefaultKeys), outputs: model1) - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: false, xform: false), using: SQLRowDecoder(keyDecodingStrategy: .convertFromSnakeCase), outputs: model1) - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: true, xform: false), using: SQLRowDecoder(keyDecodingStrategy: .convertFromSnakeCase), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: false, xform: false), using: SQLRowDecoder(keyDecodingStrategy: .convertFromSnakeCase), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: true, xform: false), using: SQLRowDecoder(keyDecodingStrategy: .convertFromSnakeCase), outputs: model1) + + try expectDecoding(BasicDecModel.self, from: row1(nulls: false, xform: true), using: SQLRowDecoder(keyDecodingStrategy: .custom({ superCase($0) })), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: true, xform: true), using: SQLRowDecoder(keyDecodingStrategy: .custom({ superCase($0) })), outputs: model1) - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: false, xform: true), using: SQLRowDecoder(keyDecodingStrategy: .custom({ superCase($0) })), outputs: model1) - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: true, xform: true), using: SQLRowDecoder(keyDecodingStrategy: .custom({ superCase($0) })), outputs: model1) - // Model 1 with prefix and key strategies - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: false, xform: nil, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .useDefaultKeys), outputs: model1) - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: true, xform: nil, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .useDefaultKeys), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: false, xform: nil, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .useDefaultKeys), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: true, xform: nil, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .useDefaultKeys), outputs: model1) - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: false, xform: false, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .convertFromSnakeCase), outputs: model1) - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: true, xform: false, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .convertFromSnakeCase), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: false, xform: false, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .convertFromSnakeCase), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: true, xform: false, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .convertFromSnakeCase), outputs: model1) - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: false, xform: true, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .custom({ superCase($0) })), outputs: model1) - XCTAssertDecoding(BasicDecModel.self, from: row1(nulls: true, xform: true, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .custom({ superCase($0) })), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: false, xform: true, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .custom({ superCase($0) })), outputs: model1) + try expectDecoding(BasicDecModel.self, from: row1(nulls: true, xform: true, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .custom({ superCase($0) })), outputs: model1) // Model 2 with key strategies - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: false, xform: nil), using: SQLRowDecoder(keyDecodingStrategy: .useDefaultKeys), outputs: model2) - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: true, xform: nil), using: SQLRowDecoder(keyDecodingStrategy: .useDefaultKeys), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: false, xform: nil), using: SQLRowDecoder(keyDecodingStrategy: .useDefaultKeys), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: true, xform: nil), using: SQLRowDecoder(keyDecodingStrategy: .useDefaultKeys), outputs: model2) + + try expectDecoding(BasicDecModel.self, from: row2(nulls: false, xform: false), using: SQLRowDecoder(keyDecodingStrategy: .convertFromSnakeCase), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: true, xform: false), using: SQLRowDecoder(keyDecodingStrategy: .convertFromSnakeCase), outputs: model2) - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: false, xform: false), using: SQLRowDecoder(keyDecodingStrategy: .convertFromSnakeCase), outputs: model2) - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: true, xform: false), using: SQLRowDecoder(keyDecodingStrategy: .convertFromSnakeCase), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: false, xform: true), using: SQLRowDecoder(keyDecodingStrategy: .custom({ superCase($0) })), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: true, xform: true), using: SQLRowDecoder(keyDecodingStrategy: .custom({ superCase($0) })), outputs: model2) - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: false, xform: true), using: SQLRowDecoder(keyDecodingStrategy: .custom({ superCase($0) })), outputs: model2) - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: true, xform: true), using: SQLRowDecoder(keyDecodingStrategy: .custom({ superCase($0) })), outputs: model2) - // Model 2 with prefix and key strategies - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: false, xform: nil, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .useDefaultKeys), outputs: model2) - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: true, xform: nil, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .useDefaultKeys), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: false, xform: nil, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .useDefaultKeys), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: true, xform: nil, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .useDefaultKeys), outputs: model2) - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: false, xform: false, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .convertFromSnakeCase), outputs: model2) - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: true, xform: false, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .convertFromSnakeCase), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: false, xform: false, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .convertFromSnakeCase), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: true, xform: false, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .convertFromSnakeCase), outputs: model2) - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: false, xform: true, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .custom({ superCase($0) })), outputs: model2) - XCTAssertDecoding(BasicDecModel.self, from: row2(nulls: true, xform: true, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .custom({ superCase($0) })), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: false, xform: true, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .custom({ superCase($0) })), outputs: model2) + try expectDecoding(BasicDecModel.self, from: row2(nulls: true, xform: true, prefix: "p_"), using: SQLRowDecoder(prefix: "p_", keyDecodingStrategy: .custom({ superCase($0) })), outputs: model2) } - - func testDecodeUnkeyedValues() { - XCTAssertThrowsError(try SQLRowDecoder().decode(Array.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } + + @Test("decode unkeyed values") + func decodeUnkeyedValues() { + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(Array.self, from: TestRow(data: [:])) } } - - func testDecodeTopLevelValues() { - XCTAssertThrowsError(try SQLRowDecoder().decode(Bool.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(String.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(Double.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(Float.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(Int8.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(Int16.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(Int32.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(Int64.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(UInt8.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(UInt16.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(UInt32.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(UInt64.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(Int.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(UInt.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } + + @Test("decode top-level values") + func decodeTopLevelValues() { + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(Bool.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(String.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(Double.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(Float.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(Int8.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(Int16.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(Int32.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(Int64.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(UInt8.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(UInt16.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(UInt32.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(UInt64.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(Int.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(UInt.self, from: TestRow(data: [:])) } } - - func testDecodeNestedKeyedValues() { - XCTAssertNoThrow(try SQLRowDecoder().decode(TestDecodableIfPresent.self, from: TestRow(data: ["foo": Date()]))) - XCTAssertNoThrow(try SQLRowDecoder().decode(TestDecodableIfPresent.self, from: TestRow(data: ["foo": Date?.none]))) - XCTAssertNoThrow(try SQLRowDecoder().decode(Dictionary>.self, from: TestRow(data: ["a": ["b": "c"]]))) - XCTAssertNoThrow(try SQLRowDecoder(keyDecodingStrategy: .convertFromSnakeCase).decode(Dictionary>.self, from: TestRow(data: ["a": ["b": "c"]]))) - XCTAssertNoThrow(try SQLRowDecoder(keyDecodingStrategy: .custom({ superCase($0) })).decode(Dictionary>.self, from: TestRow(data: ["A": ["b": "c"]]))) - XCTAssertNoThrow(try SQLRowDecoder().decode(Dictionary>.self, from: TestRow(data: ["a": ["b", "c"]]))) - XCTAssertThrowsError(try SQLRowDecoder().decode(TestDecNestedKeyedContainers.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(TestDecNestedUnkeyedContainer.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertThrowsError(try SQLRowDecoder().decode(TestKeylessSuperDecoder.self, from: TestRow(data: [:]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } - XCTAssertNoThrow(try SQLRowDecoder().decode(TestDecNestedSingleValueContainer.self, from: TestRow(data: ["foo": ["_0": 1, "_1": 1]]))) - XCTAssertNoThrow(try SQLRowDecoder().decode(TestDecNestedSingleValueContainer.self, from: TestRow(data: ["foo": Dictionary?.none]))) - XCTAssertThrowsError(try SQLRowDecoder().decode(TestDecEnum.self, from: TestRow(data: ["foo": Dictionary()]))) { XCTAssert($0 is SQLCodingError, "Expected SQLCodingError, got \(String(reflecting: $0))") } + + @Test("decode nested keyed values") + func decodeNestedKeyedValues() { + #expect(throws: Never.self) { try SQLRowDecoder().decode(TestDecodableIfPresent.self, from: TestRow(data: ["foo": Date()])) } + #expect(throws: Never.self) { try SQLRowDecoder().decode(TestDecodableIfPresent.self, from: TestRow(data: ["foo": Date?.none])) } + #expect(throws: Never.self) { try SQLRowDecoder().decode(Dictionary>.self, from: TestRow(data: ["a": ["b": "c"]])) } + #expect(throws: Never.self) { try SQLRowDecoder(keyDecodingStrategy: .convertFromSnakeCase).decode(Dictionary>.self, from: TestRow(data: ["a": ["b": "c"]])) } + #expect(throws: Never.self) { try SQLRowDecoder(keyDecodingStrategy: .custom({ superCase($0) })).decode(Dictionary>.self, from: TestRow(data: ["A": ["b": "c"]])) } + #expect(throws: Never.self) { try SQLRowDecoder().decode(Dictionary>.self, from: TestRow(data: ["a": ["b", "c"]])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(TestDecNestedKeyedContainers.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(TestDecNestedUnkeyedContainer.self, from: TestRow(data: [:])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(TestKeylessSuperDecoder.self, from: TestRow(data: [:])) } + #expect(throws: Never.self) { try SQLRowDecoder().decode(TestDecNestedSingleValueContainer.self, from: TestRow(data: ["foo": ["_0": 1, "_1": 1]])) } + #expect(throws: Never.self) { try SQLRowDecoder().decode(TestDecNestedSingleValueContainer.self, from: TestRow(data: ["foo": Dictionary?.none])) } + #expect(throws: SQLCodingError.self) { try SQLRowDecoder().decode(TestDecEnum.self, from: TestRow(data: ["foo": Dictionary()])) } } - - func testDecoderMiscErrorHandling() { + + @Test("decoder misc. error handling") + func decoderMiscErrorHandling() { struct ErroringRow: SQLRow { let allColumns: [String] func contains(column: String) -> Bool { column == "foo" } func decodeNil(column: String) throws -> Bool { throw DecodingError.valueNotFound(Optional.self, .init(codingPath: [], debugDescription: "")) } func decode(column: String, as: D.Type) throws -> D { throw DecodingError.valueNotFound(Optional.self, .init(codingPath: [], debugDescription: "")) } } - XCTAssertThrowsError(try SQLRowDecoder().decode(TestDecodableIfPresent.self, from: ErroringRow(allColumns: ["foo"]))) { - guard case .valueNotFound(_, let context) = $0 as? DecodingError else { - return XCTFail("Expected DecodingError.valueNotFound(), got \(String(reflecting: $0))") - } - XCTAssertEqual(["foo"], context.codingPath.map(\.stringValue)) - } - XCTAssertThrowsError(try SQLRowDecoder().decode(TestDecNestedSingleValueContainer.self, from: ErroringRow(allColumns: ["foo"]))) { - guard case .valueNotFound(_, let context) = $0 as? DecodingError else { - return XCTFail("Expected DecodingError.valueNotFound(), got \(String(reflecting: $0))") - } - XCTAssertEqual(["foo", "foo"], context.codingPath.map(\.stringValue)) - } - XCTAssertThrowsError(try SQLRowDecoder().decode(TestDecNestedSingleValueContainer?.self, from: ErroringRow(allColumns: ["foo"]))) { - guard case .valueNotFound(_, let context) = $0 as? DecodingError else { - return XCTFail("Expected DecodingError.valueNotFound(), got \(String(reflecting: $0))") - } - XCTAssertEqual(["foo", "foo"], context.codingPath.map(\.stringValue)) - } - XCTAssertThrowsError(try SQLRowDecoder().decode([String: String].self, from: ErroringRow(allColumns: ["foo"]))) { - guard case .valueNotFound(_, let context) = $0 as? DecodingError else { - return XCTFail("Expected DecodingError.valueNotFound(), got \(String(reflecting: $0))") - } - XCTAssertEqual([SomeCodingKey(stringValue: "foo")].map(\.stringValue), context.codingPath.map(\.stringValue)) - } - XCTAssertThrowsError(try SQLRowDecoder().decode([String: String].self, from: ErroringRow(allColumns: ["b"]))) { - guard case .keyNotFound(_, let context) = $0 as? DecodingError else { - return XCTFail("Expected DecodingError.keyNotFound(), got \(String(reflecting: $0))") - } - XCTAssertEqual(Array().map(\.stringValue), context.codingPath.map(\.stringValue)) - } + let error1 = #expect(throws: DecodingError.self) { try SQLRowDecoder().decode(TestDecodableIfPresent.self, from: ErroringRow(allColumns: ["foo"])) } + guard case .valueNotFound(_, let context) = error1 else { Issue.record("Expected .valueNotFound, got \(String(reflecting: error1))"); return } + #expect(["foo"] == context.codingPath.map(\.stringValue)) + + let error2 = #expect(throws: DecodingError.self) { try SQLRowDecoder().decode(TestDecNestedSingleValueContainer.self, from: ErroringRow(allColumns: ["foo"])) } + guard case .valueNotFound(_, let context) = error2 else { Issue.record("Expected .valueNotFound, got \(String(reflecting: error2))"); return } + #expect(["foo", "foo"] == context.codingPath.map(\.stringValue)) + + let error3 = #expect(throws: DecodingError.self) { try SQLRowDecoder().decode(TestDecNestedSingleValueContainer?.self, from: ErroringRow(allColumns: ["foo"])) } + guard case .valueNotFound(_, let context) = error3 else { Issue.record("Expected .valueNotFound, got \(String(reflecting: error3))"); return } + #expect(["foo", "foo"] == context.codingPath.map(\.stringValue)) + + let error4 = #expect(throws: DecodingError.self) { try SQLRowDecoder().decode([String: String].self, from: ErroringRow(allColumns: ["foo"])) } + guard case .valueNotFound(_, let context) = error4 else { Issue.record("Expected .valueNotFound, got \(String(reflecting: error4))"); return } + #expect([SomeCodingKey(stringValue: "foo")].map(\.stringValue) == context.codingPath.map(\.stringValue)) + + let error5 = #expect(throws: DecodingError.self) { try SQLRowDecoder().decode([String: String].self, from: ErroringRow(allColumns: ["b"])) } + guard case .keyNotFound(_, let context) = error5 else { Issue.record("Expected .keyNotFound, got \(String(reflecting: error5))"); return } + #expect(Array().map(\.stringValue) == context.codingPath.map(\.stringValue)) } } @@ -186,9 +186,9 @@ struct TestDecodableIfPresent: Decodable { struct TestKeylessSuperDecoder: Decodable { let foo: Bool - + init(from decoder: any Decoder) throws { - XCTAssertNil(decoder.userInfo[.init(rawValue: "a")!]) // for completeness of code coverage + #expect(decoder.userInfo[.init(rawValue: "a")!] == nil) // for completeness of code coverage let container = try decoder.container(keyedBy: SomeCodingKey.self) let superDecoder = try container.superDecoder() let subcontainer = try superDecoder.singleValueContainer() @@ -198,7 +198,7 @@ struct TestKeylessSuperDecoder: Decodable { struct TestDecNestedUnkeyedContainer: Decodable { let foo: Bool - + init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: SomeCodingKey.self) var subcontainer = try container.nestedUnkeyedContainer(forKey: .init(stringValue: "foo")) @@ -208,12 +208,12 @@ struct TestDecNestedUnkeyedContainer: Decodable { struct TestDecNestedKeyedContainers: Decodable { let foo: (Int, Int) - + init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: SomeCodingKey.self) let superDecoder = try container.superDecoder(forKey: .init(stringValue: "foo")) let subcontainer = try superDecoder.container(keyedBy: SomeCodingKey.self) - + self.foo = ( try subcontainer.decode(Int.self, forKey: .init(stringValue: "_0")), try subcontainer.decode(Int.self, forKey: .init(stringValue: "_1")) @@ -223,12 +223,12 @@ struct TestDecNestedKeyedContainers: Decodable { struct TestDecNestedSingleValueContainer: Decodable { let foo: (Int, Int)? - + init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: SomeCodingKey.self) let superDecoder = try container.superDecoder(forKey: .init(stringValue: "foo")) let subcontainer = try superDecoder.singleValueContainer() - + if subcontainer.decodeNil() { self.foo = nil } else { diff --git a/Tests/SQLKitTests/SQLUnionTests.swift b/Tests/SQLKitTests/SQLUnionTests.swift index af0b094..88110b3 100644 --- a/Tests/SQLKitTests/SQLUnionTests.swift +++ b/Tests/SQLKitTests/SQLUnionTests.swift @@ -1,134 +1,144 @@ import SQLKit -import XCTest +import Testing -final class SQLUnionTests: XCTestCase { - var db = TestDatabase() - - override class func setUp() { - XCTAssert(isLoggingConfigured) - } - +@Suite("UNION tests") +struct UnionTests { // MARK: Top-level unions - func testUnion_UNION() { + @Test("UNION") + func union_UNION() throws { + let db = TestDatabase() + // Check that queries are explicitly malformed without the feature flags - self.db._dialect.unionFeatures = [] + db._dialect.unionFeatures = [] - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").union(distinct: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").union(distinct: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` SELECT ``id`` FROM ``t2``" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").union(all: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").union(all: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` SELECT ``id`` FROM ``t2``" ) // Test that queries are correctly formed with the feature flags - self.db._dialect.unionFeatures.formUnion([.union, .unionAll]) + db._dialect.unionFeatures.formUnion([.union, .unionAll]) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").union(distinct: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").union(distinct: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` UNION SELECT ``id`` FROM ``t2``" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").union(all: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").union(all: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` UNION ALL SELECT ``id`` FROM ``t2``" ) // Test that the explicit distinct flag is respected - self.db._dialect.unionFeatures.insert(.explicitDistinct) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").union(distinct: { $0.column("id").from("t2") }), + db._dialect.unionFeatures.insert(.explicitDistinct) + try expectSerialization( + of: db.select().column("id").from("t1").union(distinct: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` UNION DISTINCT SELECT ``id`` FROM ``t2``" ) } - - func testUnion_INTERSECT() { + + @Test("INTERSECT") + func union_INTERSECT() throws { + let db = TestDatabase() + // Check that queries are explicitly malformed without the feature flags - self.db._dialect.unionFeatures = [] + db._dialect.unionFeatures = [] - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").intersect(distinct: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").intersect(distinct: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` SELECT ``id`` FROM ``t2``" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").intersect(all: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").intersect(all: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` SELECT ``id`` FROM ``t2``" ) // Test that queries are correctly formed with the feature flags - self.db._dialect.unionFeatures.formUnion([.intersect, .intersectAll]) + db._dialect.unionFeatures.formUnion([.intersect, .intersectAll]) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").intersect(distinct: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").intersect(distinct: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` INTERSECT SELECT ``id`` FROM ``t2``" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").intersect(all: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").intersect(all: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` INTERSECT ALL SELECT ``id`` FROM ``t2``" ) // Test that the explicit distinct flag is respected - self.db._dialect.unionFeatures.insert(.explicitDistinct) + db._dialect.unionFeatures.insert(.explicitDistinct) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").intersect(distinct: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").intersect(distinct: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` INTERSECT DISTINCT SELECT ``id`` FROM ``t2``" ) } - - func testUnion_EXCEPT() { + + @Test("EXCEPT") + func union_EXCEPT() throws { + let db = TestDatabase() + // Check that queries are explicitly malformed without the feature flags - self.db._dialect.unionFeatures = [] + db._dialect.unionFeatures = [] - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").except(distinct: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").except(distinct: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` SELECT ``id`` FROM ``t2``" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").except(all: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").except(all: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` SELECT ``id`` FROM ``t2``" ) // Test that queries are correctly formed with the feature flags - self.db._dialect.unionFeatures.formUnion([.except, .exceptAll]) + db._dialect.unionFeatures.formUnion([.except, .exceptAll]) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").except(distinct: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").except(distinct: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` EXCEPT SELECT ``id`` FROM ``t2``" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").except(all: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").except(all: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` EXCEPT ALL SELECT ``id`` FROM ``t2``" ) - + // Test that the explicit distinct flag is respected - self.db._dialect.unionFeatures.insert(.explicitDistinct) + db._dialect.unionFeatures.insert(.explicitDistinct) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").except(distinct: { $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").except(distinct: { $0.column("id").from("t2") }), is: "SELECT ``id`` FROM ``t1`` EXCEPT DISTINCT SELECT ``id`` FROM ``t2``" ) } - - func testUnionWithParenthesizedSubqueriesFlag() { + + @Test("union with parenthesized subqueries flag") + func unionWithParenthesizedSubqueriesFlag() throws { + let db = TestDatabase() + // Test that the parenthesized subqueries flag does as expected, including for multiple unions - self.db._dialect.unionFeatures = [.union, .unionAll, .parenthesizedSubqueries] - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").union(distinct: { $0.column("id").from("t2") }), + db._dialect.unionFeatures = [.union, .unionAll, .parenthesizedSubqueries] + try expectSerialization( + of: db.select().column("id").from("t1").union(distinct: { $0.column("id").from("t2") }), is: "(SELECT ``id`` FROM ``t1``) UNION (SELECT ``id`` FROM ``t2``)" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").union(distinct: { $0.column("id").from("t2") }).union(distinct: { $0.column("id").from("t3") }), + try expectSerialization( + of: db.select().column("id").from("t1").union(distinct: { $0.column("id").from("t2") }).union(distinct: { $0.column("id").from("t3") }), is: "(SELECT ``id`` FROM ``t1``) UNION (SELECT ``id`` FROM ``t2``) UNION (SELECT ``id`` FROM ``t3``)" ) } - - func testUnionChaining() { + + @Test("union chaining") + func unionChaining() throws { + let db = TestDatabase() + // Test that chaining and mixing multiple union types works - self.db._dialect.unionFeatures = [.union, .unionAll, .intersect, .intersectAll, .except, .exceptAll, .explicitDistinct, .parenthesizedSubqueries] - XCTAssertSerialization( - of: self.db.select().column("id").from("t1") + db._dialect.unionFeatures = [.union, .unionAll, .intersect, .intersectAll, .except, .exceptAll, .explicitDistinct, .parenthesizedSubqueries] + try expectSerialization( + of: db.select().column("id").from("t1") .union(distinct: { $0.column("id").from("t2") }) .union(all: { $0.column("id").from("t3") }) .union( { $0.column("id").from("t23") }) @@ -141,81 +151,96 @@ final class SQLUnionTests: XCTestCase { is: "(SELECT ``id`` FROM ``t1``) UNION DISTINCT (SELECT ``id`` FROM ``t2``) UNION ALL (SELECT ``id`` FROM ``t3``) UNION DISTINCT (SELECT ``id`` FROM ``t23``) INTERSECT DISTINCT (SELECT ``id`` FROM ``t4``) INTERSECT ALL (SELECT ``id`` FROM ``t5``) INTERSECT DISTINCT (SELECT ``id`` FROM ``t45``) EXCEPT DISTINCT (SELECT ``id`` FROM ``t6``) EXCEPT ALL (SELECT ``id`` FROM ``t7``) EXCEPT DISTINCT (SELECT ``id`` FROM ``t67``)" ) } - - func testOneQueryUnion() { + + @Test("one-query union") + func oneQueryUnion() throws { + let db = TestDatabase() + // Test that having a single entry in the union just executes that entry - XCTAssertSerialization( - of: self.db.union { $0.column("id").from("t1") }, + try expectSerialization( + of: db.union { $0.column("id").from("t1") }, is: "SELECT ``id`` FROM ``t1``" ) } - - func testUnionSubtypesFromSelect() { - self.db._dialect.unionFeatures = [.union, .unionAll, .intersect, .intersectAll, .except, .exceptAll, .parenthesizedSubqueries] - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").union({ $0.column("id").from("t2") }), + + @Test("union subtypes from SELECT") + func unionSubtypesFromSelect() throws { + let db = TestDatabase() + + db._dialect.unionFeatures = [.union, .unionAll, .intersect, .intersectAll, .except, .exceptAll, .parenthesizedSubqueries] + try expectSerialization( + of: db.select().column("id").from("t1").union({ $0.column("id").from("t2") }), is: "(SELECT ``id`` FROM ``t1``) UNION (SELECT ``id`` FROM ``t2``)" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").intersect({ $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").intersect({ $0.column("id").from("t2") }), is: "(SELECT ``id`` FROM ``t1``) INTERSECT (SELECT ``id`` FROM ``t2``)" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").except({ $0.column("id").from("t2") }), + try expectSerialization( + of: db.select().column("id").from("t1").except({ $0.column("id").from("t2") }), is: "(SELECT ``id`` FROM ``t1``) EXCEPT (SELECT ``id`` FROM ``t2``)" ) } - - func testUnionOverallModifiers() { + + @Test("union overall modifiers") + func unionOverallModifiers() throws { + let db = TestDatabase() + // Test LIMIT and OFFSET - self.db._dialect.unionFeatures = [.union, .unionAll, .intersect, .intersectAll, .except, .exceptAll, .parenthesizedSubqueries] - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").union({ $0.column("id").from("t2") }).limit(3).offset(5), + db._dialect.unionFeatures = [.union, .unionAll, .intersect, .intersectAll, .except, .exceptAll, .parenthesizedSubqueries] + try expectSerialization( + of: db.select().column("id").from("t1").union({ $0.column("id").from("t2") }).limit(3).offset(5), is: "(SELECT ``id`` FROM ``t1``) UNION (SELECT ``id`` FROM ``t2``) LIMIT 3 OFFSET 5" ) - + // Cover the property getters - let builder = self.db.union({ $0.where(SQLLiteral.boolean(true)) }).limit(1).offset(2) - XCTAssertEqual(builder.limit, 1) - XCTAssertEqual(builder.offset, 2) - + let builder = db.union({ $0.where(SQLLiteral.boolean(true)) }).limit(1).offset(2) + #expect(builder.limit == 1) + #expect(builder.offset == 2) + // Test multiple ORDER BY statements - XCTAssertSerialization( - of: self.db.select().column("*").from("t1").union({ $0.column("*").from("t2") }).orderBy("id").orderBy("name", .descending), + try expectSerialization( + of: db.select().column("*").from("t1").union({ $0.column("*").from("t2") }).orderBy("id").orderBy("name", .descending), is: "(SELECT * FROM ``t1``) UNION (SELECT * FROM ``t2``) ORDER BY ``id`` ASC, ``name`` DESC" ) } - - func testUnionAddMethod() { - var query = SQLUnion(initialQuery: self.db.select().columns("*").select) - query.add(self.db.select().columns("*").select, all: true) - query.add(self.db.select().columns("*").select, all: false) - - self.db._dialect.unionFeatures = [] - XCTAssertSerialization(of: self.db.raw("\(query)"), is: "SELECT * SELECT * SELECT *") - - self.db._dialect.unionFeatures = [.union, .unionAll] - XCTAssertSerialization(of: self.db.raw("\(query)"), is: "SELECT * UNION ALL SELECT * UNION SELECT *") + + @Test("union add method") + func unionAddMethod() throws { + let db = TestDatabase() + var query = SQLUnion(initialQuery: db.select().columns("*").select) + + query.add(db.select().columns("*").select, all: true) + query.add(db.select().columns("*").select, all: false) + + db._dialect.unionFeatures = [] + try expectSerialization(of: db.raw("\(query)"), is: "SELECT * SELECT * SELECT *") + + db._dialect.unionFeatures = [.union, .unionAll] + try expectSerialization(of: db.raw("\(query)"), is: "SELECT * UNION ALL SELECT * UNION SELECT *") } // MARK: Subquery unions - func testUnionSubquery_UNION() { + @Test("UNION in subquery") + func unionSubquery_UNION() throws { + let db = TestDatabase() + // Check that queries are explicitly malformed without the feature flags - self.db._dialect.unionFeatures = [] + db._dialect.unionFeatures = [] - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .union(distinct: { $0.column("id").from("t3") }) .finish() ), is: "SELECT ``id`` FROM ``t1`` WHERE ``foo`` NOT IN (SELECT ``id`` FROM ``t2`` SELECT ``id`` FROM ``t3``)" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .union(all: { $0.column("id").from("t3") }) .finish() @@ -224,18 +249,18 @@ final class SQLUnionTests: XCTestCase { ) // Test that queries are correctly formed with the feature flags - self.db._dialect.unionFeatures.formUnion([.union, .unionAll]) + db._dialect.unionFeatures.formUnion([.union, .unionAll]) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .union(distinct: { $0.column("id").from("t3") }) .finish() ), is: "SELECT ``id`` FROM ``t1`` WHERE ``foo`` NOT IN (SELECT ``id`` FROM ``t2`` UNION SELECT ``id`` FROM ``t3``)" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .union(all: { $0.column("id").from("t3") }) .finish() @@ -244,9 +269,9 @@ final class SQLUnionTests: XCTestCase { ) // Test that the explicit distinct flag is respected - self.db._dialect.unionFeatures.insert(.explicitDistinct) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + db._dialect.unionFeatures.insert(.explicitDistinct) + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .union(distinct: { $0.column("id").from("t3") }) .finish() @@ -254,21 +279,24 @@ final class SQLUnionTests: XCTestCase { is: "SELECT ``id`` FROM ``t1`` WHERE ``foo`` NOT IN (SELECT ``id`` FROM ``t2`` UNION DISTINCT SELECT ``id`` FROM ``t3``)" ) } - - func testUnionSubquery_INTERSECT() { + + @Test("INTERSECT in subquery") + func unionSubquery_INTERSECT() throws { + let db = TestDatabase() + // Check that queries are explicitly malformed without the feature flags - self.db._dialect.unionFeatures = [] + db._dialect.unionFeatures = [] - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .intersect(distinct: { $0.column("id").from("t3") }) .finish() ), is: "SELECT ``id`` FROM ``t1`` WHERE ``foo`` NOT IN (SELECT ``id`` FROM ``t2`` SELECT ``id`` FROM ``t3``)" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .intersect(all: { $0.column("id").from("t3") }) .finish() @@ -277,18 +305,18 @@ final class SQLUnionTests: XCTestCase { ) // Test that queries are correctly formed with the feature flags - self.db._dialect.unionFeatures.formUnion([.intersect, .intersectAll]) + db._dialect.unionFeatures.formUnion([.intersect, .intersectAll]) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .intersect(distinct: { $0.column("id").from("t3") }) .finish() ), is: "SELECT ``id`` FROM ``t1`` WHERE ``foo`` NOT IN (SELECT ``id`` FROM ``t2`` INTERSECT SELECT ``id`` FROM ``t3``)" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .intersect(all: { $0.column("id").from("t3") }) .finish() @@ -297,10 +325,10 @@ final class SQLUnionTests: XCTestCase { ) // Test that the explicit distinct flag is respected - self.db._dialect.unionFeatures.insert(.explicitDistinct) + db._dialect.unionFeatures.insert(.explicitDistinct) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .intersect(distinct: { $0.column("id").from("t3") }) .finish() @@ -308,21 +336,24 @@ final class SQLUnionTests: XCTestCase { is: "SELECT ``id`` FROM ``t1`` WHERE ``foo`` NOT IN (SELECT ``id`` FROM ``t2`` INTERSECT DISTINCT SELECT ``id`` FROM ``t3``)" ) } - - func testUnionSubquery_EXCEPT() { + + @Test("EXCEPT in subquery") + func unionSubquery_EXCEPT() throws { + let db = TestDatabase() + // Check that queries are explicitly malformed without the feature flags - self.db._dialect.unionFeatures = [] + db._dialect.unionFeatures = [] - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .except(distinct: { $0.column("id").from("t3") }) .finish() ), is: "SELECT ``id`` FROM ``t1`` WHERE ``foo`` NOT IN (SELECT ``id`` FROM ``t2`` SELECT ``id`` FROM ``t3``)" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .except(all: { $0.column("id").from("t3") }) .finish() @@ -331,30 +362,30 @@ final class SQLUnionTests: XCTestCase { ) // Test that queries are correctly formed with the feature flags - self.db._dialect.unionFeatures.formUnion([.except, .exceptAll]) + db._dialect.unionFeatures.formUnion([.except, .exceptAll]) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .except(distinct: { $0.column("id").from("t3") }) .finish() ), is: "SELECT ``id`` FROM ``t1`` WHERE ``foo`` NOT IN (SELECT ``id`` FROM ``t2`` EXCEPT SELECT ``id`` FROM ``t3``)" ) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .except(all: { $0.column("id").from("t3") }) .finish() ), is: "SELECT ``id`` FROM ``t1`` WHERE ``foo`` NOT IN (SELECT ``id`` FROM ``t2`` EXCEPT ALL SELECT ``id`` FROM ``t3``)" ) - + // Test that the explicit distinct flag is respected - self.db._dialect.unionFeatures.insert(.explicitDistinct) + db._dialect.unionFeatures.insert(.explicitDistinct) - XCTAssertSerialization( - of: self.db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery + try expectSerialization( + of: db.select().column("id").from("t1").where("foo", .notIn, SQLSubquery .union { $0 .column("id").from("t2") } .except(distinct: { $0.column("id").from("t3") }) .finish() diff --git a/Tests/SQLKitTests/TestMocks.swift b/Tests/SQLKitTests/TestMocks.swift index ae9405f..85d3a12 100644 --- a/Tests/SQLKitTests/TestMocks.swift +++ b/Tests/SQLKitTests/TestMocks.swift @@ -1,18 +1,12 @@ import OrderedCollections -@testable import SQLKit -import NIOCore +public import SQLKit +import protocol NIOCore.EventLoop +import class NIOCore.EventLoopFuture +import class NIOEmbedded.NIOAsyncTestingEventLoop import Logging -import Dispatch - -/// An extremely incorrect implementation of the bare minimum of the `EventLoop` protocol, 'cause we have to have -/// _something_ for a database's event loop property despite never doing anything async. -final class FakeEventLoop: EventLoop, @unchecked Sendable { - func shutdownGracefully(queue: DispatchQueue, _: @escaping @Sendable ((any Error)?) -> Void) {} - var inEventLoop: Bool = false - func execute(_ work: @escaping @Sendable () -> Void) { self.inEventLoop = true; work(); self.inEventLoop = false } - @discardableResult func scheduleTask(deadline: NIODeadline, _: @escaping @Sendable () throws -> T) -> Scheduled { fatalError() } - @discardableResult func scheduleTask(in: TimeAmount, _: @escaping @Sendable () throws -> T) -> Scheduled { fatalError() } -} +#if canImport(Dispatch) +import class Dispatch.DispatchQueue +#endif extension SQLQueryBuilder { /// Serialize this builder's query and return the textual SQL, discarding any bindings. @@ -30,11 +24,11 @@ extension SQLQueryBuilder { } } -/// A very minimal mock `SQLDatabase` which implements `execut(sql:_:)` by saving the serialized SQL and bindings to +/// A very minimal mock `SQLDatabase` which implements `execute(sql:_:)` by saving the serialized SQL and bindings to /// its internal arrays of accumulated "results". Most things about its dialect are mutable. final class TestDatabase: SQLDatabase, @unchecked Sendable { let logger: Logger = { var l = Logger(label: "codes.vapor.sql.test"); l.logLevel = .debug; return l }() - let eventLoop: any EventLoop = FakeEventLoop() + let eventLoop: any EventLoop = NIOAsyncTestingEventLoop() var results: [String] = [] var bindResults: [[any Encodable & Sendable]] = [] var outputs: [any SQLRow] = [] @@ -151,3 +145,11 @@ extension SQLKit.SQLDataType: Swift.Equatable { } } } + +/// Used when testing defaulted async implementations +struct TestNoAsyncDatabase: SQLDatabase { + func execute(sql query: any SQLExpression, _ onRow: @escaping @Sendable (any SQLRow) -> ()) -> EventLoopFuture { self.eventLoop.makeSucceededVoidFuture() } + var logger: Logger { .init(label: "l") } + var eventLoop: any EventLoop { NIOAsyncTestingEventLoop() } + var dialect: any SQLDialect { GenericDialect() } +} diff --git a/Tests/SQLKitTests/Utilities.swift b/Tests/SQLKitTests/Utilities.swift index dd6cca8..9168efd 100644 --- a/Tests/SQLKitTests/Utilities.swift +++ b/Tests/SQLKitTests/Utilities.swift @@ -1,76 +1,99 @@ +import Foundation import Logging import SQLKit -import XCTest +import Testing -func XCTAssertNoThrowWithResult( - _ expression: @autoclosure () throws -> T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) -> T? { - var result: T? - - XCTAssertNoThrow(result = try expression(), message(), file: file, line: line) - return result -} - -func XCTAssertSerialization( +func expectSerialization( of queryBuilder: @autoclosure () throws -> some SQLQueryBuilder, is serialization: @autoclosure() throws -> String, - message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line -) { - XCTAssertEqual(try queryBuilder().simpleSerialize(), try serialization(), message(), file: file, line: line) + comment: Comment? = nil, + sourceLocation: SourceLocation = #_sourceLocation +) throws { + #expect(try queryBuilder().simpleSerialize() == serialization(), comment, sourceLocation: sourceLocation) } -func XCTAssertEncoding( +func expectEncoding( _ model: @autoclosure() throws -> any Encodable, using encoder: @autoclosure () throws -> SQLQueryEncoder, outputs columns: @autoclosure () throws -> [String], _ values: @autoclosure () throws -> [any SQLExpression], - _ message: @autoclosure() -> String = "", file: StaticString = #filePath, line: UInt = #line -) { - guard let columns = XCTAssertNoThrowWithResult(try columns(), message(), file: file, line: line), - let values = XCTAssertNoThrowWithResult(try values(), message(), file: file, line: line), - let model = XCTAssertNoThrowWithResult(try model(), message(), file: file, line: line), - let encoder = XCTAssertNoThrowWithResult(try encoder(), message(), file: file, line: line), - let encodedData = XCTAssertNoThrowWithResult(try encoder.encode(model), message(), file: file, line: line) - else { return } + comment: Comment? = nil, + sourceLocation: SourceLocation = #_sourceLocation +) throws { + let columns = try columns() + let values = try values() + let model = try model() + let encoder = try encoder() + let encodedData = try encoder.encode(model) let encodedColumns = encodedData.map(\.0), encodedValues = encodedData.map(\.1) - - XCTAssertEqual(columns, encodedColumns, message(), file: file, line: line) - XCTAssertEqual(values.count, encodedValues.count, message(), file: file, line: line) + + #expect(columns == encodedColumns, comment, sourceLocation: sourceLocation) + #expect(values.count == encodedValues.count, comment, sourceLocation: sourceLocation) for (value, encValue) in zip(values, encodedValues) { switch (value, encValue) { - case (let value as SQLLiteral, let encValue as SQLLiteral): XCTAssertEqual(value, encValue, message(), file: file, line: line) - case (let value as SQLBind, let encValue as SQLBind): XCTAssertEqual(value, encValue, message(), file: file, line: line) - case (let value as TestEncExpr.Enm, let encValue as TestEncExpr.Enm): XCTAssertEqual(value, encValue, message(), file: file, line: line) - default: XCTFail("Unexpected output (expected \(String(reflecting: value)), got \(String(reflecting: encValue))) \(message())", file: file, line: line) + case (let value as SQLLiteral, let encValue as SQLLiteral): #expect(value == encValue, comment, sourceLocation: sourceLocation) + case (let value as SQLBind, let encValue as SQLBind): #expect(value == encValue, comment, sourceLocation: sourceLocation) + case (let value as TestEncExpr.Enm, let encValue as TestEncExpr.Enm): #expect(value == encValue, comment, sourceLocation: sourceLocation) + default: Issue.record("Unexpected output (expected \(String(reflecting: value)), got \(String(reflecting: encValue))) \(comment)", sourceLocation: sourceLocation) } } } -func XCTAssertDecoding( +func expectDecoding( _: D.Type, from row: @autoclosure () throws -> some SQLRow, using decoder: @autoclosure () throws -> SQLRowDecoder, outputs model: @autoclosure () throws -> D, - _ message: @autoclosure() -> String = "", file: StaticString = #filePath, line: UInt = #line -) { - guard let row = XCTAssertNoThrowWithResult(try row(), message(), file: file, line: line), - let decoder = XCTAssertNoThrowWithResult(try decoder(), message(), file: file, line: line), - let model = XCTAssertNoThrowWithResult(try model(), message(), file: file, line: line), - let decodedModel = XCTAssertNoThrowWithResult(try decoder.decode(D.self, from: row), message(), file: file, line: line) - else { return } - - XCTAssertEqual(model, decodedModel, message(), file: file, line: line) + comment: Comment? = nil, + sourceLocation: SourceLocation = #_sourceLocation +) throws { + let row = try row() + let decoder = try decoder() + let model = try model() + let decodedModel = try decoder.decode(D.self, from: row) + + #expect(model == decodedModel, comment, sourceLocation: sourceLocation) } let isLoggingConfigured: Bool = { LoggingSystem.bootstrap { label in - var handler = StreamLogHandler.standardOutput(label: label) - + var handler = ModifiedStreamLogHandler.standardOutput(label: label) + handler.logLevel = ProcessInfo.processInfo.environment["LOG_LEVEL"].flatMap(Logger.Level.init(rawValue:)) ?? .info return handler } return true }() +struct ModifiedStreamLogHandler: LogHandler { + static func standardOutput(label: String) -> Self { .init(label: label) } + let label: String + var logLevel: Logger.Level = .info, metadataProvider = LoggingSystem.metadataProvider, metadata = Logger.Metadata() + subscript(metadataKey key: String) -> Logger.Metadata.Value? { get { self.metadata[key] } set { self.metadata[key] = newValue } } + func log(event: LogEvent) { + print("\(self.timestamp()) \(event.level) \(self.label) :\(self.prepMetadata(event.metadata).map { " \($0)" } ?? "") [\(event.source)] \(event.message)") + } + func prepMetadata(_ explicit: Logger.Metadata?) -> String? { self.prettify(self.metadata.merging(self.metadataProvider?.get() ?? [:]) { $1 }.merging(explicit ?? [:]) { $1 }) } + func prettify(_ metadata: Logger.Metadata) -> String? { metadata.isEmpty ? nil : metadata.lazy.sorted { $0.0 < $1.0 }.map { "\($0)=\($1.prettyDescription)" }.joined(separator: " ") } + private func timestamp() -> String { .init(unsafeUninitializedCapacity: 255) { buffer in + var timestamp = time(nil), tm = tm() + guard let localTime = localtime_r(×tamp, &tm) else { return buffer.initialize(fromContentsOf: "".utf8) } + return strftime(buffer.baseAddress!, buffer.count, "%Y-%m-%dT%H:%M:%S%z", localTime) + } } +} +extension Logger.MetadataValue { + var prettyDescription: String { + switch self { + case .dictionary(let dict): "[\(dict.mapValues(\.prettyDescription).lazy.sorted { $0.0 < $1.0 }.map { "\($0): \($1)" }.joined(separator: ", "))]" + case .array(let list): "[\(list.map(\.prettyDescription).joined(separator: ", "))]" + case .string(let str): #""\#(str)""# + case .stringConvertible(let repr): + switch repr { + case let repr as Bool: "\(repr)" + case let repr as any FixedWidthInteger: "\(repr)" + case let repr as any BinaryFloatingPoint: "\(repr)" + default: #""\#(repr.description)""# + } + } + } +} diff --git a/Tests/SQLKitTests/XCTAsyncAssertions.swift b/Tests/SQLKitTests/XCTAsyncAssertions.swift deleted file mode 100644 index 1f8b6e1..0000000 --- a/Tests/SQLKitTests/XCTAsyncAssertions.swift +++ /dev/null @@ -1,266 +0,0 @@ -import XCTest - -// MARK: - Unwrap - -func XCTUnwrapAsync( - _ expression: @autoclosure () async throws -> T?, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async throws -> T { - let result: T? - - do { - result = try await expression() - } catch { - return try XCTUnwrap(try { throw error }(), message(), file: file, line: line) - } - return try XCTUnwrap(result, message(), file: file, line: line) -} - -// MARK: - Equality - -func XCTAssertEqualAsync( - _ expression1: @autoclosure () async throws -> T, - _ expression2: @autoclosure () async throws -> T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async where T: Equatable { - do { - let expr1 = try await expression1(), expr2 = try await expression2() - return XCTAssertEqual(expr1, expr2, message(), file: file, line: line) - } catch { - return XCTAssertEqual(try { () -> Bool in throw error }(), false, message(), file: file, line: line) - } -} - -func XCTAssertNotEqualAsync( - _ expression1: @autoclosure () async throws -> T, - _ expression2: @autoclosure () async throws -> T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async where T: Equatable { - do { - let expr1 = try await expression1(), expr2 = try await expression2() - return XCTAssertNotEqual(expr1, expr2, message(), file: file, line: line) - } catch { - return XCTAssertNotEqual(try { () -> Bool in throw error }(), true, message(), file: file, line: line) - } -} - -// MARK: - Fuzzy equality - -func XCTAssertEqualAsync( - _ expression1: @autoclosure () async throws -> T, - _ expression2: @autoclosure () async throws -> T, - accuracy: T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async where T: Numeric { - do { - let expr1 = try await expression1(), expr2 = try await expression2() - return XCTAssertEqual(expr1, expr2, accuracy: accuracy, message(), file: file, line: line) - } catch { - return XCTAssertEqual(try { () -> Bool in throw error }(), false, message(), file: file, line: line) - } -} - -func XCTAssertNotEqualAsync( - _ expression1: @autoclosure () async throws -> T, - _ expression2: @autoclosure () async throws -> T, - accuracy: T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async where T: Numeric { - do { - let expr1 = try await expression1(), expr2 = try await expression2() - return XCTAssertNotEqual(expr1, expr2, accuracy: accuracy, message(), file: file, line: line) - } catch { - return XCTAssertNotEqual(try { () -> Bool in throw error }(), false, message(), file: file, line: line) - } -} - -func XCTAssertEqualAsync( - _ expression1: @autoclosure () async throws -> T, - _ expression2: @autoclosure () async throws -> T, - accuracy: T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async where T: FloatingPoint { - do { - let expr1 = try await expression1(), expr2 = try await expression2() - return XCTAssertEqual(expr1, expr2, accuracy: accuracy, message(), file: file, line: line) - } catch { - return XCTAssertEqual(try { () -> Bool in throw error }(), false, message(), file: file, line: line) - } -} - -func XCTAssertNotEqualAsync( - _ expression1: @autoclosure () async throws -> T, - _ expression2: @autoclosure () async throws -> T, - accuracy: T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async where T: FloatingPoint { - do { - let expr1 = try await expression1(), expr2 = try await expression2() - return XCTAssertNotEqual(expr1, expr2, accuracy: accuracy, message(), file: file, line: line) - } catch { - return XCTAssertNotEqual(try { () -> Bool in throw error }(), false, message(), file: file, line: line) - } -} - -// MARK: - Comparability - -func XCTAssertGreaterThanAsync( - _ expression1: @autoclosure () async throws -> T, - _ expression2: @autoclosure () async throws -> T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async where T: Comparable { - do { - let expr1 = try await expression1(), expr2 = try await expression2() - return XCTAssertGreaterThan(expr1, expr2, message(), file: file, line: line) - } catch { - return XCTAssertGreaterThan(try { () -> Int in throw error }(), 0, message(), file: file, line: line) - } -} - -func XCTAssertGreaterThanOrEqualAsync( - _ expression1: @autoclosure () async throws -> T, - _ expression2: @autoclosure () async throws -> T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async where T: Comparable { - do { - let expr1 = try await expression1(), expr2 = try await expression2() - return XCTAssertGreaterThanOrEqual(expr1, expr2, message(), file: file, line: line) - } catch { - return XCTAssertGreaterThanOrEqual(try { () -> Int in throw error }(), 0, message(), file: file, line: line) - } -} - - -func XCTAssertLessThanAsync( - _ expression1: @autoclosure () async throws -> T, - _ expression2: @autoclosure () async throws -> T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async where T: Comparable { - do { - let expr1 = try await expression1(), expr2 = try await expression2() - return XCTAssertLessThan(expr1, expr2, message(), file: file, line: line) - } catch { - return XCTAssertLessThan(try { () -> Int in throw error }(), 0, message(), file: file, line: line) - } -} - -func XCTAssertLessThanOrEqualAsync( - _ expression1: @autoclosure () async throws -> T, - _ expression2: @autoclosure () async throws -> T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async where T: Comparable { - do { - let expr1 = try await expression1(), expr2 = try await expression2() - return XCTAssertLessThanOrEqual(expr1, expr2, message(), file: file, line: line) - } catch { - return XCTAssertLessThanOrEqual(try { () -> Int in throw error }(), 0, message(), file: file, line: line) - } -} - -// MARK: - Truthiness - -func XCTAssertAsync( - _ predicate: @autoclosure () async throws -> Bool, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async { - do { - let result = try await predicate() - XCTAssert(result, message(), file: file, line: line) - } catch { - return XCTAssert(try { throw error }(), message(), file: file, line: line) - } -} - -func XCTAssertTrueAsync( - _ predicate: @autoclosure () async throws -> Bool, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async { - do { - let result = try await predicate() - XCTAssertTrue(result, message(), file: file, line: line) - } catch { - return XCTAssertTrue(try { throw error }(), message(), file: file, line: line) - } -} - -func XCTAssertFalseAsync( - _ predicate: @autoclosure () async throws -> Bool, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async { - do { - let result = try await predicate() - XCTAssertFalse(result, message(), file: file, line: line) - } catch { - return XCTAssertFalse(try { throw error }(), message(), file: file, line: line) - } -} - -// MARK: - Existence - -func XCTAssertNilAsync( - _ expression: @autoclosure () async throws -> Any?, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async { - do { - let result = try await expression() - return XCTAssertNil(result, message(), file: file, line: line) - } catch { - return XCTAssertNil(try { throw error }(), message(), file: file, line: line) - } -} - -func XCTAssertNotNilAsync( - _ expression: @autoclosure () async throws -> Any?, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async { - do { - let result = try await expression() - XCTAssertNotNil(result, message(), file: file, line: line) - } catch { - return XCTAssertNotNil(try { throw error }(), message(), file: file, line: line) - } -} - -// MARK: - Exceptionality - -func XCTAssertThrowsErrorAsync( - _ expression: @autoclosure () async throws -> T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line, - _ callback: (any Error) -> Void = { _ in } -) async { - do { - _ = try await expression() - XCTAssertThrowsError({}(), message(), file: file, line: line, callback) - } catch { - XCTAssertThrowsError(try { throw error }(), message(), file: file, line: line, callback) - } -} - -func XCTAssertNoThrowAsync( - _ expression: @autoclosure () async throws -> T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) async { - do { - _ = try await expression() - } catch { - XCTAssertNoThrow(try { throw error }(), message(), file: file, line: line) - } -}