Skip to content

Commit bfee3d0

Browse files
authored
Extract optional PostgREST properties and add schema support for RPC. (#716)
* Extract optional PostgREST properties and add schema support for RPC. * Fix tests * Fix custom headers for RPC requests * Remove wildcard import * Fix test * Fix DSL builder * Update Postgrest/src/commonTest/kotlin/PostgrestRequestBuilderTest.kt * Update PostgrestRequestBuilderTest.kt
1 parent 86e5a97 commit bfee3d0

File tree

12 files changed

+151
-85
lines changed

12 files changed

+151
-85
lines changed

Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt

-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ sealed interface Postgrest : MainPlugin<Postgrest.Config>, CustomSerializationPl
5353

5454
/**
5555
* Creates a new [PostgrestQueryBuilder] for the given schema and table
56-
* @param schema The schema to use for the requests
5756
* @param table The table to use for the requests
5857
*/
5958
operator fun get(table: String): PostgrestQueryBuilder = from(table)

Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestRpc.kt

+15-13
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
package io.github.jan.supabase.postgrest
33

44
import io.github.jan.supabase.encodeToJsonElement
5+
import io.github.jan.supabase.exceptions.RestException
56
import io.github.jan.supabase.postgrest.executor.RestRequestExecutor
67
import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder
8+
import io.github.jan.supabase.postgrest.query.request.RpcPostgrestRequestBuilder
79
import io.github.jan.supabase.postgrest.request.RpcRequest
810
import io.github.jan.supabase.postgrest.result.PostgrestResult
911
import io.ktor.http.HttpMethod
@@ -34,29 +36,29 @@ enum class RpcMethod(val httpMethod: HttpMethod) {
3436
*
3537
* @param function The name of the function
3638
* @param parameters The parameters for the function
37-
* @param method The HTTP method to use. Default is POST
3839
* @param request Filter the result
3940
* @throws RestException or one of its subclasses if the request failed
4041
*/
4142
suspend inline fun <reified T : Any> Postgrest.rpc(
4243
function: String,
4344
parameters: T,
44-
method: RpcMethod = RpcMethod.POST,
45-
request: PostgrestRequestBuilder.() -> Unit = {},
45+
request: RpcPostgrestRequestBuilder.() -> Unit = {},
4646
): PostgrestResult {
4747
val encodedParameters = if (parameters is JsonElement) parameters else serializer.encodeToJsonElement(parameters)
48-
val requestBuilder = PostgrestRequestBuilder(config.propertyConversionMethod).apply(request)
48+
val requestBuilder = RpcPostgrestRequestBuilder(config.defaultSchema, config.propertyConversionMethod).apply(request)
4949
val urlParams = buildMap {
5050
putAll(requestBuilder.params.mapToFirstValue())
51-
if(method != RpcMethod.POST) {
51+
if(requestBuilder.method != RpcMethod.POST) {
5252
putAll(encodedParameters.jsonObject.mapValues { it.value.toString() })
5353
}
5454
}
5555
val rpcRequest = RpcRequest(
56-
method = method.httpMethod,
56+
method = requestBuilder.method.httpMethod,
5757
count = requestBuilder.count,
5858
urlParams = urlParams,
59-
body = encodedParameters
59+
body = encodedParameters,
60+
schema = requestBuilder.schema,
61+
headers = requestBuilder.headers.build()
6062
)
6163
return RestRequestExecutor.execute(postgrest = this, path = "rpc/$function", request = rpcRequest)
6264
}
@@ -65,20 +67,20 @@ suspend inline fun <reified T : Any> Postgrest.rpc(
6567
* Executes a database function
6668
*
6769
* @param function The name of the function
68-
* @param method The HTTP method to use. Default is POST
6970
* @param request Filter the result
7071
* @throws RestException or one of its subclasses if the request failed
7172
*/
7273
suspend inline fun Postgrest.rpc(
7374
function: String,
74-
method: RpcMethod = RpcMethod.POST,
75-
request: PostgrestRequestBuilder.() -> Unit = {}
75+
request: RpcPostgrestRequestBuilder.() -> Unit = {}
7676
): PostgrestResult {
77-
val requestBuilder = PostgrestRequestBuilder(config.propertyConversionMethod).apply(request)
77+
val requestBuilder = RpcPostgrestRequestBuilder(config.defaultSchema, config.propertyConversionMethod).apply(request)
7878
val rpcRequest = RpcRequest(
79-
method = method.httpMethod,
79+
method = requestBuilder.method.httpMethod,
8080
count = requestBuilder.count,
81-
urlParams = requestBuilder.params.mapToFirstValue()
81+
urlParams = requestBuilder.params.mapToFirstValue(),
82+
schema = requestBuilder.schema,
83+
headers = requestBuilder.headers.build()
8284
)
8385
return RestRequestExecutor.execute(postgrest = this, path = "rpc/$function", request = rpcRequest)
8486
}

Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt

+27-54
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import io.github.jan.supabase.gotrue.PostgrestFilterDSL
88
import io.github.jan.supabase.postgrest.Postgrest
99
import io.github.jan.supabase.postgrest.executor.RestRequestExecutor
1010
import io.github.jan.supabase.postgrest.mapToFirstValue
11+
import io.github.jan.supabase.postgrest.query.request.InsertPostgrestRequestBuilder
12+
import io.github.jan.supabase.postgrest.query.request.SelectPostgrestRequestBuilder
13+
import io.github.jan.supabase.postgrest.query.request.UpsertPostgrestRequestBuilder
1114
import io.github.jan.supabase.postgrest.request.DeleteRequest
1215
import io.github.jan.supabase.postgrest.request.InsertRequest
1316
import io.github.jan.supabase.postgrest.request.SelectRequest
@@ -30,23 +33,21 @@ class PostgrestQueryBuilder(
3033
* Executes vertical filtering with select on [table]
3134
*
3235
* @param columns The columns to retrieve, defaults to [Columns.ALL]. You can also use [Columns.list], [Columns.type] or [Columns.raw] to specify the columns
33-
* @param head If true, no body will be returned. Useful when using count.
34-
* @param request Additional filtering to apply to the query
36+
* @param request Additional configurations for the request including filters
3537
* @return PostgrestResult which is either an error, an empty JsonArray or the data you requested as an JsonArray
3638
* @throws RestException or one of its subclasses if receiving an error response
3739
* @throws HttpRequestTimeoutException if the request timed out
3840
* @throws HttpRequestException on network related issues
3941
*/
4042
suspend inline fun select(
4143
columns: Columns = Columns.ALL,
42-
head: Boolean = false,
43-
request: @PostgrestFilterDSL PostgrestRequestBuilder.() -> Unit = {}
44+
request: @PostgrestFilterDSL SelectPostgrestRequestBuilder.() -> Unit = {}
4445
): PostgrestResult {
45-
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod) {
46+
val requestBuilder = SelectPostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply {
4647
request(); params["select"] = listOf(columns.value)
4748
}
4849
val selectRequest = SelectRequest(
49-
head = head,
50+
head = requestBuilder.head,
5051
count = requestBuilder.count,
5152
urlParams = requestBuilder.params.mapToFirstValue(),
5253
schema = schema,
@@ -57,38 +58,28 @@ class PostgrestQueryBuilder(
5758

5859
/**
5960
* Perform an UPSERT on the table or view. Depending on the column(s) passed
60-
* to [onConflict], [upsert] allows you to perform the equivalent of
61+
* to [UpsertPostgrestRequestBuilder.onConflict], [upsert] allows you to perform the equivalent of
6162
* `[insert] if a row with the corresponding onConflict columns doesn't
6263
* exist, or if it does exist, perform an alternative action depending on
63-
* [ignoreDuplicates].
64+
* [UpsertPostgrestRequestBuilder.ignoreDuplicates].
6465
*
6566
* By default, upserted rows are not returned. To return it, call `[PostgrestRequestBuilder.select]`.
6667
*
6768
* @param values The values to insert, will automatically get serialized into json.
68-
* @param request Additional filtering to apply to the query
69-
* @param onConflict Comma-separated UNIQUE column(s) to specify how
70-
* duplicate rows are determined. Two rows are duplicates if all the
71-
* `onConflict` columns are equal.
72-
* @param defaultToNull Make missing fields default to `null`.
73-
* Otherwise, use the default value for the column. This only applies when
74-
* inserting new rows, not when merging with existing rows under
75-
* @param ignoreDuplicates If `true`, duplicate rows are ignored. If `false`, duplicate rows are merged with existing rows.
69+
* @param request Additional configurations for the request including filters
7670
* @throws RestException or one of its subclasses if receiving an error response
7771
* @throws HttpRequestTimeoutException if the request timed out
7872
* @throws HttpRequestException on network related issues
7973
*/
8074
suspend inline fun <reified T : Any> upsert(
8175
values: List<T>,
82-
onConflict: String? = null,
83-
defaultToNull: Boolean = true,
84-
ignoreDuplicates: Boolean = false,
85-
request: PostgrestRequestBuilder.() -> Unit = {}
76+
request: UpsertPostgrestRequestBuilder.() -> Unit = {}
8677
): PostgrestResult {
87-
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request)
78+
val requestBuilder = UpsertPostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request)
8879
val body = postgrest.serializer.encodeToJsonElement(values).jsonArray
8980
val columns = body.map { it.jsonObject.keys }.flatten().distinct()
9081
requestBuilder.params["columns"] = listOf(columns.joinToString(","))
91-
onConflict?.let {
82+
requestBuilder.onConflict?.let {
9283
requestBuilder.params["on_conflict"] = listOf(it)
9384
}
9485
val insertRequest = InsertRequest(
@@ -97,8 +88,8 @@ class PostgrestQueryBuilder(
9788
returning = requestBuilder.returning,
9889
count = requestBuilder.count,
9990
urlParams = requestBuilder.params.mapToFirstValue(),
100-
defaultToNull = defaultToNull,
101-
ignoreDuplicates = ignoreDuplicates,
91+
defaultToNull = requestBuilder.defaultToNull,
92+
ignoreDuplicates = requestBuilder.ignoreDuplicates,
10293
schema = schema,
10394
headers = requestBuilder.headers.build()
10495
)
@@ -107,52 +98,38 @@ class PostgrestQueryBuilder(
10798

10899
/**
109100
* Perform an UPSERT on the table or view. Depending on the column(s) passed
110-
* to [onConflict], [upsert] allows you to perform the equivalent of
101+
* to [UpsertPostgrestRequestBuilder.onConflict], [upsert] allows you to perform the equivalent of
111102
* `[insert] if a row with the corresponding onConflict columns doesn't
112103
* exist, or if it does exist, perform an alternative action depending on
113-
* [ignoreDuplicates].
104+
* [UpsertPostgrestRequestBuilder.ignoreDuplicates].
114105
*
115106
* By default, upserted rows are not returned. To return it, call `[PostgrestRequestBuilder.select]`.
116107
*
117108
* @param value The value to insert, will automatically get serialized into json.
118109
* @param request Additional filtering to apply to the query
119-
* @param onConflict Comma-separated UNIQUE column(s) to specify how
120-
* duplicate rows are determined. Two rows are duplicates if all the
121-
* `onConflict` columns are equal.
122-
* @param defaultToNull Make missing fields default to `null`.
123-
* Otherwise, use the default value for the column. This only applies when
124-
* inserting new rows, not when merging with existing rows under
125-
* @param ignoreDuplicates If `true`, duplicate rows are ignored. If `false`, duplicate rows are merged with existing rows.
126110
* @throws RestException or one of its subclasses if receiving an error response
127111
* @throws HttpRequestTimeoutException if the request timed out
128112
* @throws HttpRequestException on network related issues
129113
*/
130114
suspend inline fun <reified T : Any> upsert(
131115
value: T,
132-
onConflict: String? = null,
133-
defaultToNull: Boolean = true,
134-
ignoreDuplicates: Boolean = false,
135-
request: PostgrestRequestBuilder.() -> Unit = {}
136-
): PostgrestResult = upsert(listOf(value), onConflict, defaultToNull, ignoreDuplicates, request)
116+
request: UpsertPostgrestRequestBuilder.() -> Unit = {}
117+
): PostgrestResult = upsert(listOf(value), request)
137118

138119
/**
139120
* Executes an insert operation on the [table]
140121
*
141122
* @param values The values to insert, will automatically get serialized into json.
142123
* @param request Additional filtering to apply to the query
143-
* @param defaultToNull Make missing fields default to `null`.
144-
* Otherwise, use the default value for the column. This only applies when
145-
* inserting new rows, not when merging with existing rows under
146124
* @throws RestException or one of its subclasses if receiving an error response
147125
* @throws HttpRequestTimeoutException if the request timed out
148126
* @throws HttpRequestException on network related issues
149127
*/
150128
suspend inline fun <reified T : Any> insert(
151129
values: List<T>,
152-
defaultToNull: Boolean = true,
153-
request: PostgrestRequestBuilder.() -> Unit = {}
130+
request: InsertPostgrestRequestBuilder.() -> Unit = {}
154131
): PostgrestResult {
155-
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request)
132+
val requestBuilder = InsertPostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request)
156133
val body = postgrest.serializer.encodeToJsonElement(values).jsonArray
157134
val columns = body.map { it.jsonObject.keys }.flatten().distinct()
158135
requestBuilder.params["columns"] = listOf(columns.joinToString(","))
@@ -163,7 +140,7 @@ class PostgrestQueryBuilder(
163140
urlParams = requestBuilder.params.mapToFirstValue(),
164141
schema = schema,
165142
headers = requestBuilder.headers.build(),
166-
defaultToNull = defaultToNull
143+
defaultToNull = requestBuilder.defaultToNull
167144
)
168145
return RestRequestExecutor.execute(postgrest = postgrest, path = table, request = insertRequest)
169146
}
@@ -173,18 +150,14 @@ class PostgrestQueryBuilder(
173150
*
174151
* @param value The value to insert, will automatically get serialized into json.
175152
* @param request Additional filtering to apply to the query
176-
* @param defaultToNull Make missing fields default to `null`.
177-
* Otherwise, use the default value for the column. This only applies when
178-
* inserting new rows, not when merging with existing rows under
179153
* @throws RestException or one of its subclasses if receiving an error response
180154
* @throws HttpRequestTimeoutException if the request timed out
181155
* @throws HttpRequestException on network related issues
182156
*/
183157
suspend inline fun <reified T : Any> insert(
184158
value: T,
185-
defaultToNull: Boolean = true,
186-
request: PostgrestRequestBuilder.() -> Unit = {}
187-
) = insert(listOf(value), defaultToNull, request)
159+
request: InsertPostgrestRequestBuilder.() -> Unit = {}
160+
) = insert(listOf(value), request)
188161

189162
/**
190163
* Executes an update operation on the [table].
@@ -201,7 +174,7 @@ class PostgrestQueryBuilder(
201174
crossinline update: PostgrestUpdate.() -> Unit = {},
202175
request: PostgrestRequestBuilder.() -> Unit = {}
203176
): PostgrestResult {
204-
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request)
177+
val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request)
205178
val updateRequest = UpdateRequest(
206179
body = buildPostgrestUpdate(postgrest.config.propertyConversionMethod, postgrest.serializer, update),
207180
returning = requestBuilder.returning,
@@ -228,7 +201,7 @@ class PostgrestQueryBuilder(
228201
value: T,
229202
request: PostgrestRequestBuilder.() -> Unit = {}
230203
): PostgrestResult {
231-
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request)
204+
val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request)
232205
val updateRequest = UpdateRequest(
233206
returning = requestBuilder.returning,
234207
count = requestBuilder.count,
@@ -253,7 +226,7 @@ class PostgrestQueryBuilder(
253226
suspend inline fun delete(
254227
request: PostgrestRequestBuilder.() -> Unit = {}
255228
): PostgrestResult {
256-
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request)
229+
val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request)
257230
val deleteRequest = DeleteRequest(
258231
returning = requestBuilder.returning,
259232
count = requestBuilder.count,

Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt

+1-9
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import kotlin.js.JsName
1515
* A builder for Postgrest requests.
1616
*/
1717
@PostgrestFilterDSL
18-
class PostgrestRequestBuilder(@PublishedApi internal val propertyConversionMethod: PropertyConversionMethod) {
18+
open class PostgrestRequestBuilder(@PublishedApi internal val propertyConversionMethod: PropertyConversionMethod) {
1919

2020
/**
2121
* The [Count] algorithm to use to count rows in the table or view.
@@ -163,11 +163,3 @@ class PostgrestRequestBuilder(@PublishedApi internal val propertyConversionMetho
163163

164164
}
165165

166-
@SupabaseInternal
167-
inline fun postgrestRequest(propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, block: PostgrestRequestBuilder.() -> Unit): PostgrestRequestBuilder {
168-
val filter = PostgrestRequestBuilder(propertyConversionMethod)
169-
filter.block()
170-
return filter
171-
}
172-
173-
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.github.jan.supabase.postgrest.query.request
2+
3+
import io.github.jan.supabase.postgrest.PropertyConversionMethod
4+
import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder
5+
import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder
6+
7+
/**
8+
* Request builder for [PostgrestQueryBuilder.insert]
9+
*/
10+
open class InsertPostgrestRequestBuilder(propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) {
11+
12+
/**
13+
* Make missing fields default to `null`.
14+
* Otherwise, use the default value for the column. This only applies when
15+
* inserting new rows, not when merging with existing rows under
16+
*/
17+
var defaultToNull: Boolean = true
18+
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.github.jan.supabase.postgrest.query.request
2+
3+
import io.github.jan.supabase.postgrest.Postgrest
4+
import io.github.jan.supabase.postgrest.PropertyConversionMethod
5+
import io.github.jan.supabase.postgrest.RpcMethod
6+
import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder
7+
import io.github.jan.supabase.postgrest.rpc
8+
9+
/**
10+
* Request builder for [Postgrest.rpc]
11+
*/
12+
class RpcPostgrestRequestBuilder(defaultSchema: String, propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) {
13+
14+
/**
15+
* The HTTP method to use. Default is POST
16+
*/
17+
var method: RpcMethod = RpcMethod.POST
18+
19+
/**
20+
* The database schema
21+
*/
22+
var schema: String = defaultSchema
23+
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.github.jan.supabase.postgrest.query.request
2+
3+
import io.github.jan.supabase.postgrest.PropertyConversionMethod
4+
import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder
5+
import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder
6+
7+
/**
8+
* Request builder for [PostgrestQueryBuilder.select]
9+
*/
10+
class SelectPostgrestRequestBuilder(propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) {
11+
12+
/**
13+
* If true, no body will be returned. Useful when using count.
14+
*/
15+
var head: Boolean = false
16+
17+
}

0 commit comments

Comments
 (0)