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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 57 additions & 9 deletions Sources/PostgREST/PostgrestClient.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ConcurrencyExtras
import Foundation
import Helpers
import HTTPTypes
import Helpers

public typealias PostgrestError = Helpers.PostgrestError
public typealias HTTPError = Helpers.HTTPError
Expand Down Expand Up @@ -129,31 +129,69 @@ public final class PostgrestClient: Sendable {
/// - Parameters:
/// - fn: The function name to call.
/// - params: The parameters to pass to the function call.
/// - head: When set to `true`, `data`, will not be returned. Useful if you only need the count.
/// - get: When set to `true`, the function will be called with read-only access mode.
/// - count: Count algorithm to use to count rows returned by the function. Only applicable for [set-returning functions](https://www.postgresql.org/docs/current/functions-srf.html).
public func rpc(
_ fn: String,
params: some Encodable & Sendable,
head: Bool = false,
get: Bool = false,
count: CountOption? = nil
) throws -> PostgrestFilterBuilder {
try PostgrestRpcBuilder(
let method: HTTPTypes.HTTPRequest.Method
var url = configuration.url.appendingPathComponent("rpc/\(fn)")
let bodyData = try configuration.encoder.encode(params)
var body: Data?

if head || get {
method = head ? .head : .get

guard let json = try JSONSerialization.jsonObject(with: bodyData) as? [String: Any] else {
throw PostgrestError(
message: "Params should be a key-value type when using `GET` or `HEAD` options.")
}

for (key, value) in json {
let formattedValue = (value as? [Any]).map(cleanFilterArray) ?? String(describing: value)
url.appendQueryItems([URLQueryItem(name: key, value: formattedValue)])
}

} else {
method = .post
body = bodyData
}

var request = HTTPRequest(
url: url,
method: method,
headers: HTTPFields(configuration.headers),
body: params is NoParams ? nil : body
)

if let count {
request.headers[.prefer] = "count=\(count.rawValue)"
}

return PostgrestFilterBuilder(
configuration: configuration,
request: HTTPRequest(
url: configuration.url.appendingPathComponent("rpc/\(fn)"),
method: .post,
headers: HTTPFields(configuration.headers)
)
).rpc(params: params, count: count)
request: request
)
}

/// Perform a function call.
/// - Parameters:
/// - fn: The function name to call.
/// - head: When set to `true`, `data`, will not be returned. Useful if you only need the count.
/// - get: When set to `true`, the function will be called with read-only access mode.
/// - count: Count algorithm to use to count rows returned by the function. Only applicable for [set-returning functions](https://www.postgresql.org/docs/current/functions-srf.html).
public func rpc(
_ fn: String,
head: Bool = false,
get: Bool = false,
count: CountOption? = nil
) throws -> PostgrestFilterBuilder {
try rpc(fn, params: NoParams(), count: count)
try rpc(fn, params: NoParams(), head: head, get: get, count: count)
}

/// Select a schema to query or perform an function (rpc) call.
Expand All @@ -165,4 +203,14 @@ public final class PostgrestClient: Sendable {
configuration.schema = schema
return PostgrestClient(configuration: configuration)
}

private func cleanFilterArray(_ filter: [Any]) -> String {
"{\(filter.map { String(describing: $0) }.joined(separator: ","))}"
}
}

struct NoParams: Encodable {}

extension HTTPField.Name {
static let prefer = Self("Prefer")!
}
2 changes: 1 addition & 1 deletion Sources/PostgREST/PostgrestFilterBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import Helpers

public class PostgrestFilterBuilder: PostgrestTransformBuilder {
public class PostgrestFilterBuilder: PostgrestTransformBuilder, @unchecked Sendable {
public enum Operator: String, CaseIterable, Sendable {
case eq, neq, gt, gte, lt, lte, like, ilike, `is`, `in`, cs, cd, sl, sr, nxl, nxr, adj, ov, fts,
plfts, phfts, wfts
Expand Down Expand Up @@ -624,7 +624,7 @@
query: String,
config: String? = nil
) -> PostgrestFilterBuilder {
plfts(column, query: query, config: config)

Check warning on line 627 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (MAC_CATALYST)

'plfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .plain type.

Check warning on line 627 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (MAC_CATALYST)

'plfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .plain type.

Check warning on line 627 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (WATCHOS)

'plfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .plain type.

Check warning on line 627 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (WATCHOS)

'plfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .plain type.
}

public func phraseToFullTextSearch(
Expand All @@ -632,7 +632,7 @@
query: String,
config: String? = nil
) -> PostgrestFilterBuilder {
phfts(column, query: query, config: config)

Check warning on line 635 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (MAC_CATALYST)

'phfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .phrase type.

Check warning on line 635 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (MAC_CATALYST)

'phfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .phrase type.

Check warning on line 635 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (WATCHOS)

'phfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .phrase type.

Check warning on line 635 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (WATCHOS)

'phfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .phrase type.
}

public func webFullTextSearch(
Expand All @@ -640,6 +640,6 @@
query: String,
config: String? = nil
) -> PostgrestFilterBuilder {
wfts(column, query: query, config: config)

Check warning on line 643 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (MAC_CATALYST)

'wfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .websearch type.

Check warning on line 643 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (MAC_CATALYST)

'wfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .websearch type.

Check warning on line 643 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (WATCHOS)

'wfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .websearch type.

Check warning on line 643 in Sources/PostgREST/PostgrestFilterBuilder.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (WATCHOS)

'wfts(_:query:config:)' is deprecated: Use textSearch(_:query:config:type) with .websearch type.
}
}
2 changes: 1 addition & 1 deletion Sources/PostgREST/PostgrestQueryBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import Helpers

public final class PostgrestQueryBuilder: PostgrestBuilder {
public final class PostgrestQueryBuilder: PostgrestBuilder, @unchecked Sendable {
/// Perform a SELECT query on the table or view.
/// - Parameters:
/// - columns: The columns to retrieve, separated by commas. Columns can be renamed when returned with `customName:columnName`
Expand Down
48 changes: 0 additions & 48 deletions Sources/PostgREST/PostgrestRpcBuilder.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/PostgREST/PostgrestTransformBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import Helpers

public class PostgrestTransformBuilder: PostgrestBuilder {
public class PostgrestTransformBuilder: PostgrestBuilder, @unchecked Sendable {
/// Perform a SELECT on the query result.
///
/// By default, `.insert()`, `.update()`, `.upsert()`, and `.delete()` do not return modified rows. By calling this method, modified rows are returned in `value`.
Expand Down
49 changes: 40 additions & 9 deletions Tests/IntegrationTests/Potsgrest/PostgresTransformsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ final class PostgrestTransformsTests: XCTestCase {
configuration: PostgrestClient.Configuration(
url: URL(string: "\(DotEnv.SUPABASE_URL)/rest/v1")!,
headers: [
"apikey": DotEnv.SUPABASE_ANON_KEY,
"apikey": DotEnv.SUPABASE_ANON_KEY
],
logger: nil
)
)

func testOrder() async throws {
let res = try await client.from("users")
let res =
try await client.from("users")
.select()
.order("username", ascending: false)
.execute().value as AnyJSON
Expand Down Expand Up @@ -63,7 +64,8 @@ final class PostgrestTransformsTests: XCTestCase {
}

func testOrderOnMultipleColumns() async throws {
let res = try await client.from("messages")
let res =
try await client.from("messages")
.select()
.order("channel_id", ascending: false)
.order("username", ascending: false)
Expand Down Expand Up @@ -92,7 +94,8 @@ final class PostgrestTransformsTests: XCTestCase {
}

func testLimit() async throws {
let res = try await client.from("users")
let res =
try await client.from("users")
.select()
.limit(1)
.execute().value as AnyJSON
Expand All @@ -113,7 +116,8 @@ final class PostgrestTransformsTests: XCTestCase {
}

func testRange() async throws {
let res = try await client.from("users")
let res =
try await client.from("users")
.select()
.range(from: 1, to: 3)
.execute().value as AnyJSON
Expand Down Expand Up @@ -148,7 +152,8 @@ final class PostgrestTransformsTests: XCTestCase {
}

func testSingle() async throws {
let res = try await client.from("users")
let res =
try await client.from("users")
.select()
.limit(1)
.single()
Expand All @@ -168,7 +173,8 @@ final class PostgrestTransformsTests: XCTestCase {
}

func testSingleOnInsert() async throws {
let res = try await client.from("users")
let res =
try await client.from("users")
.insert(["username": "foo"])
.select()
.single()
Expand All @@ -193,7 +199,8 @@ final class PostgrestTransformsTests: XCTestCase {
}

func testSelectOnInsert() async throws {
let res = try await client.from("users")
let res =
try await client.from("users")
.insert(["username": "foo"])
.select("status")
.execute().value as AnyJSON
Expand All @@ -215,7 +222,8 @@ final class PostgrestTransformsTests: XCTestCase {
}

func testSelectOnRpc() async throws {
let res = try await client.rpc("get_username_and_status", params: ["name_param": "supabot"])
let res =
try await client.rpc("get_username_and_status", params: ["name_param": "supabot"])
.select("status")
.execute().value as AnyJSON

Expand All @@ -230,6 +238,29 @@ final class PostgrestTransformsTests: XCTestCase {
}
}

func testRpcWithArray() async throws {
struct Params: Encodable {
let arr: [Int]
let index: Int
}
let res =
try await client.rpc("get_array_element", params: Params(arr: [37, 420, 64], index: 2))
.execute().value as Int
XCTAssertEqual(res, 420)
}

func testRpcWithReadOnlyAccessMode() async throws {
struct Params: Encodable {
let arr: [Int]
let index: Int
}
let res =
try await client.rpc(
"get_array_element", params: Params(arr: [37, 420, 64], index: 2), get: true
).execute().value as Int
XCTAssertEqual(res, 420)
}

func testCsv() async throws {
let res = try await client.from("users").select().csv().execute().string()
assertInlineSnapshot(of: res, as: .json) {
Expand Down
13 changes: 13 additions & 0 deletions Tests/PostgRESTTests/BuildURLRequestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,19 @@ final class BuildURLRequestTests: XCTestCase {
.select()
.gt("created_at", value: Date(timeIntervalSince1970: 0))
},
TestCase(name: "rpc call with head") { client in
try client.rpc("sum", head: true)
},
TestCase(name: "rpc call with get") { client in
try client.rpc("sum", get: true)
},
TestCase(name: "rpc call with get and params") { client in
try client.rpc(
"get_array_element",
params: ["array": [37, 420, 64], "index": 2] as AnyJSON,
get: true
)
},
]

for testCase in testCases {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
curl \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Client-Info: postgrest-swift/x.y.z" \
"https://example.supabase.co/rpc/get_array_element?array=%7B37,420,64%7D&index=2"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
curl \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Client-Info: postgrest-swift/x.y.z" \
"https://example.supabase.co/rpc/sum"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
curl \
--head \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Client-Info: postgrest-swift/x.y.z" \
"https://example.supabase.co/rpc/sum"
Loading