diff --git a/Sources/PostgREST/PostgrestQueryBuilder.swift b/Sources/PostgREST/PostgrestQueryBuilder.swift index 4e1507a7..1a20263b 100644 --- a/Sources/PostgREST/PostgrestQueryBuilder.swift +++ b/Sources/PostgREST/PostgrestQueryBuilder.swift @@ -69,15 +69,11 @@ public final class PostgrestQueryBuilder: PostgrestBuilder { if !prefersHeaders.isEmpty { $0.request.headers["Prefer"] = prefersHeaders.joined(separator: ",") } - - // TODO: How to do this in Swift? - // if (Array.isArray(values)) { - // const columns = values.reduce((acc, x) => acc.concat(Object.keys(x)), [] as string[]) - // if (columns.length > 0) { - // const uniqueColumns = [...new Set(columns)].map((column) => `"${column}"`) - // this.url.searchParams.set('columns', uniqueColumns.join(',')) - // } - // } + if let body = $0.request.body, let jsonObject = try JSONSerialization.jsonObject(with: body) as? [[String: Any]] { + let allKeys = jsonObject.flatMap(\.keys) + let uniqueKeys = Set(allKeys).sorted() + $0.request.query.append(URLQueryItem(name: "columns", value: uniqueKeys.joined(separator: ","))) + } } return PostgrestFilterBuilder(self) @@ -118,6 +114,12 @@ public final class PostgrestQueryBuilder: PostgrestBuilder { if !prefersHeaders.isEmpty { $0.request.headers["Prefer"] = prefersHeaders.joined(separator: ",") } + + if let body = $0.request.body, let jsonObject = try JSONSerialization.jsonObject(with: body) as? [[String: Any]] { + let allKeys = jsonObject.flatMap(\.keys) + let uniqueKeys = Set(allKeys).sorted() + $0.request.query.append(URLQueryItem(name: "columns", value: uniqueKeys.joined(separator: ","))) + } } return PostgrestFilterBuilder(self) } diff --git a/Tests/PostgRESTTests/BuildURLRequestTests.swift b/Tests/PostgRESTTests/BuildURLRequestTests.swift index 2ef87882..4753ba04 100644 --- a/Tests/PostgRESTTests/BuildURLRequestTests.swift +++ b/Tests/PostgRESTTests/BuildURLRequestTests.swift @@ -10,19 +10,43 @@ import XCTest import FoundationNetworking #endif +struct User: Encodable { + var email: String + var username: String? +} + @MainActor final class BuildURLRequestTests: XCTestCase { let url = URL(string: "https://example.supabase.co")! struct TestCase: Sendable { let name: String - var record = false + let record: Bool + let file: StaticString + let line: UInt let build: @Sendable (PostgrestClient) async throws -> PostgrestBuilder + + init( + name: String, + record: Bool = false, + file: StaticString = #file, + line: UInt = #line, + build: @escaping @Sendable (PostgrestClient) async throws -> PostgrestBuilder + ) { + self.name = name + self.record = record + self.file = file + self.line = line + self.build = build + } } func testBuildRequest() async throws { let runningTestCase = ActorIsolated(TestCase?.none) + let encoder = PostgrestClient.Configuration.jsonEncoder + encoder.outputFormatting = .sortedKeys + let client = PostgrestClient( url: url, schema: nil, @@ -39,12 +63,15 @@ final class BuildURLRequestTests: XCTestCase { as: .curl, named: runningTestCase.name, record: runningTestCase.record, - testName: "testBuildRequest()" + file: runningTestCase.file, + testName: "testBuildRequest()", + line: runningTestCase.line ) } return (Data(), URLResponse()) - } + }, + encoder: encoder ) let testCases: [TestCase] = [ @@ -55,7 +82,16 @@ final class BuildURLRequestTests: XCTestCase { }, TestCase(name: "insert new user") { client in try await client.from("users") - .insert(["email": "johndoe@supabase.io"]) + .insert(User(email: "johndoe@supabase.io")) + }, + TestCase(name: "bulk insert users") { client in + try await client.from("users") + .insert( + [ + User(email: "johndoe@supabase.io"), + User(email: "johndoe2@supabase.io", username: "johndoe2"), + ] + ) }, TestCase(name: "call rpc") { client in try await client.rpc("test_fcn", params: ["KEY": "VALUE"]) @@ -89,11 +125,20 @@ final class BuildURLRequestTests: XCTestCase { }, TestCase(name: "test upsert not ignoring duplicates") { client in try await client.from("users") - .upsert(["email": "johndoe@supabase.io"]) + .upsert(User(email: "johndoe@supabase.io")) + }, + TestCase(name: "bulk upsert") { client in + try await client.from("users") + .upsert( + [ + User(email: "johndoe@supabase.io"), + User(email: "johndoe2@supabase.io", username: "johndoe2"), + ] + ) }, TestCase(name: "test upsert ignoring duplicates") { client in try await client.from("users") - .upsert(["email": "johndoe@supabase.io"], ignoreDuplicates: true) + .upsert(User(email: "johndoe@supabase.io"), ignoreDuplicates: true) }, TestCase(name: "query with + character") { client in await client.from("users") @@ -110,7 +155,7 @@ final class BuildURLRequestTests: XCTestCase { await client.schema("storage") .from("objects") .select() - } + }, ] for testCase in testCases { diff --git a/Tests/PostgRESTTests/__Snapshots__/BuildURLRequestTests/testBuildRequest.bulk-insert-users.txt b/Tests/PostgRESTTests/__Snapshots__/BuildURLRequestTests/testBuildRequest.bulk-insert-users.txt new file mode 100644 index 00000000..e9fdf5b6 --- /dev/null +++ b/Tests/PostgRESTTests/__Snapshots__/BuildURLRequestTests/testBuildRequest.bulk-insert-users.txt @@ -0,0 +1,7 @@ +curl \ + --request POST \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --header "X-Client-Info: postgrest-swift/x.y.z" \ + --data "[{\"email\":\"johndoe@supabase.io\"},{\"email\":\"johndoe2@supabase.io\",\"username\":\"johndoe2\"}]" \ + "https://example.supabase.co/users?columns=email,username" \ No newline at end of file diff --git a/Tests/PostgRESTTests/__Snapshots__/BuildURLRequestTests/testBuildRequest.bulk-upsert.txt b/Tests/PostgRESTTests/__Snapshots__/BuildURLRequestTests/testBuildRequest.bulk-upsert.txt new file mode 100644 index 00000000..43a239d3 --- /dev/null +++ b/Tests/PostgRESTTests/__Snapshots__/BuildURLRequestTests/testBuildRequest.bulk-upsert.txt @@ -0,0 +1,8 @@ +curl \ + --request POST \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --header "Prefer: resolution=merge-duplicates,return=representation" \ + --header "X-Client-Info: postgrest-swift/x.y.z" \ + --data "[{\"email\":\"johndoe@supabase.io\"},{\"email\":\"johndoe2@supabase.io\",\"username\":\"johndoe2\"}]" \ + "https://example.supabase.co/users?columns=email,username" \ No newline at end of file