From b86a3d262dc600bb78fcdcd085f7e47e06854706 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Fri, 18 Oct 2024 14:30:52 +0200 Subject: [PATCH 1/3] Introduce new experimental DSL for Postgrest Columns --- .../jan/supabase/postgrest/query/Columns.kt | 24 +++---- .../postgrest/query/ColumnsBuilder.kt | 67 +++++++++++++++++++ 2 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Columns.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Columns.kt index 4b7564c35..213de54b0 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Columns.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Columns.kt @@ -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 @@ -17,6 +18,14 @@ value class Columns @PublishedApi internal constructor(val value: String) { */ val ALL = Columns("*") + /** + * TODO + */ + @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 @@ -41,21 +50,6 @@ value class Columns @PublishedApi internal constructor(val value: String) { */ inline fun type() = list(classPropertyNames()) - 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("") - } - } } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt new file mode 100644 index 000000000..c8a1d56f3 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt @@ -0,0 +1,67 @@ +package io.github.jan.supabase.postgrest.query + +open class BasicColumnsBuilder { + + internal val columns: MutableList = mutableListOf() + + fun named(vararg columns: String) { + this.columns.addAll(columns) + } + + fun all() { + columns.add("*") + } + + fun json(column: String, key: String, returnAsText: Boolean = false) { + val operator = if(returnAsText) "->>" else "->" + columns.add("$column$operator$key") + } + + 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()})") + } + + infix fun String.withAlias(alias: String) = "$alias:$this" + + infix fun String.withFunction(name: String) = "$this.$name" + + infix fun String.withType(type: String) = "$this::$type" + + fun avg() = "avg()" + + fun count() = "count()" + + fun max() = "max()" + + fun min() = "min()" + + fun sum() = "sum()" + + fun build() = columns.joinToString(",").also(::println) + +} + +class ForeignColumnsBuilder(): BasicColumnsBuilder() { + + var spread = false + 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("") +} \ No newline at end of file From b1002efd663693cd80338fc6548fab53fdeff51a Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Fri, 25 Oct 2024 14:42:15 +0200 Subject: [PATCH 2/3] Add docs --- .../jan/supabase/postgrest/query/Columns.kt | 3 +- .../postgrest/query/ColumnsBuilder.kt | 93 ++++++++++++++++++- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Columns.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Columns.kt index 213de54b0..c04175b2d 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Columns.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Columns.kt @@ -19,7 +19,8 @@ value class Columns @PublishedApi internal constructor(val value: String) { val ALL = Columns("*") /** - * TODO + * Select all columns given in the [builder] parameter + * @param builder The columns to select */ @SupabaseExperimental inline operator fun invoke(builder: BasicColumnsBuilder.() -> Unit): Columns { diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt index c8a1d56f3..2c2990c4e 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt @@ -1,22 +1,69 @@ package io.github.jan.supabase.postgrest.query +/** + * Type-safe builder for selecting columns + */ open class BasicColumnsBuilder { internal val columns: MutableList = mutableListOf() + /** + * 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("*") } - fun json(column: String, key: String, returnAsText: Boolean = false) { + /** + * 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 "->" - columns.add("$column$operator$key") + 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 "" @@ -24,29 +71,67 @@ open class BasicColumnsBuilder { 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" + /** + * Applies the `avg()` function to the column + */ fun avg() = "avg()" + /** + * Applies the `count()` function to the column + */ fun count() = "count()" + /** + * Applies the `max()` function to the column + */ fun max() = "max()" + /** + * Applies the `min()` function to the column + */ fun min() = "min()" + /** + * Applies the `sum()` function to the column + */ fun sum() = "sum()" - fun build() = columns.joinToString(",").also(::println) + @PublishedApi + internal fun build() = columns.joinToString(",").also(::println) } -class ForeignColumnsBuilder(): BasicColumnsBuilder() { +/** + * 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 } From 93e0e8bdee0d8dcfe04f9bb67a6a5e7dc1148dd3 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Fri, 25 Oct 2024 14:56:55 +0200 Subject: [PATCH 3/3] Use constants --- .../postgrest/query/ColumnsBuilder.kt | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt index 2c2990c4e..9e5cde47c 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt @@ -1,5 +1,13 @@ 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 */ @@ -92,30 +100,30 @@ open class BasicColumnsBuilder { /** * Applies the `avg()` function to the column */ - fun avg() = "avg()" + fun avg() = Aggregates.AVG /** * Applies the `count()` function to the column */ - fun count() = "count()" + fun count() = Aggregates.COUNT /** * Applies the `max()` function to the column */ - fun max() = "max()" + fun max() = Aggregates.MAX /** * Applies the `min()` function to the column */ - fun min() = "min()" + fun min() = Aggregates.MIN /** * Applies the `sum()` function to the column */ - fun sum() = "sum()" + fun sum() = Aggregates.SUM @PublishedApi - internal fun build() = columns.joinToString(",").also(::println) + internal fun build() = columns.joinToString(",") }