Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Extract optional PostgREST properties and add schema support for RPC. #716

Merged
merged 8 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ sealed interface Postgrest : MainPlugin<Postgrest.Config>, CustomSerializationPl

/**
* Creates a new [PostgrestQueryBuilder] for the given schema and table
* @param schema The schema to use for the requests
* @param table The table to use for the requests
*/
operator fun get(table: String): PostgrestQueryBuilder = from(table)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
package io.github.jan.supabase.postgrest

import io.github.jan.supabase.encodeToJsonElement
import io.github.jan.supabase.exceptions.RestException
import io.github.jan.supabase.postgrest.executor.RestRequestExecutor
import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder
import io.github.jan.supabase.postgrest.query.request.RpcPostgrestRequestBuilder
import io.github.jan.supabase.postgrest.request.RpcRequest
import io.github.jan.supabase.postgrest.result.PostgrestResult
import io.ktor.http.HttpMethod
Expand Down Expand Up @@ -34,29 +36,29 @@ enum class RpcMethod(val httpMethod: HttpMethod) {
*
* @param function The name of the function
* @param parameters The parameters for the function
* @param method The HTTP method to use. Default is POST
* @param request Filter the result
* @throws RestException or one of its subclasses if the request failed
*/
suspend inline fun <reified T : Any> Postgrest.rpc(
function: String,
parameters: T,
method: RpcMethod = RpcMethod.POST,
request: PostgrestRequestBuilder.() -> Unit = {},
): PostgrestResult {
val encodedParameters = if (parameters is JsonElement) parameters else serializer.encodeToJsonElement(parameters)
val requestBuilder = PostgrestRequestBuilder(config.propertyConversionMethod).apply(request)
val requestBuilder = RpcPostgrestRequestBuilder(config.defaultSchema, config.propertyConversionMethod).apply(request)
val urlParams = buildMap {
putAll(requestBuilder.params.mapToFirstValue())
if(method != RpcMethod.POST) {
if(requestBuilder.method != RpcMethod.POST) {
putAll(encodedParameters.jsonObject.mapValues { it.value.toString() })
}
}
val rpcRequest = RpcRequest(
method = method.httpMethod,
method = requestBuilder.method.httpMethod,
count = requestBuilder.count,
urlParams = urlParams,
body = encodedParameters
body = encodedParameters,
schema = requestBuilder.schema,
headers = requestBuilder.headers.build()
)
return RestRequestExecutor.execute(postgrest = this, path = "rpc/$function", request = rpcRequest)
}
Expand All @@ -65,20 +67,20 @@ suspend inline fun <reified T : Any> Postgrest.rpc(
* Executes a database function
*
* @param function The name of the function
* @param method The HTTP method to use. Default is POST
* @param request Filter the result
* @throws RestException or one of its subclasses if the request failed
*/
suspend inline fun Postgrest.rpc(
function: String,
method: RpcMethod = RpcMethod.POST,
request: PostgrestRequestBuilder.() -> Unit = {}
request: RpcPostgrestRequestBuilder.() -> Unit = {}
): PostgrestResult {
val requestBuilder = PostgrestRequestBuilder(config.propertyConversionMethod).apply(request)
val requestBuilder = RpcPostgrestRequestBuilder(config.defaultSchema, config.propertyConversionMethod).apply(request)
val rpcRequest = RpcRequest(
method = method.httpMethod,
method = requestBuilder.method.httpMethod,
count = requestBuilder.count,
urlParams = requestBuilder.params.mapToFirstValue()
urlParams = requestBuilder.params.mapToFirstValue(),
schema = requestBuilder.schema,
headers = requestBuilder.headers.build()
)
return RestRequestExecutor.execute(postgrest = this, path = "rpc/$function", request = rpcRequest)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import io.github.jan.supabase.gotrue.PostgrestFilterDSL
import io.github.jan.supabase.postgrest.Postgrest
import io.github.jan.supabase.postgrest.executor.RestRequestExecutor
import io.github.jan.supabase.postgrest.mapToFirstValue
import io.github.jan.supabase.postgrest.query.request.InsertPostgrestRequestBuilder
import io.github.jan.supabase.postgrest.query.request.SelectPostgrestRequestBuilder
import io.github.jan.supabase.postgrest.query.request.UpsertPostgrestRequestBuilder
import io.github.jan.supabase.postgrest.request.DeleteRequest
import io.github.jan.supabase.postgrest.request.InsertRequest
import io.github.jan.supabase.postgrest.request.SelectRequest
Expand All @@ -30,23 +33,21 @@ class PostgrestQueryBuilder(
* Executes vertical filtering with select on [table]
*
* @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
* @param head If true, no body will be returned. Useful when using count.
* @param request Additional filtering to apply to the query
* @param request Additional configurations for the request including filters
* @return PostgrestResult which is either an error, an empty JsonArray or the data you requested as an JsonArray
* @throws RestException or one of its subclasses if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
suspend inline fun select(
columns: Columns = Columns.ALL,
head: Boolean = false,
request: @PostgrestFilterDSL PostgrestRequestBuilder.() -> Unit = {}
request: @PostgrestFilterDSL SelectPostgrestRequestBuilder.() -> Unit = {}
): PostgrestResult {
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod) {
val requestBuilder = SelectPostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply {
request(); params["select"] = listOf(columns.value)
}
val selectRequest = SelectRequest(
head = head,
head = requestBuilder.head,
count = requestBuilder.count,
urlParams = requestBuilder.params.mapToFirstValue(),
schema = schema,
Expand All @@ -57,38 +58,28 @@ class PostgrestQueryBuilder(

/**
* Perform an UPSERT on the table or view. Depending on the column(s) passed
* to [onConflict], [upsert] allows you to perform the equivalent of
* to [UpsertPostgrestRequestBuilder.onConflict], [upsert] allows you to perform the equivalent of
* `[insert] if a row with the corresponding onConflict columns doesn't
* exist, or if it does exist, perform an alternative action depending on
* [ignoreDuplicates].
* [UpsertPostgrestRequestBuilder.ignoreDuplicates].
*
* By default, upserted rows are not returned. To return it, call `[PostgrestRequestBuilder.select]`.
*
* @param values The values to insert, will automatically get serialized into json.
* @param request Additional filtering to apply to the query
* @param onConflict Comma-separated UNIQUE column(s) to specify how
* duplicate rows are determined. Two rows are duplicates if all the
* `onConflict` columns are equal.
* @param defaultToNull Make missing fields default to `null`.
* Otherwise, use the default value for the column. This only applies when
* inserting new rows, not when merging with existing rows under
* @param ignoreDuplicates If `true`, duplicate rows are ignored. If `false`, duplicate rows are merged with existing rows.
* @param request Additional configurations for the request including filters
* @throws RestException or one of its subclasses if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
suspend inline fun <reified T : Any> upsert(
values: List<T>,
onConflict: String? = null,
defaultToNull: Boolean = true,
ignoreDuplicates: Boolean = false,
request: PostgrestRequestBuilder.() -> Unit = {}
request: UpsertPostgrestRequestBuilder.() -> Unit = {}
): PostgrestResult {
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request)
val requestBuilder = UpsertPostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request)
val body = postgrest.serializer.encodeToJsonElement(values).jsonArray
val columns = body.map { it.jsonObject.keys }.flatten().distinct()
requestBuilder.params["columns"] = listOf(columns.joinToString(","))
onConflict?.let {
requestBuilder.onConflict?.let {
requestBuilder.params["on_conflict"] = listOf(it)
}
val insertRequest = InsertRequest(
Expand All @@ -97,8 +88,8 @@ class PostgrestQueryBuilder(
returning = requestBuilder.returning,
count = requestBuilder.count,
urlParams = requestBuilder.params.mapToFirstValue(),
defaultToNull = defaultToNull,
ignoreDuplicates = ignoreDuplicates,
defaultToNull = requestBuilder.defaultToNull,
ignoreDuplicates = requestBuilder.ignoreDuplicates,
schema = schema,
headers = requestBuilder.headers.build()
)
Expand All @@ -107,52 +98,38 @@ class PostgrestQueryBuilder(

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

/**
* Executes an insert operation on the [table]
*
* @param values The values to insert, will automatically get serialized into json.
* @param request Additional filtering to apply to the query
* @param defaultToNull Make missing fields default to `null`.
* Otherwise, use the default value for the column. This only applies when
* inserting new rows, not when merging with existing rows under
* @throws RestException or one of its subclasses if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
suspend inline fun <reified T : Any> insert(
values: List<T>,
defaultToNull: Boolean = true,
request: PostgrestRequestBuilder.() -> Unit = {}
request: InsertPostgrestRequestBuilder.() -> Unit = {}
): PostgrestResult {
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request)
val requestBuilder = InsertPostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request)
val body = postgrest.serializer.encodeToJsonElement(values).jsonArray
val columns = body.map { it.jsonObject.keys }.flatten().distinct()
requestBuilder.params["columns"] = listOf(columns.joinToString(","))
Expand All @@ -163,7 +140,7 @@ class PostgrestQueryBuilder(
urlParams = requestBuilder.params.mapToFirstValue(),
schema = schema,
headers = requestBuilder.headers.build(),
defaultToNull = defaultToNull
defaultToNull = requestBuilder.defaultToNull
)
return RestRequestExecutor.execute(postgrest = postgrest, path = table, request = insertRequest)
}
Expand All @@ -173,18 +150,14 @@ class PostgrestQueryBuilder(
*
* @param value The value to insert, will automatically get serialized into json.
* @param request Additional filtering to apply to the query
* @param defaultToNull Make missing fields default to `null`.
* Otherwise, use the default value for the column. This only applies when
* inserting new rows, not when merging with existing rows under
* @throws RestException or one of its subclasses if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
suspend inline fun <reified T : Any> insert(
value: T,
defaultToNull: Boolean = true,
request: PostgrestRequestBuilder.() -> Unit = {}
) = insert(listOf(value), defaultToNull, request)
request: InsertPostgrestRequestBuilder.() -> Unit = {}
) = insert(listOf(value), request)

/**
* Executes an update operation on the [table].
Expand All @@ -201,7 +174,7 @@ class PostgrestQueryBuilder(
crossinline update: PostgrestUpdate.() -> Unit = {},
request: PostgrestRequestBuilder.() -> Unit = {}
): PostgrestResult {
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request)
val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request)
val updateRequest = UpdateRequest(
body = buildPostgrestUpdate(postgrest.config.propertyConversionMethod, postgrest.serializer, update),
returning = requestBuilder.returning,
Expand All @@ -228,7 +201,7 @@ class PostgrestQueryBuilder(
value: T,
request: PostgrestRequestBuilder.() -> Unit = {}
): PostgrestResult {
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request)
val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request)
val updateRequest = UpdateRequest(
returning = requestBuilder.returning,
count = requestBuilder.count,
Expand All @@ -253,7 +226,7 @@ class PostgrestQueryBuilder(
suspend inline fun delete(
request: PostgrestRequestBuilder.() -> Unit = {}
): PostgrestResult {
val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request)
val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request)
val deleteRequest = DeleteRequest(
returning = requestBuilder.returning,
count = requestBuilder.count,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import kotlin.js.JsName
* A builder for Postgrest requests.
*/
@PostgrestFilterDSL
class PostgrestRequestBuilder(@PublishedApi internal val propertyConversionMethod: PropertyConversionMethod) {
open class PostgrestRequestBuilder(@PublishedApi internal val propertyConversionMethod: PropertyConversionMethod) {

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

}

@SupabaseInternal
inline fun postgrestRequest(propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, block: PostgrestRequestBuilder.() -> Unit): PostgrestRequestBuilder {
val filter = PostgrestRequestBuilder(propertyConversionMethod)
filter.block()
return filter
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.github.jan.supabase.postgrest.query.request

import io.github.jan.supabase.postgrest.PropertyConversionMethod
import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder
import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder

/**
* Request builder for [PostgrestQueryBuilder.insert]
*/
open class InsertPostgrestRequestBuilder(propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) {

/**
* Make missing fields default to `null`.
* Otherwise, use the default value for the column. This only applies when
* inserting new rows, not when merging with existing rows under
*/
var defaultToNull: Boolean = true

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.jan.supabase.postgrest.query.request

import io.github.jan.supabase.postgrest.Postgrest
import io.github.jan.supabase.postgrest.PropertyConversionMethod
import io.github.jan.supabase.postgrest.RpcMethod
import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder
import io.github.jan.supabase.postgrest.rpc

/**
* Request builder for [Postgrest.rpc]
*/
class RpcPostgrestRequestBuilder(defaultSchema: String, propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) {

/**
* The HTTP method to use. Default is POST
*/
var method: RpcMethod = RpcMethod.POST

/**
* The database schema
*/
var schema: String = defaultSchema

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.github.jan.supabase.postgrest.query.request

import io.github.jan.supabase.postgrest.PropertyConversionMethod
import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder
import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder

/**
* Request builder for [PostgrestQueryBuilder.select]
*/
class SelectPostgrestRequestBuilder(propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) {

/**
* If true, no body will be returned. Useful when using count.
*/
var head: Boolean = false

}
Loading
Loading