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

Introduce new experimental DSL for Postgrest Columns #761

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.jan.supabase.postgrest.query

import io.github.jan.supabase.annotations.SupabaseExperimental
import io.github.jan.supabase.postgrest.classPropertyNames
import kotlin.jvm.JvmInline

Expand All @@ -17,6 +18,15 @@ value class Columns @PublishedApi internal constructor(val value: String) {
*/
val ALL = Columns("*")

/**
* Select all columns given in the [builder] parameter
* @param builder The columns to select
*/
@SupabaseExperimental
inline operator fun invoke(builder: BasicColumnsBuilder.() -> Unit): Columns {
return Columns(BasicColumnsBuilder().apply(builder).build())
}

/**
* Select all columns given in the [value] parameter
* @param value The columns to select, separated by a comma
Expand All @@ -41,21 +51,6 @@ value class Columns @PublishedApi internal constructor(val value: String) {
*/
inline fun <reified T> type() = list(classPropertyNames<T>())

private fun String.clean(): String {
var quoted = false
val regex = Regex("\\s")
return this.map {
if (it == '"') {
quoted = !quoted
}
if (regex.matches(it.toString()) && !quoted) {
""
} else {
it
}
}.joinToString("")
}

}

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

internal object Aggregates {
const val AVG = "avg()"
const val COUNT = "count()"
const val MAX = "max()"
const val MIN = "min()"
const val SUM = "sum()"
}

/**
* Type-safe builder for selecting columns
*/
open class BasicColumnsBuilder {

internal val columns: MutableList<String> = mutableListOf<String>()

/**
* Selects the given [columns].
* - To rename a column, use the [withAlias] infix function.
* - To use a function on a column, use the [withFunction] infix function.
* - To specify a type for a column/cast a column value, use the [withType] infix function.
*
* Example:
* ```kotlin
* named("name" withAlias "my_name", "id" withType "text") // name AS my_name, id::text
* ```
* @param columns The columns to select
*/
fun named(vararg columns: String) {
this.columns.addAll(columns)
}

/**
* Selects all/the remaining columns
*/
fun all() {
columns.add("*")
}

/**
* Selects a JSON column
*
* For example to select the key `key` from the JSON column in `json_data`:
*
* ```json
* {
* "key": "value",
* "array": [{
* "key": "value"
* }]
* }
* ```
*
* ```kotlin
* json("json_data", "array", "0", "key", returnAsText = true) // jsonData->array->0->>key
* ```
*
* @param column The column to select
* @param path The path to the JSON key
* @param returnAsText Whether to return the JSON key as text
*/
fun json(column: String, vararg path: String, returnAsText: Boolean = false) {
val operator = if(returnAsText) "->>" else "->"
val formattedPath = if(path.size > 1) path.dropLast(1).joinToString("->", prefix = "->") else ""
val key = path.last()
columns.add("$column$formattedPath$operator$key")
}

/**
* Selects a foreign column
* @param name The name of the foreign column or the table name
* @param columnsBuilder The columns to select from the foreign column
*/
fun foreign(name: String, columnsBuilder: ForeignColumnsBuilder.() -> Unit = {}) {
val foreignColumns = ForeignColumnsBuilder().apply(columnsBuilder)
val spread = if(foreignColumns.spread) "..." else ""
val key = if(foreignColumns.key != null) "!${foreignColumns.key}" else ""
columns.add("$spread$name$key(${foreignColumns.build()})")
}

/**
* Renames a column to the given [alias]
* @param alias The alias to rename the column to
*/
infix fun String.withAlias(alias: String) = "$alias:$this"

/**
* Applies a function to the column
* @param name The name of the function
*/
infix fun String.withFunction(name: String) = "$this.$name"

/**
* Casts a column to the given [type]
* @param type The type to cast the column to
*/
infix fun String.withType(type: String) = "$this::$type"
Comment on lines +92 to +98
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the names here, maybe applyFunction or useFunction and castAs would be better


/**
* Applies the `avg()` function to the column
*/
fun avg() = Aggregates.AVG

/**
* Applies the `count()` function to the column
*/
fun count() = Aggregates.COUNT

/**
* Applies the `max()` function to the column
*/
fun max() = Aggregates.MAX

/**
* Applies the `min()` function to the column
*/
fun min() = Aggregates.MIN

/**
* Applies the `sum()` function to the column
*/
fun sum() = Aggregates.SUM

@PublishedApi
internal fun build() = columns.joinToString(",")

}

/**
* Type-safe builder for selecting columns
*/
class ForeignColumnsBuilder: BasicColumnsBuilder() {

/**
* Whether to spread the foreign columns in the response
*/
var spread = false

/**
* The key to use for the foreign column when having multiple foreign columns
*/
var key: String? = null

}

internal fun String.clean(): String {
var quoted = false
val regex = Regex("\\s")
return this.map {
if (it == '"') {
quoted = !quoted
}
if (regex.matches(it.toString()) && !quoted) {
""
} else {
it
}
}.joinToString("")
}