From 0cc6ac24ed83028295ba4d4309b5f86905cd3501 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Sat, 26 Oct 2024 21:04:35 +0200 Subject: [PATCH 01/19] Initial commit --- KSP/build.gradle.kts | 14 ++ .../jan/supabase/ksp/PrimitiveColumnType.kt | 16 ++ .../supabase/ksp/SelectableSymbolProcessor.kt | 196 ++++++++++++++++++ .../ksp/SelectableSymbolProcessorProvider.kt | 11 + .../io/github/jan/supabase/ksp/Utils.kt | 39 ++++ ...ols.ksp.processing.SymbolProcessorProvider | 1 + .../postgrest/annotations/ApplyFunction.kt | 15 ++ .../supabase/postgrest/annotations/Cast.kt | 15 ++ .../postgrest/annotations/ColumnName.kt | 17 ++ .../supabase/postgrest/annotations/Foreign.kt | 10 + .../postgrest/annotations/JsonPath.kt | 31 +++ .../postgrest/annotations/Selectable.kt | 43 ++++ gradle/libs.versions.toml | 6 + settings.gradle.kts | 2 + 14 files changed, 416 insertions(+) create mode 100644 KSP/build.gradle.kts create mode 100644 KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt create mode 100644 KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt create mode 100644 KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessorProvider.kt create mode 100644 KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt create mode 100644 KSP/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider create mode 100644 Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt create mode 100644 Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt create mode 100644 Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ColumnName.kt create mode 100644 Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Foreign.kt create mode 100644 Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt create mode 100644 Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt diff --git a/KSP/build.gradle.kts b/KSP/build.gradle.kts new file mode 100644 index 000000000..cc296ced1 --- /dev/null +++ b/KSP/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id(libs.plugins.kotlin.jvm.get().pluginId) +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(libs.ksp) + implementation(libs.kotlin.poet) + implementation(libs.kotlin.poet.ksp) + implementation(project(":postgrest-kt")) +} \ No newline at end of file diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt new file mode 100644 index 000000000..0c56baaab --- /dev/null +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt @@ -0,0 +1,16 @@ +package io.github.jan.supabase.ksp + +val primitiveColumnTypes = mapOf( + "kotlin.String" to "text", + "kotlin.Int" to "int", + "kotlin.Long" to "int8", + "kotlin.Float" to "float4", + "kotlin.Double" to "float8", + "kotlin.Boolean" to "bool", + "kotlin.Byte" to "int2", + "kotlin.Short" to "int2", + "kotlin.Char" to "char", + //TIMESTAMPS etc. +) + +fun isPrimitive(qualifiedName: String) = primitiveColumnTypes.containsKey(qualifiedName) \ No newline at end of file diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt new file mode 100644 index 000000000..7d72f890c --- /dev/null +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -0,0 +1,196 @@ +package io.github.jan.supabase.ksp + +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSValueParameter +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.ksp.toClassName +import io.github.jan.supabase.postgrest.annotations.ApplyFunction +import io.github.jan.supabase.postgrest.annotations.Cast +import io.github.jan.supabase.postgrest.annotations.ColumnName +import io.github.jan.supabase.postgrest.annotations.Foreign +import io.github.jan.supabase.postgrest.annotations.JsonPath +import io.github.jan.supabase.postgrest.annotations.Selectable +import io.github.jan.supabase.postgrest.query.Columns + +class SelectableSymbolProcessor( + private val codeGenerator: CodeGenerator, + private val logger: KSPLogger, + private val options: Map +) : SymbolProcessor { + override fun process(resolver: Resolver): List { + val symbols = resolver.getSymbolsWithAnnotation(Selectable::class.java.name).filterIsInstance() + if (!symbols.iterator().hasNext()) return emptyList() + symbols.forEach { symbol -> + val className = symbol.simpleName.asString() + val packageName = symbol.containingFile?.packageName?.asString().orEmpty() + if (!symbol.modifiers.containsIgnoreCase("data")) { + logger.error("This object is not a data class", symbol) + return emptyList() + } + val companionObject = symbol.anyCompanionObject() + if(companionObject == null) { + logger.error("Companion object not found", symbol) + return emptyList() + } + val parameters = symbol.primaryConstructor?.parameters + if(parameters == null) { + logger.error("Primary constructor is null or has no parameter", symbol) + return emptyList() + } + val columns = parameters.map { processParameters(it, resolver) }.joinToString(",") + writeColumnsExtensionProperty(symbol, companionObject, columns, "${className}Columns", packageName) + } + return emptyList() + } + + private fun writeColumnsExtensionProperty( + symbol: KSClassDeclaration, + companionObject: KSClassDeclaration, + columns: String, + className: String, + packageName: String + ) { + val fileSpec = FileSpec.builder(packageName, className) + .addImport("io.github.jan.supabase.postgrest.query", "Columns") + .addProperty(PropertySpec.builder("columns", Columns::class) + .receiver(companionObject.toClassName()) + .getter(FunSpec.getterBuilder().addStatement("return Columns.raw(\"$columns\")").build()) + .build() + ) + .build() + codeGenerator.createNewFile( + Dependencies(false, symbol.containingFile!!), + packageName, + className, + extensionName = "kt" + ).bufferedWriter().use { + fileSpec.writeTo(it) + } + } + + private fun processParameters(parameter: KSValueParameter, resolver: Resolver): String { + val parameterClass = resolver.getClassDeclarationByName(parameter.type.resolve().declaration.qualifiedName!!) + val innerColumns = if(!isPrimitive(parameterClass!!.qualifiedName!!.asString())) { + val annotation = parameterClass.annotations.getAnnotationOrNull(Selectable::class.java.simpleName) + if(annotation == null) { + logger.error("Type of parameter ${parameter.name!!.getShortName()} is not a primitive type and does not have @Selectable annotation", parameter) + return "" + } else { + val columns = parameterClass.primaryConstructor?.parameters + if(columns == null) { + logger.error("Primary constructor of ${parameterClass.qualifiedName} is null or has no parameter", parameter) + return "" + } + columns.map { processParameters(it, resolver) }.joinToString(",") + } + } else "" + val alias = parameter.name!!.getShortName() + val columnName = parameter.annotations.getAnnotationOrNull(ColumnName::class.java.simpleName) + ?.arguments?.getParameterValue(ColumnName.NAME_PARAMETER_NAME) ?: alias + val isForeign = parameter.annotations.getAnnotationOrNull(Foreign::class.java.simpleName) != null + val jsonPathArguments = parameter.annotations.getAnnotationOrNull(JsonPath::class.java.simpleName)?.arguments + val jsonPath = jsonPathArguments?.getParameterValue>(JsonPath.PATH_PARAMETER_NAME) + val returnAsText = jsonPathArguments?.getParameterValue(JsonPath.RETURN_AS_TEXT_PARAMETER_NAME) == true + val function = parameter.annotations.getAnnotationOrNull(ApplyFunction::class.java.simpleName) + ?.arguments?.getParameterValue(ApplyFunction.FUNCTION_PARAMETER_NAME) + val cast = parameter.annotations.getAnnotationOrNull(Cast::class.java.simpleName) + ?.arguments?.getParameterValue(Cast.TYPE_PARAMETER_NAME) + checkValidCombinations( + parameterName = parameter.name!!.asString(), + isForeign = isForeign, + jsonPath = jsonPath, + function = function + ) + return buildColumns( + alias = alias, + columnName = columnName, + isForeign = isForeign, + jsonPath = jsonPath, + returnAsText = returnAsText, + function = function, + cast = cast, + innerColumns = innerColumns, + parameterName = parameter.name!!.asString(), + qualifiedTypeName = parameterClass.qualifiedName!!.asString() + ) + } + + private fun checkValidCombinations( + parameterName: String, + isForeign: Boolean, + jsonPath: List?, + function: String? + ) { + require(!isForeign || jsonPath == null) { + "Parameter $parameterName can't have both @Foreign and @JsonPath annotation" + } + require(!isForeign || function == null) { + "Parameter $parameterName can't have both @Foreign and @ApplyFunction annotation" + } + require(jsonPath == null || function == null) { + "Parameter $parameterName can't have both @JsonPath and @ApplyFunction annotation" + } + } + + private fun buildColumns( + alias: String, + columnName: String, + isForeign: Boolean, + jsonPath: List?, + returnAsText: Boolean, + function: String?, + cast: String?, + innerColumns: String, + parameterName: String, + qualifiedTypeName: String + ): String { + return buildString { + if(jsonPath != null) { + append(buildJsonPath(jsonPath, returnAsText)) + return@buildString + } + + //If the alias is the same as the column name, we can just assume parameter name (alias) is the column name + if(alias == columnName) { + append(alias) + } else { + append("$alias:$columnName") + } + if(isForeign) { + append("($innerColumns)") + return@buildString + } + + //If a custom cast is provided, use it + if(cast != null && cast.isNotEmpty()) { + append("::$cast") + } else if(cast != null) { //If cast is empty, try to auto-cast + val autoCast = primitiveColumnTypes[qualifiedTypeName] + if(autoCast != null) { + append("::$autoCast") + } else { + logger.error("Type of parameter $parameterName is not a primitive type and does not have an automatic cast type") + } + } + if(function != null) { + append(".$function()") + } + } + } + + private fun buildJsonPath(jsonPath: List, returnAsText: Boolean): String { + val operator = if(returnAsText) "->>" else "->" + val formattedPath = if(jsonPath.size > 1) jsonPath.dropLast(1).joinToString("->") else "" + val key = jsonPath.last() + return "$formattedPath$operator$key" + } + +} \ No newline at end of file diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessorProvider.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessorProvider.kt new file mode 100644 index 000000000..08f432e65 --- /dev/null +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessorProvider.kt @@ -0,0 +1,11 @@ +package io.github.jan.supabase.ksp + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class SelectableSymbolProcessorProvider: SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return SelectableSymbolProcessor(environment.codeGenerator, environment.logger, environment.options) + } +} \ No newline at end of file diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt new file mode 100644 index 000000000..6196264f5 --- /dev/null +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt @@ -0,0 +1,39 @@ +package io.github.jan.supabase.ksp + +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSValueArgument +import com.google.devtools.ksp.symbol.Modifier + +fun Sequence.getAnnotation(target: String): KSAnnotation { + return getAnnotationOrNull(target) ?: + throw NoSuchElementException("Sequence contains no element matching the predicate.") +} + +fun KSClassDeclaration.anyCompanionObject(): KSClassDeclaration? { + return declarations.filterIsInstance().firstOrNull { it.isCompanionObject } +} + +fun Sequence.getAnnotationOrNull(target: String): KSAnnotation? { + for (element in this) if (element.shortName.asString() == target) return element + return null +} + +fun Sequence.hasAnnotation(target: String): Boolean { + for (element in this) if (element.shortName.asString() == target) return true + return false +} + +fun List.getParameterValue(target: String): T { + return getParameterValueIfExist(target) ?: + throw NoSuchElementException("Sequence contains no element matching the predicate.") +} + +fun List.getParameterValueIfExist(target: String): T? { + for (element in this) if (element.name?.asString() == target) (element.value as? T)?.let { return it } + return null +} + +fun Collection.containsIgnoreCase(name: String): Boolean { + return stream().anyMatch { it.name.equals(name, true) } +} \ No newline at end of file diff --git a/KSP/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/KSP/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 000000000..ac0dfdc8b --- /dev/null +++ b/KSP/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +io.github.jan.supabase.ksp.SelectableSymbolProcessorProvider \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt new file mode 100644 index 000000000..b66b1b5a2 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt @@ -0,0 +1,15 @@ +package io.github.jan.supabase.postgrest.annotations + +/** + * Annotation to apply a function to a column in a PostgREST query. + * @param function The function to apply to the column. + */ +@Target(AnnotationTarget.VALUE_PARAMETER) +@Retention(AnnotationRetention.SOURCE) +annotation class ApplyFunction(val function: String) { + + companion object { + const val FUNCTION_PARAMETER_NAME = "function" + } + +} diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt new file mode 100644 index 000000000..0881b4e4f --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt @@ -0,0 +1,15 @@ +package io.github.jan.supabase.postgrest.annotations + +/** + * Annotation to cast a column in a PostgREST query. + * @param type The type to cast the column to. If empty, the type will be inferred from the parameter type. For example, if the parameter is of type `String`, the column will be cast to `text`. + */ +@Target(AnnotationTarget.VALUE_PARAMETER) +@Retention(AnnotationRetention.SOURCE) +annotation class Cast(val type: String = "") { + + companion object { + const val TYPE_PARAMETER_NAME = "type" + } + +} diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ColumnName.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ColumnName.kt new file mode 100644 index 000000000..9bfa608e0 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ColumnName.kt @@ -0,0 +1,17 @@ +package io.github.jan.supabase.postgrest.annotations + +/** + * Annotation to specify the name of a column in a PostgREST query. + * + * If this annotation is not present, the name of the column will be inferred from the parameter name. + * @param name The name of the column. + */ +@Target(AnnotationTarget.VALUE_PARAMETER) +@Retention(AnnotationRetention.SOURCE) +annotation class ColumnName(val name: String) { + + companion object { + const val NAME_PARAMETER_NAME = "name" + } + +} diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Foreign.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Foreign.kt new file mode 100644 index 000000000..5edc317a5 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Foreign.kt @@ -0,0 +1,10 @@ +package io.github.jan.supabase.postgrest.annotations + +/** + * Annotation to specify that a column is a foreign key in a PostgREST query. + * + * This annotation may be used in combination with [ColumnName] to specify the name of the foreign key column. The type of the parameter must be marked with [Selectable]. + */ +@Target(AnnotationTarget.VALUE_PARAMETER) +@Retention(AnnotationRetention.SOURCE) +annotation class Foreign diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt new file mode 100644 index 000000000..b203f3f13 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt @@ -0,0 +1,31 @@ +package io.github.jan.supabase.postgrest.annotations + +/** + * Annotation to specify a JSON path in a PostgREST query. + * + * Example: + * Table with a JSON column `data`: + * ```json + * { + * "id": 1 + * } + * ``` + * ```kotlin + * @Selectable + * data class Example(@JsonPath("data", "id") val id: Int) + * ``` + * + * @param path The path to the JSON property. + * @param returnAsText Whether to return the JSON property as text. If `true`, the JSON property will be returned as a string. If `false`, the JSON property will be returned as JSON. + * + */ +@Target(AnnotationTarget.VALUE_PARAMETER) +@Retention(AnnotationRetention.SOURCE) +annotation class JsonPath(vararg val path: String, val returnAsText: Boolean = false) { + + companion object { + const val PATH_PARAMETER_NAME = "path" + const val RETURN_AS_TEXT_PARAMETER_NAME = "returnAsText" + } + +} diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt new file mode 100644 index 000000000..96f29f8c3 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt @@ -0,0 +1,43 @@ +package io.github.jan.supabase.postgrest.annotations + +import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder + +/** + * Marks a class as selectable. + * + * When using the **ksp-compiler** this annotation will be processed and columns for [PostgrestQueryBuilder.select] will be generated. + * + * The columns can be accessed via the generated extension property for the companion object of the class. + * + * **Note:** + * - All classes marked with this annotation must have a companion object, which can be empty, and must be a data class + * - All parameters in the primary constructor must a primitive type`*` or a type that is also marked with [Selectable] + * - Parameters may be marked with [ColumnName], [ApplyFunction], [Cast], [JsonPath], [Foreign]. + * + * `*` Available primitive types are: String, Int, Long, Float, Double, Boolean + * + * Example usage: + * ```kotlin + * @Selectable + * data class User( + * val id: Int, + * val name: String, + * val age: Int + * //... + * ) { + * companion object + * } + * + * //Usage + * val users: List = supabase.from("users").select(User.columns).decodeList() + * ``` + * @see ApplyFunction + * @see Cast + * @see ColumnName + * @see Foreign + * @see JsonPath + * @see PostgrestQueryBuilder.select + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +annotation class Selectable diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 757a1e760..cbdd07a25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -35,9 +35,12 @@ androidx-lifecycle = "2.9.1" filekit = "0.8.8" kotlinx-browser = "0.3" secure-random = "0.5.1" +ksp = "2.0.21-1.0.25" +kotlin-poet = "2.0.0" [plugins] kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } kotlinx-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } @@ -54,6 +57,9 @@ kotlinx-atomicfu = { id = "org.jetbrains.kotlinx.atomicfu", version.ref = "atomi power-assert = { id = "org.jetbrains.kotlin.plugin.power-assert", version.ref = "kotlin" } [libraries] +ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } +kotlin-poet = { module = "com.squareup:kotlinpoet", version.ref = "kotlin-poet" } +kotlin-poet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" } kotlinx-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "atomicfu" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version.ref = "kotlinx-browser" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 9b86223b1..e5ce392aa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,6 +14,7 @@ plugins { // Main Modules include("Auth") include("Postgrest") +include("KSP") include("Storage") include("Realtime") include("Functions") @@ -51,6 +52,7 @@ project(":Storage").name = "storage-kt" project(":Realtime").name = "realtime-kt" project(":Functions").name = "functions-kt" project(":Supabase").name = "supabase-kt" +project(":KSP").name = "ksp-compiler" project(":plugins:ApolloGraphQL").name = "apollo-graphql" project(":plugins:ComposeAuth").name = "compose-auth" project(":plugins:ComposeAuthUI").name = "compose-auth-ui" From 0615226db2d7a899082ebf926750cff1493abc50 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Sat, 26 Oct 2024 21:18:11 +0200 Subject: [PATCH 02/19] Small improvements --- .../github/jan/supabase/ksp/ColumnOptions.kt | 12 +++ .../supabase/ksp/SelectableSymbolProcessor.kt | 78 +++++++++---------- .../postgrest/annotations/JsonPath.kt | 2 +- 3 files changed, 52 insertions(+), 40 deletions(-) create mode 100644 KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt new file mode 100644 index 000000000..c3c83648a --- /dev/null +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt @@ -0,0 +1,12 @@ +package io.github.jan.supabase.ksp + +data class ColumnOptions( + val alias: String, + val columnName: String, + val isForeign: Boolean, + val jsonPath: List?, + val returnAsText: Boolean, + val function: String?, + val cast: String?, + val innerColumns: String, +) diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt index 7d72f890c..213f066f7 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -7,6 +7,7 @@ import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSNode import com.google.devtools.ksp.symbol.KSValueParameter import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec @@ -103,13 +104,7 @@ class SelectableSymbolProcessor( ?.arguments?.getParameterValue(ApplyFunction.FUNCTION_PARAMETER_NAME) val cast = parameter.annotations.getAnnotationOrNull(Cast::class.java.simpleName) ?.arguments?.getParameterValue(Cast.TYPE_PARAMETER_NAME) - checkValidCombinations( - parameterName = parameter.name!!.asString(), - isForeign = isForeign, - jsonPath = jsonPath, - function = function - ) - return buildColumns( + val options = ColumnOptions( alias = alias, columnName = columnName, isForeign = isForeign, @@ -117,71 +112,76 @@ class SelectableSymbolProcessor( returnAsText = returnAsText, function = function, cast = cast, - innerColumns = innerColumns, + innerColumns = innerColumns + ) + checkValidCombinations( + parameterName = parameter.name!!.asString(), + options = options, + symbol = parameter + ) + return buildColumns( + options = options, parameterName = parameter.name!!.asString(), + symbol = parameter, qualifiedTypeName = parameterClass.qualifiedName!!.asString() ) } private fun checkValidCombinations( + options: ColumnOptions, parameterName: String, - isForeign: Boolean, - jsonPath: List?, - function: String? + symbol: KSNode ) { - require(!isForeign || jsonPath == null) { - "Parameter $parameterName can't have both @Foreign and @JsonPath annotation" + if(options.isForeign && options.jsonPath != null) { + logger.error("Parameter $parameterName can't have both @Foreign and @JsonPath annotation", symbol) + } + if(options.isForeign && options.function != null) { + logger.error("Parameter $parameterName can't have both @Foreign and @ApplyFunction annotation", symbol) } - require(!isForeign || function == null) { - "Parameter $parameterName can't have both @Foreign and @ApplyFunction annotation" + if(options.jsonPath != null && options.function != null) { + logger.error("Parameter $parameterName can't have both @JsonPath and @ApplyFunction annotation", symbol) } - require(jsonPath == null || function == null) { - "Parameter $parameterName can't have both @JsonPath and @ApplyFunction annotation" + if(options.jsonPath != null && options.jsonPath.isEmpty()) { + logger.error("Parameter $parameterName can't have an empty @JsonPath annotation. At least two elements (the column name and a key) are required.", symbol) } } private fun buildColumns( - alias: String, - columnName: String, - isForeign: Boolean, - jsonPath: List?, - returnAsText: Boolean, - function: String?, - cast: String?, - innerColumns: String, + options: ColumnOptions, parameterName: String, - qualifiedTypeName: String + qualifiedTypeName: String, + symbol: KSNode ): String { return buildString { - if(jsonPath != null) { - append(buildJsonPath(jsonPath, returnAsText)) + if(options.jsonPath != null) { + append(buildJsonPath(options.jsonPath, options.returnAsText)) return@buildString } //If the alias is the same as the column name, we can just assume parameter name (alias) is the column name - if(alias == columnName) { - append(alias) + if(options.alias == options.columnName) { + append(options.alias) } else { - append("$alias:$columnName") + append("${options.alias}:${options.columnName}") } - if(isForeign) { - append("($innerColumns)") + if(options.isForeign) { + append("(${options.innerColumns})") return@buildString } //If a custom cast is provided, use it - if(cast != null && cast.isNotEmpty()) { - append("::$cast") - } else if(cast != null) { //If cast is empty, try to auto-cast + if(options.cast != null && options.cast.isNotEmpty()) { + append("::${options.cast}") + } else if(options.cast != null) { //If cast is empty, try to auto-cast val autoCast = primitiveColumnTypes[qualifiedTypeName] if(autoCast != null) { append("::$autoCast") } else { - logger.error("Type of parameter $parameterName is not a primitive type and does not have an automatic cast type") + logger.error("Type of parameter $parameterName is not a primitive type and does not have an automatic cast type. Try to specify it manually.", symbol) } } - if(function != null) { - append(".$function()") + if(options.function != null) { + append(".${options.function}()") } } } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt index b203f3f13..e4c20dfef 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt @@ -15,7 +15,7 @@ package io.github.jan.supabase.postgrest.annotations * data class Example(@JsonPath("data", "id") val id: Int) * ``` * - * @param path The path to the JSON property. + * @param path The path to the JSON property. The first element of the array is always the column name. * @param returnAsText Whether to return the JSON property as text. If `true`, the JSON property will be returned as a string. If `false`, the JSON property will be returned as JSON. * */ From 2ca488fea19f31423354e85ca40c166e909ee2d2 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Sat, 26 Oct 2024 21:29:23 +0200 Subject: [PATCH 03/19] Improve error checks --- .../jan/supabase/ksp/SelectableSymbolProcessor.kt | 10 ++++++---- .../main/kotlin/io/github/jan/supabase/ksp/Utils.kt | 10 ---------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt index 213f066f7..4bc9630fd 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -9,6 +9,7 @@ import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSNode import com.google.devtools.ksp.symbol.KSValueParameter +import com.google.devtools.ksp.symbol.Modifier import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.PropertySpec @@ -32,8 +33,8 @@ class SelectableSymbolProcessor( symbols.forEach { symbol -> val className = symbol.simpleName.asString() val packageName = symbol.containingFile?.packageName?.asString().orEmpty() - if (!symbol.modifiers.containsIgnoreCase("data")) { - logger.error("This object is not a data class", symbol) + if (!symbol.modifiers.contains(Modifier.DATA)) { + logger.error("The class $className is not a data class", symbol) return emptyList() } val companionObject = symbol.anyCompanionObject() @@ -82,8 +83,9 @@ class SelectableSymbolProcessor( val innerColumns = if(!isPrimitive(parameterClass!!.qualifiedName!!.asString())) { val annotation = parameterClass.annotations.getAnnotationOrNull(Selectable::class.java.simpleName) if(annotation == null) { - logger.error("Type of parameter ${parameter.name!!.getShortName()} is not a primitive type and does not have @Selectable annotation", parameter) - return "" + //Could be a JSON column or a custom type, so don't throw an error + logger.info("Type of parameter ${parameter.name!!.getShortName()} is not a primitive type and does not have @Selectable annotation", parameter) + "" } else { val columns = parameterClass.primaryConstructor?.parameters if(columns == null) { diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt index 6196264f5..b46faf78f 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt @@ -3,7 +3,6 @@ package io.github.jan.supabase.ksp import com.google.devtools.ksp.symbol.KSAnnotation import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSValueArgument -import com.google.devtools.ksp.symbol.Modifier fun Sequence.getAnnotation(target: String): KSAnnotation { return getAnnotationOrNull(target) ?: @@ -19,11 +18,6 @@ fun Sequence.getAnnotationOrNull(target: String): KSAnnotation? { return null } -fun Sequence.hasAnnotation(target: String): Boolean { - for (element in this) if (element.shortName.asString() == target) return true - return false -} - fun List.getParameterValue(target: String): T { return getParameterValueIfExist(target) ?: throw NoSuchElementException("Sequence contains no element matching the predicate.") @@ -32,8 +26,4 @@ fun List.getParameterValue(target: String): T { fun List.getParameterValueIfExist(target: String): T? { for (element in this) if (element.name?.asString() == target) (element.value as? T)?.let { return it } return null -} - -fun Collection.containsIgnoreCase(name: String): Boolean { - return stream().anyMatch { it.name.equals(name, true) } } \ No newline at end of file From be8a91963ec7b70a7177828ece560d4e56265c31 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Sun, 27 Oct 2024 12:56:13 +0100 Subject: [PATCH 04/19] Support for more auto casts --- KSP/build.gradle.kts | 6 ++++++ .../github/jan/supabase/ksp/PrimitiveColumnType.kt | 12 ++++++++++-- .../supabase/postgrest/annotations/ApplyFunction.kt | 9 ++++++++- .../jan/supabase/postgrest/annotations/Cast.kt | 4 +++- .../jan/supabase/postgrest/annotations/ColumnName.kt | 4 +++- .../jan/supabase/postgrest/annotations/Foreign.kt | 2 +- .../jan/supabase/postgrest/annotations/JsonPath.kt | 6 ++++-- .../jan/supabase/postgrest/annotations/Selectable.kt | 10 +++++----- buildSrc/src/main/kotlin/Modules.kt | 1 + 9 files changed, 41 insertions(+), 13 deletions(-) diff --git a/KSP/build.gradle.kts b/KSP/build.gradle.kts index cc296ced1..2e9bb26d4 100644 --- a/KSP/build.gradle.kts +++ b/KSP/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask + plugins { id(libs.plugins.kotlin.jvm.get().pluginId) } @@ -11,4 +13,8 @@ dependencies { implementation(libs.kotlin.poet) implementation(libs.kotlin.poet.ksp) implementation(project(":postgrest-kt")) +} + +tasks.named>("compileKotlin").configure { + compilerOptions.freeCompilerArgs.add("-opt-in=io.github.jan.supabase.annotations.SupabaseInternal") } \ No newline at end of file diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt index 0c56baaab..aaad2249c 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt @@ -2,7 +2,7 @@ package io.github.jan.supabase.ksp val primitiveColumnTypes = mapOf( "kotlin.String" to "text", - "kotlin.Int" to "int", + "kotlin.Int" to "int4", "kotlin.Long" to "int8", "kotlin.Float" to "float4", "kotlin.Double" to "float8", @@ -10,7 +10,15 @@ val primitiveColumnTypes = mapOf( "kotlin.Byte" to "int2", "kotlin.Short" to "int2", "kotlin.Char" to "char", - //TIMESTAMPS etc. + "kotlinx.datetime.Instant" to "timestamptz", + "kotlinx.datetime.LocalDateTime" to "timestamp", + "kotlin.uuid.Uuid" to "uuid", + "kotlinx.datetime.LocalTime" to "time", + "kotlinx.datetime.LocalDate" to "date", + "kotlinx.serialization.json.JsonElement" to "jsonb", + "kotlinx.serialization.json.JsonObject" to "jsonb", + "kotlinx.serialization.json.JsonArray" to "jsonb", + "kotlinx.serialization.json.JsonPrimitive" to "jsonb", ) fun isPrimitive(qualifiedName: String) = primitiveColumnTypes.containsKey(qualifiedName) \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt index b66b1b5a2..3bf709b65 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt @@ -1,5 +1,7 @@ package io.github.jan.supabase.postgrest.annotations +import io.github.jan.supabase.annotations.SupabaseInternal + /** * Annotation to apply a function to a column in a PostgREST query. * @param function The function to apply to the column. @@ -9,7 +11,12 @@ package io.github.jan.supabase.postgrest.annotations annotation class ApplyFunction(val function: String) { companion object { - const val FUNCTION_PARAMETER_NAME = "function" + @SupabaseInternal const val FUNCTION_PARAMETER_NAME = "function" + const val AVG = "avg" + const val COUNT = "count" + const val MAX = "max" + const val MIN = "min" + const val SUM = "sum" } } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt index 0881b4e4f..608f49f69 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt @@ -1,5 +1,7 @@ package io.github.jan.supabase.postgrest.annotations +import io.github.jan.supabase.annotations.SupabaseInternal + /** * Annotation to cast a column in a PostgREST query. * @param type The type to cast the column to. If empty, the type will be inferred from the parameter type. For example, if the parameter is of type `String`, the column will be cast to `text`. @@ -9,7 +11,7 @@ package io.github.jan.supabase.postgrest.annotations annotation class Cast(val type: String = "") { companion object { - const val TYPE_PARAMETER_NAME = "type" + @SupabaseInternal const val TYPE_PARAMETER_NAME = "type" } } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ColumnName.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ColumnName.kt index 9bfa608e0..f6deb7f3e 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ColumnName.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ColumnName.kt @@ -1,5 +1,7 @@ package io.github.jan.supabase.postgrest.annotations +import io.github.jan.supabase.annotations.SupabaseInternal + /** * Annotation to specify the name of a column in a PostgREST query. * @@ -11,7 +13,7 @@ package io.github.jan.supabase.postgrest.annotations annotation class ColumnName(val name: String) { companion object { - const val NAME_PARAMETER_NAME = "name" + @SupabaseInternal const val NAME_PARAMETER_NAME = "name" } } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Foreign.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Foreign.kt index 5edc317a5..0ba826b75 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Foreign.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Foreign.kt @@ -3,7 +3,7 @@ package io.github.jan.supabase.postgrest.annotations /** * Annotation to specify that a column is a foreign key in a PostgREST query. * - * This annotation may be used in combination with [ColumnName] to specify the name of the foreign key column. The type of the parameter must be marked with [Selectable]. + * This annotation may be used in combination with [ColumnName] to specify the name of the foreign key column. The type of the parameter must be annotated with [Selectable]. */ @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.SOURCE) diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt index e4c20dfef..0e18f09d8 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt @@ -1,5 +1,7 @@ package io.github.jan.supabase.postgrest.annotations +import io.github.jan.supabase.annotations.SupabaseInternal + /** * Annotation to specify a JSON path in a PostgREST query. * @@ -24,8 +26,8 @@ package io.github.jan.supabase.postgrest.annotations annotation class JsonPath(vararg val path: String, val returnAsText: Boolean = false) { companion object { - const val PATH_PARAMETER_NAME = "path" - const val RETURN_AS_TEXT_PARAMETER_NAME = "returnAsText" + @SupabaseInternal const val PATH_PARAMETER_NAME = "path" + @SupabaseInternal const val RETURN_AS_TEXT_PARAMETER_NAME = "returnAsText" } } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt index 96f29f8c3..6688cf31c 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt @@ -3,18 +3,18 @@ package io.github.jan.supabase.postgrest.annotations import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder /** - * Marks a class as selectable. + * Annotates a class as selectable. * * When using the **ksp-compiler** this annotation will be processed and columns for [PostgrestQueryBuilder.select] will be generated. * * The columns can be accessed via the generated extension property for the companion object of the class. * * **Note:** - * - All classes marked with this annotation must have a companion object, which can be empty, and must be a data class - * - All parameters in the primary constructor must a primitive type`*` or a type that is also marked with [Selectable] - * - Parameters may be marked with [ColumnName], [ApplyFunction], [Cast], [JsonPath], [Foreign]. + * - All classes annotated with this annotation must have a companion object, which can be empty, and must be a data class + * - All parameters in the primary constructor must a primitive type¹, a type that is also annotated with [Selectable] or a serializable type. + * - Parameters may be annotated with [ColumnName], [ApplyFunction], [Cast], [JsonPath], [Foreign]. * - * `*` Available primitive types are: String, Int, Long, Float, Double, Boolean + * ¹: Available primitive types are: String, Int, Long, Float, Double, Boolean, Byte, Short, Char, Instant, LocalDateTime, Uuid, LocalTime, LocalDate, JsonElement, JsonObject, JsonArray, JsonPrimitive * * Example usage: * ```kotlin diff --git a/buildSrc/src/main/kotlin/Modules.kt b/buildSrc/src/main/kotlin/Modules.kt index 4cf5bdc56..82402c7d8 100644 --- a/buildSrc/src/main/kotlin/Modules.kt +++ b/buildSrc/src/main/kotlin/Modules.kt @@ -1,3 +1,4 @@ +import org.gradle.kotlin.dsl.DependencyHandlerScope import org.jetbrains.compose.desktop.DesktopExtension import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler From 93732cd01545a8f19ed0e65e295d7d5c813d64f1 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Sun, 27 Oct 2024 13:09:45 +0100 Subject: [PATCH 05/19] Add alias support for json columns --- KSP/build.gradle.kts | 1 + .../github/jan/supabase/ksp/ColumnOptions.kt | 3 +- .../supabase/ksp/SelectableSymbolProcessor.kt | 37 ++++++++++++++----- .../postgrest/annotations/JsonPath.kt | 12 ++++-- .../postgrest/annotations/Selectable.kt | 2 + 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/KSP/build.gradle.kts b/KSP/build.gradle.kts index 2e9bb26d4..30bc9c2cd 100644 --- a/KSP/build.gradle.kts +++ b/KSP/build.gradle.kts @@ -17,4 +17,5 @@ dependencies { tasks.named>("compileKotlin").configure { compilerOptions.freeCompilerArgs.add("-opt-in=io.github.jan.supabase.annotations.SupabaseInternal") + compilerOptions.freeCompilerArgs.add("-opt-in=io.github.jan.supabase.annotations.SupabaseExperimental") } \ No newline at end of file diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt index c3c83648a..24e8815f5 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt @@ -2,11 +2,12 @@ package io.github.jan.supabase.ksp data class ColumnOptions( val alias: String, - val columnName: String, + val columnName: String?, val isForeign: Boolean, val jsonPath: List?, val returnAsText: Boolean, val function: String?, val cast: String?, val innerColumns: String, + val jsonKey: String? ) diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt index 4bc9630fd..4dcced5be 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -97,10 +97,11 @@ class SelectableSymbolProcessor( } else "" val alias = parameter.name!!.getShortName() val columnName = parameter.annotations.getAnnotationOrNull(ColumnName::class.java.simpleName) - ?.arguments?.getParameterValue(ColumnName.NAME_PARAMETER_NAME) ?: alias + ?.arguments?.getParameterValue(ColumnName.NAME_PARAMETER_NAME) val isForeign = parameter.annotations.getAnnotationOrNull(Foreign::class.java.simpleName) != null val jsonPathArguments = parameter.annotations.getAnnotationOrNull(JsonPath::class.java.simpleName)?.arguments val jsonPath = jsonPathArguments?.getParameterValue>(JsonPath.PATH_PARAMETER_NAME) + val jsonKey = jsonPathArguments?.getParameterValue(JsonPath.KEY_PARAMETER_NAME) val returnAsText = jsonPathArguments?.getParameterValue(JsonPath.RETURN_AS_TEXT_PARAMETER_NAME) == true val function = parameter.annotations.getAnnotationOrNull(ApplyFunction::class.java.simpleName) ?.arguments?.getParameterValue(ApplyFunction.FUNCTION_PARAMETER_NAME) @@ -111,6 +112,7 @@ class SelectableSymbolProcessor( columnName = columnName, isForeign = isForeign, jsonPath = jsonPath, + jsonKey = jsonKey, returnAsText = returnAsText, function = function, cast = cast, @@ -143,8 +145,8 @@ class SelectableSymbolProcessor( if(options.jsonPath != null && options.function != null) { logger.error("Parameter $parameterName can't have both @JsonPath and @ApplyFunction annotation", symbol) } - if(options.jsonPath != null && options.jsonPath.isEmpty()) { - logger.error("Parameter $parameterName can't have an empty @JsonPath annotation. At least two elements (the column name and a key) are required.", symbol) + if(options.jsonKey != null && options.columnName == null) { + logger.error("Parameter $parameterName must have a @ColumnName annotation when using @JsonPath", symbol) } } @@ -156,12 +158,15 @@ class SelectableSymbolProcessor( ): String { return buildString { if(options.jsonPath != null) { - append(buildJsonPath(options.jsonPath, options.returnAsText)) + if(options.alias != options.jsonKey) { + append("${options.alias}:") + } + append(buildJsonPath(options.columnName, options.jsonKey, options.jsonPath, options.returnAsText)) return@buildString } - //If the alias is the same as the column name, we can just assume parameter name (alias) is the column name - if(options.alias == options.columnName) { + //If the column name is not provided, use the alias (parameter name) + if(options.columnName == null) { append(options.alias) } else { append("${options.alias}:${options.columnName}") @@ -188,11 +193,23 @@ class SelectableSymbolProcessor( } } - private fun buildJsonPath(jsonPath: List, returnAsText: Boolean): String { + private fun buildJsonPath( + columnName: String?, + jsonKey: String?, + jsonPath: List, + returnAsText: Boolean + ): String { val operator = if(returnAsText) "->>" else "->" - val formattedPath = if(jsonPath.size > 1) jsonPath.dropLast(1).joinToString("->") else "" - val key = jsonPath.last() - return "$formattedPath$operator$key" + return buildString { + append(columnName) + if(jsonPath.isNotEmpty()) { + jsonPath.forEach { + append("->$it") + } + } + append(operator) + append(jsonKey) + } } } \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt index 0e18f09d8..1918d441f 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt @@ -14,19 +14,25 @@ import io.github.jan.supabase.annotations.SupabaseInternal * ``` * ```kotlin * @Selectable - * data class Example(@JsonPath("data", "id") val id: Int) + * data class Example( + * @ColumnName("data") + * @JsonPath("id") + * val id: Int + * ) * ``` * - * @param path The path to the JSON property. The first element of the array is always the column name. + * @param key The key of the JSON property. + * @param path Additional path to the JSON property. If the JSON property is nested, you can specify the path to the property. * @param returnAsText Whether to return the JSON property as text. If `true`, the JSON property will be returned as a string. If `false`, the JSON property will be returned as JSON. * */ @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.SOURCE) -annotation class JsonPath(vararg val path: String, val returnAsText: Boolean = false) { +annotation class JsonPath(val key: String, vararg val path: String, val returnAsText: Boolean = false) { companion object { @SupabaseInternal const val PATH_PARAMETER_NAME = "path" + @SupabaseInternal const val KEY_PARAMETER_NAME = "key" @SupabaseInternal const val RETURN_AS_TEXT_PARAMETER_NAME = "returnAsText" } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt index 6688cf31c..fb2aadf1e 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt @@ -1,5 +1,6 @@ package io.github.jan.supabase.postgrest.annotations +import io.github.jan.supabase.annotations.SupabaseExperimental import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder /** @@ -40,4 +41,5 @@ import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) +@SupabaseExperimental annotation class Selectable From 1860276995bb4f8de8c7197e6985e42a0590bee5 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Sun, 27 Oct 2024 13:17:47 +0100 Subject: [PATCH 06/19] Revert JsonPath change --- .../github/jan/supabase/ksp/ColumnOptions.kt | 1 - .../supabase/ksp/SelectableSymbolProcessor.kt | 18 +++++++++--------- .../supabase/postgrest/annotations/JsonPath.kt | 16 +++++++++++----- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt index 24e8815f5..1e2b799fa 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/ColumnOptions.kt @@ -9,5 +9,4 @@ data class ColumnOptions( val function: String?, val cast: String?, val innerColumns: String, - val jsonKey: String? ) diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt index 4dcced5be..d96938342 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -101,7 +101,6 @@ class SelectableSymbolProcessor( val isForeign = parameter.annotations.getAnnotationOrNull(Foreign::class.java.simpleName) != null val jsonPathArguments = parameter.annotations.getAnnotationOrNull(JsonPath::class.java.simpleName)?.arguments val jsonPath = jsonPathArguments?.getParameterValue>(JsonPath.PATH_PARAMETER_NAME) - val jsonKey = jsonPathArguments?.getParameterValue(JsonPath.KEY_PARAMETER_NAME) val returnAsText = jsonPathArguments?.getParameterValue(JsonPath.RETURN_AS_TEXT_PARAMETER_NAME) == true val function = parameter.annotations.getAnnotationOrNull(ApplyFunction::class.java.simpleName) ?.arguments?.getParameterValue(ApplyFunction.FUNCTION_PARAMETER_NAME) @@ -112,7 +111,6 @@ class SelectableSymbolProcessor( columnName = columnName, isForeign = isForeign, jsonPath = jsonPath, - jsonKey = jsonKey, returnAsText = returnAsText, function = function, cast = cast, @@ -145,9 +143,12 @@ class SelectableSymbolProcessor( if(options.jsonPath != null && options.function != null) { logger.error("Parameter $parameterName can't have both @JsonPath and @ApplyFunction annotation", symbol) } - if(options.jsonKey != null && options.columnName == null) { + if(options.jsonPath != null && options.columnName == null) { logger.error("Parameter $parameterName must have a @ColumnName annotation when using @JsonPath", symbol) } + if(options.jsonPath != null && options.jsonPath.isEmpty()) { + logger.error("Parameter $parameterName must have at least one path in @JsonPath annotation", symbol) + } } private fun buildColumns( @@ -158,10 +159,10 @@ class SelectableSymbolProcessor( ): String { return buildString { if(options.jsonPath != null) { - if(options.alias != options.jsonKey) { + if(options.alias != options.jsonPath.last()) { append("${options.alias}:") } - append(buildJsonPath(options.columnName, options.jsonKey, options.jsonPath, options.returnAsText)) + append(buildJsonPath(options.columnName, options.jsonPath, options.returnAsText)) return@buildString } @@ -195,20 +196,19 @@ class SelectableSymbolProcessor( private fun buildJsonPath( columnName: String?, - jsonKey: String?, jsonPath: List, returnAsText: Boolean ): String { val operator = if(returnAsText) "->>" else "->" return buildString { append(columnName) - if(jsonPath.isNotEmpty()) { - jsonPath.forEach { + if(jsonPath.size > 1) { + jsonPath.dropLast(1).forEach { append("->$it") } } append(operator) - append(jsonKey) + append(jsonPath.last()) } } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt index 1918d441f..3a075fc7a 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt @@ -9,7 +9,10 @@ import io.github.jan.supabase.annotations.SupabaseInternal * Table with a JSON column `data`: * ```json * { - * "id": 1 + * "id": 1, + * "nested": { + * "name": "John" + * } * } * ``` * ```kotlin @@ -17,18 +20,21 @@ import io.github.jan.supabase.annotations.SupabaseInternal * data class Example( * @ColumnName("data") * @JsonPath("id") - * val id: Int + * val id: Int, + * + * @ColumnName("data") + * @JsonPath("nested", "name") + * val name: String * ) * ``` * - * @param key The key of the JSON property. - * @param path Additional path to the JSON property. If the JSON property is nested, you can specify the path to the property. + * @param path The path to the JSON property. At least one path must be specified. * @param returnAsText Whether to return the JSON property as text. If `true`, the JSON property will be returned as a string. If `false`, the JSON property will be returned as JSON. * */ @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.SOURCE) -annotation class JsonPath(val key: String, vararg val path: String, val returnAsText: Boolean = false) { +annotation class JsonPath(vararg val path: String, val returnAsText: Boolean = false) { companion object { @SupabaseInternal const val PATH_PARAMETER_NAME = "path" From 6bf54411cfac9b38e7572474729177cb9f970111 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Tue, 29 Oct 2024 17:11:56 +0100 Subject: [PATCH 07/19] Use new column registry --- .../supabase/ksp/SelectableSymbolProcessor.kt | 65 ++++++++++++------- .../jan/supabase/postgrest/ColumnRegistry.kt | 15 +++++ .../jan/supabase/postgrest/Postgrest.kt | 1 + .../jan/supabase/postgrest/PostgrestImpl.kt | 2 +- .../query/PostgrestBuilderExtension.kt | 43 ++++++++++++ .../postgrest/query/PostgrestQueryBuilder.kt | 27 ++++++-- .../query/PostgrestRequestBuilder.kt | 8 ++- .../query/request/InsertRequestBuilder.kt | 3 +- .../query/request/RpcRequestBuilder.kt | 7 +- .../query/request/SelectRequestBuilder.kt | 6 +- .../query/request/UpsertRequestBuilder.kt | 6 +- 11 files changed, 145 insertions(+), 38 deletions(-) create mode 100644 Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt create mode 100644 Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestBuilderExtension.kt diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt index d96938342..f8e62f97a 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -7,71 +7,75 @@ import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFile import com.google.devtools.ksp.symbol.KSNode import com.google.devtools.ksp.symbol.KSValueParameter import com.google.devtools.ksp.symbol.Modifier import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec -import com.squareup.kotlinpoet.PropertySpec -import com.squareup.kotlinpoet.ksp.toClassName +import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.annotations.ApplyFunction import io.github.jan.supabase.postgrest.annotations.Cast import io.github.jan.supabase.postgrest.annotations.ColumnName import io.github.jan.supabase.postgrest.annotations.Foreign import io.github.jan.supabase.postgrest.annotations.JsonPath import io.github.jan.supabase.postgrest.annotations.Selectable -import io.github.jan.supabase.postgrest.query.Columns class SelectableSymbolProcessor( private val codeGenerator: CodeGenerator, private val logger: KSPLogger, private val options: Map ) : SymbolProcessor { + + val packageName = options["packageName"] ?: "io.github.jan.supabase.postgrest" + val fileName = options["fileName"] ?: "PostgrestColumns" + override fun process(resolver: Resolver): List { val symbols = resolver.getSymbolsWithAnnotation(Selectable::class.java.name).filterIsInstance() if (!symbols.iterator().hasNext()) return emptyList() + val types = hashMapOf() symbols.forEach { symbol -> val className = symbol.simpleName.asString() - val packageName = symbol.containingFile?.packageName?.asString().orEmpty() + val qualifiedName = symbol.qualifiedName?.asString() + if(qualifiedName == null) { + logger.error("Qualified name of $className is null", symbol) + return@forEach; + } if (!symbol.modifiers.contains(Modifier.DATA)) { logger.error("The class $className is not a data class", symbol) return emptyList() } - val companionObject = symbol.anyCompanionObject() - if(companionObject == null) { - logger.error("Companion object not found", symbol) - return emptyList() - } val parameters = symbol.primaryConstructor?.parameters if(parameters == null) { logger.error("Primary constructor is null or has no parameter", symbol) return emptyList() } val columns = parameters.map { processParameters(it, resolver) }.joinToString(",") - writeColumnsExtensionProperty(symbol, companionObject, columns, "${className}Columns", packageName) + types[qualifiedName] = columns } + writePostgrestExtensionFunction(types, symbols.mapNotNull { it.containingFile }.toList()) return emptyList() } - private fun writeColumnsExtensionProperty( - symbol: KSClassDeclaration, - companionObject: KSClassDeclaration, - columns: String, - className: String, - packageName: String + private fun writePostgrestExtensionFunction( + columns: Map, + sources: List ) { - val fileSpec = FileSpec.builder(packageName, className) - .addImport("io.github.jan.supabase.postgrest.query", "Columns") - .addProperty(PropertySpec.builder("columns", Columns::class) - .receiver(companionObject.toClassName()) - .getter(FunSpec.getterBuilder().addStatement("return Columns.raw(\"$columns\")").build()) - .build() - ) + //Maybe add comments and SupabaseInternal annotations + val function = FunSpec.builder("addSelectableTypes") + .addKdoc(COMMENT) + .receiver(Postgrest.Config::class) + columns.forEach { (qualifiedName, columns) -> + function.addStatement("columnRegistry.registerColumns(\"$qualifiedName\", \"$columns\")") + } + val fileSpec = FileSpec.builder(packageName, fileName) + .addFunction(function.build()) + .addImport("io.github.jan.supabase.postgrest.annotations", "Selectable") .build() codeGenerator.createNewFile( - Dependencies(false, symbol.containingFile!!), + Dependencies(false, *sources.toTypedArray()), packageName, - className, + fileName, extensionName = "kt" ).bufferedWriter().use { fileSpec.writeTo(it) @@ -212,4 +216,15 @@ class SelectableSymbolProcessor( } } + companion object { + + val COMMENT = """ + |Adds the types annotated with [Selectable] to the ColumnRegistry. Allows to use the automatically generated columns in the PostgrestQueryBuilder. + | + |This file is generated by the SelectableSymbolProcessor. + |Do not modify it manually. + """.trimMargin() + + } + } \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt new file mode 100644 index 000000000..43481ed41 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt @@ -0,0 +1,15 @@ +package io.github.jan.supabase.postgrest + +import kotlin.reflect.KClass + +class ColumnRegistry( + private val map: MutableMap = mutableMapOf() +) { + + fun getColumns(kClass: KClass): String = map[kClass.qualifiedName] ?: error("No columns registered for $kClass") + + fun registerColumns(qualifiedName: String, columns: String) { + map[qualifiedName] = columns + } + +} \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt index 960ef4e75..5f24b3afd 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt @@ -101,6 +101,7 @@ interface Postgrest : MainPlugin, CustomSerializationPlugin { data class Config( var defaultSchema: String = "public", var propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, + var columnRegistry: ColumnRegistry = ColumnRegistry() ): MainConfig(), CustomSerializationConfig { override var serializer: SupabaseSerializer? = null diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt index 0ade59c93..8927975a8 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt @@ -56,7 +56,7 @@ internal class PostgrestImpl(override val supabaseClient: SupabaseClient, overri override suspend fun rpc(function: String, request: RpcRequestBuilder.() -> Unit): PostgrestResult = rpcRequest(function, null, request) private suspend fun rpcRequest(function: String, body: JsonObject? = null, request: RpcRequestBuilder.() -> Unit): PostgrestResult { - val requestBuilder = RpcRequestBuilder(config.defaultSchema, config.propertyConversionMethod).apply(request) + val requestBuilder = RpcRequestBuilder(config.defaultSchema, config.propertyConversionMethod, config.columnRegistry).apply(request) val urlParams = buildMap { putAll(requestBuilder.params.mapToFirstValue()) if(requestBuilder.method != RpcMethod.POST && body != null) { diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestBuilderExtension.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestBuilderExtension.kt new file mode 100644 index 000000000..23bf6999b --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestBuilderExtension.kt @@ -0,0 +1,43 @@ +package io.github.jan.supabase.postgrest.query + +import io.github.jan.supabase.annotations.SupabaseExperimental +import io.github.jan.supabase.auth.PostgrestFilterDSL +import io.github.jan.supabase.exceptions.HttpRequestException +import io.github.jan.supabase.exceptions.RestException +import io.github.jan.supabase.postgrest.annotations.Selectable +import io.github.jan.supabase.postgrest.query.request.SelectRequestBuilder +import io.github.jan.supabase.postgrest.result.PostgrestResult +import io.ktor.client.plugins.HttpRequestTimeoutException + +/** + * Executes vertical filtering with select on [PostgrestQueryBuilder.table]. + * + * - This method is a shorthand for [select] with the columns automatically determined by the [T] type. + * - [T] must be marked with [Selectable] and the `ksp-compiler` KSP dependency must be added to the project (via the KSP Gradle plugin). + * + * @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 + */ +@SupabaseExperimental +suspend inline fun PostgrestQueryBuilder.select( + request: @PostgrestFilterDSL SelectRequestBuilder.() -> Unit = {} +): PostgrestResult { + val registry = postgrest.config.columnRegistry + val columns = registry.getColumns(T::class) + return select(Columns.raw(columns), request) +} + +/** + * Return `data` after the query has been executed. + * + * - This method is a shorthand for [select] with the columns automatically determined by the [T] type. + * - [T] must be marked with [Selectable] and the `ksp-compiler` KSP dependency must be added to the project (via the KSP Gradle plugin). + */ +@SupabaseExperimental +inline fun PostgrestRequestBuilder.select() { + val columns = columnRegistry.getColumns(T::class) + select(Columns.raw(columns)) +} diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt index 2b0d76624..25854b832 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt @@ -43,7 +43,10 @@ class PostgrestQueryBuilder( columns: Columns = Columns.ALL, request: @PostgrestFilterDSL SelectRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = SelectRequestBuilder(postgrest.config.propertyConversionMethod).apply { + val requestBuilder = SelectRequestBuilder( + postgrest.config.propertyConversionMethod, + postgrest.config.columnRegistry + ).apply { request(); params["select"] = listOf(columns.value) } val selectRequest = SelectRequest( @@ -75,7 +78,10 @@ class PostgrestQueryBuilder( values: List, request: UpsertRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = UpsertRequestBuilder(postgrest.config.propertyConversionMethod).apply(request) + val requestBuilder = UpsertRequestBuilder( + postgrest.config.propertyConversionMethod, + postgrest.config.columnRegistry + ).apply(request) val body = postgrest.serializer.encodeToJsonElement(values).jsonArray val columns = body.map { it.jsonObject.keys }.flatten().distinct() if(columns.isNotEmpty()) requestBuilder.params["columns"] = listOf(columns.joinToString(",")) @@ -129,7 +135,10 @@ class PostgrestQueryBuilder( values: List, request: InsertRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = InsertRequestBuilder(postgrest.config.propertyConversionMethod).apply(request) + val requestBuilder = InsertRequestBuilder( + postgrest.config.propertyConversionMethod, + postgrest.config.columnRegistry + ).apply(request) val body = postgrest.serializer.encodeToJsonElement(values).jsonArray val columns = body.map { it.jsonObject.keys }.flatten().distinct() if(columns.isNotEmpty()) requestBuilder.params["columns"] = listOf(columns.joinToString(",")) @@ -174,7 +183,7 @@ class PostgrestQueryBuilder( crossinline update: PostgrestUpdate.() -> Unit = {}, request: PostgrestRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request) + val requestBuilder = newRequestBuilder(request) val updateRequest = UpdateRequest( body = buildPostgrestUpdate(postgrest.config.propertyConversionMethod, postgrest.serializer, update), returning = requestBuilder.returning, @@ -201,7 +210,7 @@ class PostgrestQueryBuilder( value: T, request: PostgrestRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request) + val requestBuilder = newRequestBuilder(request) val updateRequest = UpdateRequest( returning = requestBuilder.returning, count = requestBuilder.count, @@ -226,7 +235,7 @@ class PostgrestQueryBuilder( suspend inline fun delete( request: PostgrestRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request) + val requestBuilder = newRequestBuilder(request) val deleteRequest = DeleteRequest( returning = requestBuilder.returning, count = requestBuilder.count, @@ -237,6 +246,12 @@ class PostgrestQueryBuilder( return RestRequestExecutor.execute(postgrest = postgrest, path = table, request = deleteRequest) } + @PublishedApi + internal inline fun newRequestBuilder(request: PostgrestRequestBuilder.() -> Unit = {}) = PostgrestRequestBuilder( + postgrest.config.propertyConversionMethod, + postgrest.config.columnRegistry + ).apply(request) + companion object { const val HEADER_PREFER = "Prefer" } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt index 656d016cb..944d92a5a 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt @@ -3,6 +3,7 @@ package io.github.jan.supabase.postgrest.query import io.github.jan.supabase.annotations.SupabaseExperimental import io.github.jan.supabase.auth.PostgrestFilterDSL +import io.github.jan.supabase.postgrest.ColumnRegistry import io.github.jan.supabase.postgrest.PropertyConversionMethod import io.github.jan.supabase.postgrest.query.filter.PostgrestFilterBuilder import io.github.jan.supabase.postgrest.result.PostgrestResult @@ -14,7 +15,10 @@ import kotlin.js.JsName * A builder for Postgrest requests. */ @PostgrestFilterDSL -open class PostgrestRequestBuilder(@PublishedApi internal val propertyConversionMethod: PropertyConversionMethod) { +open class PostgrestRequestBuilder( + @PublishedApi internal val propertyConversionMethod: PropertyConversionMethod, + @PublishedApi internal val columnRegistry: ColumnRegistry +) { /** * The [Count] algorithm to use to count rows in the table or view. @@ -26,7 +30,7 @@ open class PostgrestRequestBuilder(@PublishedApi internal val propertyConversion * The [Returning] option to use. */ var returning: Returning = Returning.Minimal - private set + internal set @SupabaseExperimental val params: MutableMap> = mutableMapOf() @SupabaseExperimental val headers: HeadersBuilder = HeadersBuilder() diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt index 4c3f5ff28..b8ef1e488 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt @@ -1,5 +1,6 @@ package io.github.jan.supabase.postgrest.query.request +import io.github.jan.supabase.postgrest.ColumnRegistry import io.github.jan.supabase.postgrest.PropertyConversionMethod import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder @@ -7,7 +8,7 @@ import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder /** * Request builder for [PostgrestQueryBuilder.insert] */ -open class InsertRequestBuilder(propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) { +open class InsertRequestBuilder(propertyConversionMethod: PropertyConversionMethod, columnRegistry: ColumnRegistry): PostgrestRequestBuilder(propertyConversionMethod, columnRegistry) { /** * Make missing fields default to `null`. diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt index 5f631cf98..6dbd0d8d3 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt @@ -1,5 +1,6 @@ package io.github.jan.supabase.postgrest.query.request +import io.github.jan.supabase.postgrest.ColumnRegistry import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.PropertyConversionMethod import io.github.jan.supabase.postgrest.RpcMethod @@ -8,7 +9,11 @@ import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder /** * Request builder for [Postgrest.rpc] */ -class RpcRequestBuilder(defaultSchema: String, propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) { +class RpcRequestBuilder( + defaultSchema: String, + propertyConversionMethod: PropertyConversionMethod, + columnRegistry: ColumnRegistry +): PostgrestRequestBuilder(propertyConversionMethod, columnRegistry) { /** * The HTTP method to use. Default is POST diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt index 1ac90fac3..c83aecb3d 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt @@ -1,5 +1,6 @@ package io.github.jan.supabase.postgrest.query.request +import io.github.jan.supabase.postgrest.ColumnRegistry import io.github.jan.supabase.postgrest.PropertyConversionMethod import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder @@ -7,7 +8,10 @@ import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder /** * Request builder for [PostgrestQueryBuilder.select] */ -class SelectRequestBuilder(propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) { +class SelectRequestBuilder( + propertyConversionMethod: PropertyConversionMethod, + columnRegistry: ColumnRegistry +): PostgrestRequestBuilder(propertyConversionMethod, columnRegistry) { /** * If true, no body will be returned. Useful when using count. diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt index 024e00338..97ee743b8 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt @@ -1,12 +1,16 @@ package io.github.jan.supabase.postgrest.query.request +import io.github.jan.supabase.postgrest.ColumnRegistry import io.github.jan.supabase.postgrest.PropertyConversionMethod import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder /** * Request builder for [PostgrestQueryBuilder.upsert] */ -class UpsertRequestBuilder(propertyConversionMethod: PropertyConversionMethod): InsertRequestBuilder(propertyConversionMethod) { +class UpsertRequestBuilder( + propertyConversionMethod: PropertyConversionMethod, + columnRegistry: ColumnRegistry +): InsertRequestBuilder(propertyConversionMethod, columnRegistry) { /** * Comma-separated UNIQUE column(s) to specify how From 38b3419724247a67be97044c8685229f033cb8fd Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Wed, 30 Oct 2024 11:59:19 +0100 Subject: [PATCH 08/19] Add some docs --- KSP/README.md | 43 +++++++++++++++++++ .../supabase/ksp/SelectableSymbolProcessor.kt | 6 ++- Postgrest/README.md | 3 ++ .../jan/supabase/postgrest/ColumnRegistry.kt | 2 + .../jan/supabase/postgrest/Postgrest.kt | 5 ++- 5 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 KSP/README.md diff --git a/KSP/README.md b/KSP/README.md new file mode 100644 index 000000000..ab052aea9 --- /dev/null +++ b/KSP/README.md @@ -0,0 +1,43 @@ +# Supabase KSP compiler + +Currently only supports generating columns for `@Selectable` types. + +To install it, add the KSP Gradle plugin to your project: + +```kotlin +plugins { + id("com.google.devtools.ksp") version "2.0.21-1.0.25" //kotlinVersion-kspVersion +} +``` + +Then add the Supabase KSP compiler to your dependencies: + +> [!NOTE] +> `VERSION` is the same as the Supabase-kt version. + +**JVM**: + +```kotlin +depdencies { + ksp("io.github.jan-tennert.supabase:ksp-compiler:VERSION") +} +``` + +**Multiplatform**: + +```kotlin +kotlin { + //... + jvm() + androidTarget() + iosX64() + //... +} + +dependencies { + //Advised to use Gradle Version Catalogs + add("kspCommonMainMetadata", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") + add("kspJvm", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") + add("kspAndroid", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") + add("kspIosX64", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") +} \ No newline at end of file diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt index f8e62f97a..79e2c73b6 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -11,8 +11,11 @@ import com.google.devtools.ksp.symbol.KSFile import com.google.devtools.ksp.symbol.KSNode import com.google.devtools.ksp.symbol.KSValueParameter import com.google.devtools.ksp.symbol.Modifier +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec +import io.github.jan.supabase.annotations.SupabaseInternal import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.annotations.ApplyFunction import io.github.jan.supabase.postgrest.annotations.Cast @@ -61,9 +64,10 @@ class SelectableSymbolProcessor( columns: Map, sources: List ) { - //Maybe add comments and SupabaseInternal annotations val function = FunSpec.builder("addSelectableTypes") .addKdoc(COMMENT) + .addAnnotation(AnnotationSpec.builder(ClassName.bestGuess("kotlin.OptIn")).addMember("%T::class", + SupabaseInternal::class).build()) .receiver(Postgrest.Config::class) columns.forEach { (qualifiedName, columns) -> function.addStatement("columnRegistry.registerColumns(\"$qualifiedName\", \"$columns\")") diff --git a/Postgrest/README.md b/Postgrest/README.md index c7c4f75a9..1e283b0dc 100644 --- a/Postgrest/README.md +++ b/Postgrest/README.md @@ -59,6 +59,9 @@ val supabase = createSupabaseClient( } ``` +> [!NOTE] +> Generating columns for `@Selectable` requires the [KSP compiler](/KSP) to be installed. + # Usage See [Postgrest documentation](https://supabase.com/docs/reference/kotlin/select) for usage diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt index 43481ed41..4f0359eb8 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt @@ -1,7 +1,9 @@ package io.github.jan.supabase.postgrest +import io.github.jan.supabase.annotations.SupabaseInternal import kotlin.reflect.KClass +@SupabaseInternal class ColumnRegistry( private val map: MutableMap = mutableMapOf() ) { diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt index 5f24b3afd..76abe6175 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt @@ -2,7 +2,8 @@ package io.github.jan.supabase.postgrest import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.SupabaseSerializer -import io.github.jan.supabase.exceptions.HttpRequestException +import io.github.jan.supabase.annotations.SupabaseInternal +import io.github.jan.supabase.exceptions.RestException import io.github.jan.supabase.logging.SupabaseLogger import io.github.jan.supabase.plugins.CustomSerializationConfig import io.github.jan.supabase.plugins.CustomSerializationPlugin @@ -101,7 +102,7 @@ interface Postgrest : MainPlugin, CustomSerializationPlugin { data class Config( var defaultSchema: String = "public", var propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, - var columnRegistry: ColumnRegistry = ColumnRegistry() + @property:SupabaseInternal var columnRegistry: ColumnRegistry = ColumnRegistry() ): MainConfig(), CustomSerializationConfig { override var serializer: SupabaseSerializer? = null From 5ca1485190fc1d1ddd77b6f30dc76c1d498b74bc Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Wed, 30 Oct 2024 12:01:17 +0100 Subject: [PATCH 09/19] Fix typo --- KSP/README.md | 2 +- Postgrest/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/KSP/README.md b/KSP/README.md index ab052aea9..cd50647eb 100644 --- a/KSP/README.md +++ b/KSP/README.md @@ -1,6 +1,6 @@ # Supabase KSP compiler -Currently only supports generating columns for `@Selectable` types. +Currently only supports generating columns for `@Selectable` data classes. To install it, add the KSP Gradle plugin to your project: diff --git a/Postgrest/README.md b/Postgrest/README.md index 1e283b0dc..1ac6d4655 100644 --- a/Postgrest/README.md +++ b/Postgrest/README.md @@ -60,7 +60,7 @@ val supabase = createSupabaseClient( ``` > [!NOTE] -> Generating columns for `@Selectable` requires the [KSP compiler](/KSP) to be installed. +> Generating columns for `@Selectable` data classes requires the [KSP compiler](/KSP) to be installed. # Usage From ac5bc46655cef5890e393ce3f838d7ef36354544 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Wed, 30 Oct 2024 12:34:01 +0100 Subject: [PATCH 10/19] Update KSP docs --- KSP/README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/KSP/README.md b/KSP/README.md index cd50647eb..5c45241af 100644 --- a/KSP/README.md +++ b/KSP/README.md @@ -12,10 +12,7 @@ plugins { Then add the Supabase KSP compiler to your dependencies: -> [!NOTE] -> `VERSION` is the same as the Supabase-kt version. - -**JVM**: +[**JVM**](https://kotlinlang.org/docs/ksp-quickstart.html#add-a-processor): ```kotlin depdencies { @@ -23,7 +20,7 @@ depdencies { } ``` -**Multiplatform**: +[**Multiplatform**](https://kotlinlang.org/docs/ksp-multiplatform.html): ```kotlin kotlin { From c88297747fee31e7853c09956a9316267e8e7a3b Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Wed, 30 Oct 2024 12:53:51 +0100 Subject: [PATCH 11/19] Fix detekt and JS --- .../supabase/ksp/SelectableSymbolProcessor.kt | 15 +++++++------- .../io/github/jan/supabase/ksp/Utils.kt | 10 +++++++++- .../jan/supabase/postgrest/ColumnRegistry.kt | 2 +- .../postgrest/annotations/ApplyFunction.kt | 20 +++++++++++++++++++ Postgrest/src/commonTest/kotlin/Utils.kt | 9 +++++++-- detekt.yml | 6 +++--- 6 files changed, 48 insertions(+), 14 deletions(-) diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt index 79e2c73b6..f55d63982 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -87,12 +87,13 @@ class SelectableSymbolProcessor( } private fun processParameters(parameter: KSValueParameter, resolver: Resolver): String { - val parameterClass = resolver.getClassDeclarationByName(parameter.type.resolve().declaration.qualifiedName!!) - val innerColumns = if(!isPrimitive(parameterClass!!.qualifiedName!!.asString())) { + val parameterClass = resolver.getClassDeclarationByName(parameter.type.resolve().declaration.qualifiedKSName) + requireNotNull(parameterClass) { "Parameter class is null" } + val innerColumns = if(!isPrimitive(parameterClass.qualifiedNameAsString)) { val annotation = parameterClass.annotations.getAnnotationOrNull(Selectable::class.java.simpleName) if(annotation == null) { //Could be a JSON column or a custom type, so don't throw an error - logger.info("Type of parameter ${parameter.name!!.getShortName()} is not a primitive type and does not have @Selectable annotation", parameter) + logger.info("Type of parameter ${parameter.nameAsString} is not a primitive type and does not have @Selectable annotation", parameter) "" } else { val columns = parameterClass.primaryConstructor?.parameters @@ -103,7 +104,7 @@ class SelectableSymbolProcessor( columns.map { processParameters(it, resolver) }.joinToString(",") } } else "" - val alias = parameter.name!!.getShortName() + val alias = parameter.nameAsString val columnName = parameter.annotations.getAnnotationOrNull(ColumnName::class.java.simpleName) ?.arguments?.getParameterValue(ColumnName.NAME_PARAMETER_NAME) val isForeign = parameter.annotations.getAnnotationOrNull(Foreign::class.java.simpleName) != null @@ -125,15 +126,15 @@ class SelectableSymbolProcessor( innerColumns = innerColumns ) checkValidCombinations( - parameterName = parameter.name!!.asString(), + parameterName = parameter.nameAsString, options = options, symbol = parameter ) return buildColumns( options = options, - parameterName = parameter.name!!.asString(), + parameterName = parameter.nameAsString, symbol = parameter, - qualifiedTypeName = parameterClass.qualifiedName!!.asString() + qualifiedTypeName = parameterClass.simpleName.asString() //Qualified names are not supported in Kotlin JS ) } diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt index b46faf78f..0cf08d89e 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt @@ -2,7 +2,9 @@ package io.github.jan.supabase.ksp import com.google.devtools.ksp.symbol.KSAnnotation import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSValueArgument +import com.google.devtools.ksp.symbol.KSValueParameter fun Sequence.getAnnotation(target: String): KSAnnotation { return getAnnotationOrNull(target) ?: @@ -26,4 +28,10 @@ fun List.getParameterValue(target: String): T { fun List.getParameterValueIfExist(target: String): T? { for (element in this) if (element.name?.asString() == target) (element.value as? T)?.let { return it } return null -} \ No newline at end of file +} + +val KSValueParameter.nameAsString: String get() = name?.asString() ?: error("Parameter name is null") + +val KSDeclaration.qualifiedNameAsString get() = qualifiedKSName.asString() +val KSDeclaration.qualifiedKSName get() = qualifiedName ?: error("Qualified name is null") +val KSDeclaration.simpleNameAsString get() = simpleName.asString() \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt index 4f0359eb8..7932e984e 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt @@ -8,7 +8,7 @@ class ColumnRegistry( private val map: MutableMap = mutableMapOf() ) { - fun getColumns(kClass: KClass): String = map[kClass.qualifiedName] ?: error("No columns registered for $kClass") + fun getColumns(kClass: KClass): String = map[kClass.simpleName] ?: error("No columns registered for $kClass") fun registerColumns(qualifiedName: String, columns: String) { map[qualifiedName] = columns diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt index 3bf709b65..ab0648840 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/ApplyFunction.kt @@ -12,10 +12,30 @@ annotation class ApplyFunction(val function: String) { companion object { @SupabaseInternal const val FUNCTION_PARAMETER_NAME = "function" + + /** + * Apply the `avg` function to a column. + */ const val AVG = "avg" + + /** + * Apply the `count` function to a column. + */ const val COUNT = "count" + + /** + * Apply the `max` function to a column. + */ const val MAX = "max" + + /** + * Apply the `min` function to a column. + */ const val MIN = "min" + + /** + * Apply the `sum` function to a column. + */ const val SUM = "sum" } diff --git a/Postgrest/src/commonTest/kotlin/Utils.kt b/Postgrest/src/commonTest/kotlin/Utils.kt index b1db90d84..b2d41d3d8 100644 --- a/Postgrest/src/commonTest/kotlin/Utils.kt +++ b/Postgrest/src/commonTest/kotlin/Utils.kt @@ -1,10 +1,15 @@ import io.github.jan.supabase.annotations.SupabaseInternal +import io.github.jan.supabase.postgrest.ColumnRegistry import io.github.jan.supabase.postgrest.PropertyConversionMethod import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder @SupabaseInternal -inline fun postgrestRequest(propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, block: PostgrestRequestBuilder.() -> Unit): PostgrestRequestBuilder { - val filter = PostgrestRequestBuilder(propertyConversionMethod) +inline fun postgrestRequest( + propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, + columnRegistry: ColumnRegistry = ColumnRegistry(), + block: PostgrestRequestBuilder.() -> Unit +): PostgrestRequestBuilder { + val filter = PostgrestRequestBuilder(propertyConversionMethod, columnRegistry) filter.block() return filter } \ No newline at end of file diff --git a/detekt.yml b/detekt.yml index 0762d3878..cd4ebe1c0 100644 --- a/detekt.yml +++ b/detekt.yml @@ -75,7 +75,7 @@ comments: allowParamOnConstructorProperties: false UndocumentedPublicClass: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] + excludes: ['**/KSP/**', '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] searchInNestedClass: true searchInInnerClass: true searchInInnerObject: false @@ -86,14 +86,14 @@ comments: - 'SupabaseExperimental' UndocumentedPublicFunction: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] + excludes: ['**/KSP/**', '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] searchProtectedFunction: false ignoreAnnotated: - 'SupabaseInternal' - 'SupabaseExperimental' UndocumentedPublicProperty: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] + excludes: ['**/KSP/**', '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] searchProtectedProperty: false ignoreAnnotated: - 'SupabaseInternal' From 9df7b82192955350b61819ec08134b52664c71f8 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Wed, 30 Oct 2024 13:03:47 +0100 Subject: [PATCH 12/19] Small improvements --- .../supabase/ksp/SelectableSymbolProcessor.kt | 4 ++-- .../jan/supabase/postgrest/ColumnRegistry.kt | 21 ++++++++++++++----- .../jan/supabase/postgrest/Postgrest.kt | 2 +- Postgrest/src/commonTest/kotlin/Utils.kt | 3 ++- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt index f55d63982..e12f660ee 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -30,8 +30,8 @@ class SelectableSymbolProcessor( private val options: Map ) : SymbolProcessor { - val packageName = options["packageName"] ?: "io.github.jan.supabase.postgrest" - val fileName = options["fileName"] ?: "PostgrestColumns" + val packageName = options["selectablePackageName"] ?: "io.github.jan.supabase.postgrest" + val fileName = options["selectableFileName"] ?: "PostgrestColumns" override fun process(resolver: Resolver): List { val symbols = resolver.getSymbolsWithAnnotation(Selectable::class.java.name).filterIsInstance() diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt index 7932e984e..c3fb3d32c 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt @@ -3,15 +3,26 @@ package io.github.jan.supabase.postgrest import io.github.jan.supabase.annotations.SupabaseInternal import kotlin.reflect.KClass +@OptIn(ExperimentalSubclassOptIn::class) @SupabaseInternal -class ColumnRegistry( +@SubclassOptInRequired(SupabaseInternal::class) +interface ColumnRegistry { + + fun getColumns(kClass: KClass): String + + fun registerColumns(name: String, columns: String) + +} + +@SupabaseInternal +class MapColumnRegistry( private val map: MutableMap = mutableMapOf() -) { +): ColumnRegistry { - fun getColumns(kClass: KClass): String = map[kClass.simpleName] ?: error("No columns registered for $kClass") + override fun getColumns(kClass: KClass): String = map[kClass.simpleName] ?: error("No columns registered for $kClass") - fun registerColumns(qualifiedName: String, columns: String) { - map[qualifiedName] = columns + override fun registerColumns(name: String, columns: String) { + map[name] = columns } } \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt index 76abe6175..438b4a05e 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt @@ -102,7 +102,7 @@ interface Postgrest : MainPlugin, CustomSerializationPlugin { data class Config( var defaultSchema: String = "public", var propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, - @property:SupabaseInternal var columnRegistry: ColumnRegistry = ColumnRegistry() + @property:SupabaseInternal var columnRegistry: ColumnRegistry = MapColumnRegistry() ): MainConfig(), CustomSerializationConfig { override var serializer: SupabaseSerializer? = null diff --git a/Postgrest/src/commonTest/kotlin/Utils.kt b/Postgrest/src/commonTest/kotlin/Utils.kt index b2d41d3d8..381842ead 100644 --- a/Postgrest/src/commonTest/kotlin/Utils.kt +++ b/Postgrest/src/commonTest/kotlin/Utils.kt @@ -1,12 +1,13 @@ import io.github.jan.supabase.annotations.SupabaseInternal import io.github.jan.supabase.postgrest.ColumnRegistry +import io.github.jan.supabase.postgrest.MapColumnRegistry import io.github.jan.supabase.postgrest.PropertyConversionMethod import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder @SupabaseInternal inline fun postgrestRequest( propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, - columnRegistry: ColumnRegistry = ColumnRegistry(), + columnRegistry: ColumnRegistry = MapColumnRegistry(), block: PostgrestRequestBuilder.() -> Unit ): PostgrestRequestBuilder { val filter = PostgrestRequestBuilder(propertyConversionMethod, columnRegistry) From bd4f31b619e179617869bbd72e8dbe3e5120ef20 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Thu, 31 Oct 2024 15:31:17 +0100 Subject: [PATCH 13/19] Fix error when not using the root package --- .../io/github/jan/supabase/ksp/PrimitiveColumnType.kt | 1 + .../jan/supabase/ksp/SelectableSymbolProcessor.kt | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt index aaad2249c..197f49945 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/PrimitiveColumnType.kt @@ -1,5 +1,6 @@ package io.github.jan.supabase.ksp +//Used for auto-casting, when no type is specified val primitiveColumnTypes = mapOf( "kotlin.String" to "text", "kotlin.Int" to "int4", diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt index e12f660ee..57fa0ee6d 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -39,11 +39,7 @@ class SelectableSymbolProcessor( val types = hashMapOf() symbols.forEach { symbol -> val className = symbol.simpleName.asString() - val qualifiedName = symbol.qualifiedName?.asString() - if(qualifiedName == null) { - logger.error("Qualified name of $className is null", symbol) - return@forEach; - } + val qualifiedName = symbol.simpleName.asString() //Kotlin JS doesn't support qualified names if (!symbol.modifiers.contains(Modifier.DATA)) { logger.error("The class $className is not a data class", symbol) return emptyList() @@ -76,12 +72,13 @@ class SelectableSymbolProcessor( .addFunction(function.build()) .addImport("io.github.jan.supabase.postgrest.annotations", "Selectable") .build() - codeGenerator.createNewFile( + val stream = codeGenerator.createNewFile( Dependencies(false, *sources.toTypedArray()), packageName, fileName, extensionName = "kt" - ).bufferedWriter().use { + ) + stream.bufferedWriter().use { fileSpec.writeTo(it) } } From 1a65dc42af13d18012cd41c2c8c4233af21d00cf Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Thu, 31 Oct 2024 15:52:56 +0100 Subject: [PATCH 14/19] Cleanup and add some tests --- KSP/README.md | 1 + .../ksp/SelectableSymbolProcessorProvider.kt | 2 ++ .../io/github/jan/supabase/ksp/Utils.kt | 11 +------ .../jan/supabase/postgrest/ColumnRegistry.kt | 3 ++ .../jan/supabase/postgrest/PostgrestImpl.kt | 2 +- .../query/PostgrestBuilderExtension.kt | 2 +- .../postgrest/query/PostgrestQueryBuilder.kt | 12 +++----- .../query/PostgrestRequestBuilder.kt | 8 ++--- .../query/request/InsertRequestBuilder.kt | 5 ++-- .../query/request/RpcRequestBuilder.kt | 7 ++--- .../query/request/SelectRequestBuilder.kt | 8 ++--- .../query/request/UpsertRequestBuilder.kt | 8 ++--- .../commonTest/kotlin/ColumnRegistryTest.kt | 14 +++++++++ .../kotlin/PostgrestRequestBuilderTest.kt | 17 +++++++++++ .../src/commonTest/kotlin/PostgrestTest.kt | 30 ++++++++++++++++++- Postgrest/src/commonTest/kotlin/TestType.kt | 1 + Postgrest/src/commonTest/kotlin/Utils.kt | 9 ++---- 17 files changed, 90 insertions(+), 50 deletions(-) create mode 100644 Postgrest/src/commonTest/kotlin/ColumnRegistryTest.kt create mode 100644 Postgrest/src/commonTest/kotlin/TestType.kt diff --git a/KSP/README.md b/KSP/README.md index 5c45241af..bc7c53c19 100644 --- a/KSP/README.md +++ b/KSP/README.md @@ -32,6 +32,7 @@ kotlin { } dependencies { + //Add KSP compiler to all targets that need processing //Advised to use Gradle Version Catalogs add("kspCommonMainMetadata", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") add("kspJvm", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessorProvider.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessorProvider.kt index 08f432e65..0a2138cf8 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessorProvider.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessorProvider.kt @@ -5,7 +5,9 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider class SelectableSymbolProcessorProvider: SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { return SelectableSymbolProcessor(environment.codeGenerator, environment.logger, environment.options) } + } \ No newline at end of file diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt index 0cf08d89e..2d7a56343 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/Utils.kt @@ -1,20 +1,10 @@ package io.github.jan.supabase.ksp import com.google.devtools.ksp.symbol.KSAnnotation -import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSValueArgument import com.google.devtools.ksp.symbol.KSValueParameter -fun Sequence.getAnnotation(target: String): KSAnnotation { - return getAnnotationOrNull(target) ?: - throw NoSuchElementException("Sequence contains no element matching the predicate.") -} - -fun KSClassDeclaration.anyCompanionObject(): KSClassDeclaration? { - return declarations.filterIsInstance().firstOrNull { it.isCompanionObject } -} - fun Sequence.getAnnotationOrNull(target: String): KSAnnotation? { for (element in this) if (element.shortName.asString() == target) return element return null @@ -30,6 +20,7 @@ fun List.getParameterValueIfExist(target: String): T? { return null } +//Note these should never be null for our use case, but we still need to handle it val KSValueParameter.nameAsString: String get() = name?.asString() ?: error("Parameter name is null") val KSDeclaration.qualifiedNameAsString get() = qualifiedKSName.asString() diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt index c3fb3d32c..f6ed98504 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/ColumnRegistry.kt @@ -3,6 +3,9 @@ package io.github.jan.supabase.postgrest import io.github.jan.supabase.annotations.SupabaseInternal import kotlin.reflect.KClass +/** + * Registry used to map generated columns to a class + */ @OptIn(ExperimentalSubclassOptIn::class) @SupabaseInternal @SubclassOptInRequired(SupabaseInternal::class) diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt index 8927975a8..16a0a913b 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt @@ -56,7 +56,7 @@ internal class PostgrestImpl(override val supabaseClient: SupabaseClient, overri override suspend fun rpc(function: String, request: RpcRequestBuilder.() -> Unit): PostgrestResult = rpcRequest(function, null, request) private suspend fun rpcRequest(function: String, body: JsonObject? = null, request: RpcRequestBuilder.() -> Unit): PostgrestResult { - val requestBuilder = RpcRequestBuilder(config.defaultSchema, config.propertyConversionMethod, config.columnRegistry).apply(request) + val requestBuilder = RpcRequestBuilder(config.defaultSchema, config).apply(request) val urlParams = buildMap { putAll(requestBuilder.params.mapToFirstValue()) if(requestBuilder.method != RpcMethod.POST && body != null) { diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestBuilderExtension.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestBuilderExtension.kt index 23bf6999b..adb9c3889 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestBuilderExtension.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestBuilderExtension.kt @@ -38,6 +38,6 @@ suspend inline fun PostgrestQueryBuilder.select( */ @SupabaseExperimental inline fun PostgrestRequestBuilder.select() { - val columns = columnRegistry.getColumns(T::class) + val columns = config.columnRegistry.getColumns(T::class) select(Columns.raw(columns)) } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt index 25854b832..f03a73cbf 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt @@ -44,8 +44,7 @@ class PostgrestQueryBuilder( request: @PostgrestFilterDSL SelectRequestBuilder.() -> Unit = {} ): PostgrestResult { val requestBuilder = SelectRequestBuilder( - postgrest.config.propertyConversionMethod, - postgrest.config.columnRegistry + postgrest.config ).apply { request(); params["select"] = listOf(columns.value) } @@ -79,8 +78,7 @@ class PostgrestQueryBuilder( request: UpsertRequestBuilder.() -> Unit = {} ): PostgrestResult { val requestBuilder = UpsertRequestBuilder( - postgrest.config.propertyConversionMethod, - postgrest.config.columnRegistry + postgrest.config ).apply(request) val body = postgrest.serializer.encodeToJsonElement(values).jsonArray val columns = body.map { it.jsonObject.keys }.flatten().distinct() @@ -136,8 +134,7 @@ class PostgrestQueryBuilder( request: InsertRequestBuilder.() -> Unit = {} ): PostgrestResult { val requestBuilder = InsertRequestBuilder( - postgrest.config.propertyConversionMethod, - postgrest.config.columnRegistry + postgrest.config ).apply(request) val body = postgrest.serializer.encodeToJsonElement(values).jsonArray val columns = body.map { it.jsonObject.keys }.flatten().distinct() @@ -248,8 +245,7 @@ class PostgrestQueryBuilder( @PublishedApi internal inline fun newRequestBuilder(request: PostgrestRequestBuilder.() -> Unit = {}) = PostgrestRequestBuilder( - postgrest.config.propertyConversionMethod, - postgrest.config.columnRegistry + postgrest.config ).apply(request) companion object { diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt index 944d92a5a..c58a4786d 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt @@ -3,8 +3,7 @@ package io.github.jan.supabase.postgrest.query import io.github.jan.supabase.annotations.SupabaseExperimental import io.github.jan.supabase.auth.PostgrestFilterDSL -import io.github.jan.supabase.postgrest.ColumnRegistry -import io.github.jan.supabase.postgrest.PropertyConversionMethod +import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.query.filter.PostgrestFilterBuilder import io.github.jan.supabase.postgrest.result.PostgrestResult import io.ktor.http.HeadersBuilder @@ -16,8 +15,7 @@ import kotlin.js.JsName */ @PostgrestFilterDSL open class PostgrestRequestBuilder( - @PublishedApi internal val propertyConversionMethod: PropertyConversionMethod, - @PublishedApi internal val columnRegistry: ColumnRegistry + @PublishedApi internal val config: Postgrest.Config ) { /** @@ -160,7 +158,7 @@ open class PostgrestRequestBuilder( * @param block The filter block */ inline fun filter(block: @PostgrestFilterDSL PostgrestFilterBuilder.() -> Unit) { - val filter = PostgrestFilterBuilder(propertyConversionMethod, params) + val filter = PostgrestFilterBuilder(config.propertyConversionMethod, params) filter.block() } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt index b8ef1e488..2e78cc299 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt @@ -1,14 +1,13 @@ package io.github.jan.supabase.postgrest.query.request -import io.github.jan.supabase.postgrest.ColumnRegistry -import io.github.jan.supabase.postgrest.PropertyConversionMethod +import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder /** * Request builder for [PostgrestQueryBuilder.insert] */ -open class InsertRequestBuilder(propertyConversionMethod: PropertyConversionMethod, columnRegistry: ColumnRegistry): PostgrestRequestBuilder(propertyConversionMethod, columnRegistry) { +open class InsertRequestBuilder(config: Postgrest.Config): PostgrestRequestBuilder(config) { /** * Make missing fields default to `null`. diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt index 6dbd0d8d3..0bbf3caad 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt @@ -1,8 +1,6 @@ package io.github.jan.supabase.postgrest.query.request -import io.github.jan.supabase.postgrest.ColumnRegistry 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 @@ -11,9 +9,8 @@ import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder */ class RpcRequestBuilder( defaultSchema: String, - propertyConversionMethod: PropertyConversionMethod, - columnRegistry: ColumnRegistry -): PostgrestRequestBuilder(propertyConversionMethod, columnRegistry) { + config: Postgrest.Config +): PostgrestRequestBuilder(config) { /** * The HTTP method to use. Default is POST diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt index c83aecb3d..3249f0077 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt @@ -1,7 +1,6 @@ package io.github.jan.supabase.postgrest.query.request -import io.github.jan.supabase.postgrest.ColumnRegistry -import io.github.jan.supabase.postgrest.PropertyConversionMethod +import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder @@ -9,9 +8,8 @@ import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder * Request builder for [PostgrestQueryBuilder.select] */ class SelectRequestBuilder( - propertyConversionMethod: PropertyConversionMethod, - columnRegistry: ColumnRegistry -): PostgrestRequestBuilder(propertyConversionMethod, columnRegistry) { + config: Postgrest.Config +): PostgrestRequestBuilder(config) { /** * If true, no body will be returned. Useful when using count. diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt index 97ee743b8..2356d0938 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt @@ -1,16 +1,14 @@ package io.github.jan.supabase.postgrest.query.request -import io.github.jan.supabase.postgrest.ColumnRegistry -import io.github.jan.supabase.postgrest.PropertyConversionMethod +import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder /** * Request builder for [PostgrestQueryBuilder.upsert] */ class UpsertRequestBuilder( - propertyConversionMethod: PropertyConversionMethod, - columnRegistry: ColumnRegistry -): InsertRequestBuilder(propertyConversionMethod, columnRegistry) { + config: Postgrest.Config +): InsertRequestBuilder(config) { /** * Comma-separated UNIQUE column(s) to specify how diff --git a/Postgrest/src/commonTest/kotlin/ColumnRegistryTest.kt b/Postgrest/src/commonTest/kotlin/ColumnRegistryTest.kt new file mode 100644 index 000000000..efd7e7cd5 --- /dev/null +++ b/Postgrest/src/commonTest/kotlin/ColumnRegistryTest.kt @@ -0,0 +1,14 @@ +import io.github.jan.supabase.postgrest.MapColumnRegistry +import kotlin.test.Test +import kotlin.test.assertEquals + +class ColumnRegistryTest { + + @Test + fun testColumnRegistry() { + val registry = MapColumnRegistry() + registry.registerColumns("Test", "column1, column2") + assertEquals("column1, column2", registry.getColumns(Test::class)) + } + +} \ No newline at end of file diff --git a/Postgrest/src/commonTest/kotlin/PostgrestRequestBuilderTest.kt b/Postgrest/src/commonTest/kotlin/PostgrestRequestBuilderTest.kt index 1ddc65858..243db63c3 100644 --- a/Postgrest/src/commonTest/kotlin/PostgrestRequestBuilderTest.kt +++ b/Postgrest/src/commonTest/kotlin/PostgrestRequestBuilderTest.kt @@ -1,9 +1,13 @@ +import io.github.jan.supabase.postgrest.MapColumnRegistry +import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.query.Count import io.github.jan.supabase.postgrest.query.Order import io.github.jan.supabase.postgrest.query.Returning +import io.github.jan.supabase.postgrest.query.select import io.ktor.http.HttpHeaders import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertIs class PostgrestRequestBuilderTest { @@ -23,6 +27,19 @@ class PostgrestRequestBuilderTest { assertEquals(Returning.Representation(), request.returning) } + @Test + fun testSelectWithSelectable() { + val columnRegistry = MapColumnRegistry() + columnRegistry.registerColumns("TestType", "column1, column2") + val request = postgrestRequest( + config = Postgrest.Config(columnRegistry = columnRegistry) + ) { + select() + } + assertIs(request.returning) + assertEquals("column1,column2", (request.returning as Returning.Representation).columns.value) + } + @Test fun testSingle() { val request = postgrestRequest { diff --git a/Postgrest/src/commonTest/kotlin/PostgrestTest.kt b/Postgrest/src/commonTest/kotlin/PostgrestTest.kt index 13396d1b6..908e02fd4 100644 --- a/Postgrest/src/commonTest/kotlin/PostgrestTest.kt +++ b/Postgrest/src/commonTest/kotlin/PostgrestTest.kt @@ -7,6 +7,7 @@ import io.github.jan.supabase.postgrest.postgrest import io.github.jan.supabase.postgrest.query.Columns import io.github.jan.supabase.postgrest.query.request.InsertRequestBuilder import io.github.jan.supabase.postgrest.query.request.UpsertRequestBuilder +import io.github.jan.supabase.postgrest.query.select import io.github.jan.supabase.postgrest.result.PostgrestResult import io.github.jan.supabase.testing.assertMethodIs import io.github.jan.supabase.testing.assertPathIs @@ -57,6 +58,28 @@ class PostgrestTest { ) } + @Test + fun testSelectWithSelectableType() { + testClient( + postgrestConfig = { + columnRegistry.registerColumns("TestType", "column1,column2") + }, + request = { table -> + from(table).select { + headers["custom"] = "value" + params["custom"] = listOf("value") + } + }, + requestHandler = { + assertMethodIs(HttpMethod.Get, it.method) + assertEquals("column1,column2", it.url.parameters["select"]) + assertEquals("value", it.headers["custom"]) + assertEquals("value", it.url.parameters["custom"]) + respond("") + } + ) + } + @Test fun testSelectSchema() { val columns = Columns.list("column1", "column2") @@ -362,11 +385,16 @@ class PostgrestTest { private fun testClient( table: String = "table", + postgrestConfig: Postgrest.Config.() -> Unit = {}, request: suspend SupabaseClient.(table: String) -> PostgrestResult, requestHandler: suspend MockRequestHandleScope.(HttpRequestData) -> HttpResponseData = { respond("")}, ) { val supabase = createMockedSupabaseClient( - configuration = configureClient + configuration = { + install(Postgrest) { + postgrestConfig() + } + } ) { assertPathIs("/$table", it.url.pathAfterVersion()) requestHandler(it) diff --git a/Postgrest/src/commonTest/kotlin/TestType.kt b/Postgrest/src/commonTest/kotlin/TestType.kt new file mode 100644 index 000000000..cb4aa56c8 --- /dev/null +++ b/Postgrest/src/commonTest/kotlin/TestType.kt @@ -0,0 +1 @@ +data class TestType(val column1: String, val column2: String) diff --git a/Postgrest/src/commonTest/kotlin/Utils.kt b/Postgrest/src/commonTest/kotlin/Utils.kt index 381842ead..dea02a807 100644 --- a/Postgrest/src/commonTest/kotlin/Utils.kt +++ b/Postgrest/src/commonTest/kotlin/Utils.kt @@ -1,16 +1,13 @@ import io.github.jan.supabase.annotations.SupabaseInternal -import io.github.jan.supabase.postgrest.ColumnRegistry -import io.github.jan.supabase.postgrest.MapColumnRegistry -import io.github.jan.supabase.postgrest.PropertyConversionMethod +import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder @SupabaseInternal inline fun postgrestRequest( - propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, - columnRegistry: ColumnRegistry = MapColumnRegistry(), + config: Postgrest.Config = Postgrest.Config(), block: PostgrestRequestBuilder.() -> Unit ): PostgrestRequestBuilder { - val filter = PostgrestRequestBuilder(propertyConversionMethod, columnRegistry) + val filter = PostgrestRequestBuilder(config) filter.block() return filter } \ No newline at end of file From b5079f87f937347f7007c5fee3d0e9fae7aaff7e Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Thu, 31 Oct 2024 15:54:04 +0100 Subject: [PATCH 15/19] Fix invalid test type --- Postgrest/src/commonTest/kotlin/ColumnRegistryTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Postgrest/src/commonTest/kotlin/ColumnRegistryTest.kt b/Postgrest/src/commonTest/kotlin/ColumnRegistryTest.kt index efd7e7cd5..d39ec3b17 100644 --- a/Postgrest/src/commonTest/kotlin/ColumnRegistryTest.kt +++ b/Postgrest/src/commonTest/kotlin/ColumnRegistryTest.kt @@ -7,8 +7,8 @@ class ColumnRegistryTest { @Test fun testColumnRegistry() { val registry = MapColumnRegistry() - registry.registerColumns("Test", "column1, column2") - assertEquals("column1, column2", registry.getColumns(Test::class)) + registry.registerColumns("TestType", "column1, column2") + assertEquals("column1, column2", registry.getColumns(TestType::class)) } } \ No newline at end of file From 196961bdbd010f21006bb76bfb9ffb27f627157a Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Thu, 31 Oct 2024 15:59:52 +0100 Subject: [PATCH 16/19] Improve docs --- .../jan/supabase/postgrest/annotations/Cast.kt | 2 +- .../jan/supabase/postgrest/annotations/JsonPath.kt | 1 - .../supabase/postgrest/annotations/Selectable.kt | 13 +++++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt index 608f49f69..062c68751 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Cast.kt @@ -4,7 +4,7 @@ import io.github.jan.supabase.annotations.SupabaseInternal /** * Annotation to cast a column in a PostgREST query. - * @param type The type to cast the column to. If empty, the type will be inferred from the parameter type. For example, if the parameter is of type `String`, the column will be cast to `text`. + * @param type The type to cast the column to. If empty, the type will be inferred from the parameter type. For example, if the parameter is of type [String], the column will be cast to `text`. For all supported auto-casts see the primitive types in [Selectable]. */ @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.SOURCE) diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt index 3a075fc7a..1dafbfbf9 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/JsonPath.kt @@ -38,7 +38,6 @@ annotation class JsonPath(vararg val path: String, val returnAsText: Boolean = f companion object { @SupabaseInternal const val PATH_PARAMETER_NAME = "path" - @SupabaseInternal const val KEY_PARAMETER_NAME = "key" @SupabaseInternal const val RETURN_AS_TEXT_PARAMETER_NAME = "returnAsText" } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt index fb2aadf1e..f82f19e75 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/annotations/Selectable.kt @@ -2,6 +2,15 @@ package io.github.jan.supabase.postgrest.annotations import io.github.jan.supabase.annotations.SupabaseExperimental import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlin.uuid.Uuid /** * Annotates a class as selectable. @@ -15,7 +24,7 @@ import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder * - All parameters in the primary constructor must a primitive type¹, a type that is also annotated with [Selectable] or a serializable type. * - Parameters may be annotated with [ColumnName], [ApplyFunction], [Cast], [JsonPath], [Foreign]. * - * ¹: Available primitive types are: String, Int, Long, Float, Double, Boolean, Byte, Short, Char, Instant, LocalDateTime, Uuid, LocalTime, LocalDate, JsonElement, JsonObject, JsonArray, JsonPrimitive + * ¹: Available primitive types are: [String], [Int], [Long], [Float], [Double], [Boolean], [Byte], [Short], [Char], [Instant], [LocalDateTime], [Uuid], [LocalTime], [LocalDate], [JsonElement], [JsonObject], [JsonArray], [JsonPrimitive] * * Example usage: * ```kotlin @@ -42,4 +51,4 @@ import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) @SupabaseExperimental -annotation class Selectable +annotation class Selectable \ No newline at end of file From dac4d824242b8708c7a7aa24c8207b62376ecd55 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Fri, 1 Nov 2024 14:40:32 +0100 Subject: [PATCH 17/19] Add note about IDE issues --- KSP/README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/KSP/README.md b/KSP/README.md index bc7c53c19..7a232bcbd 100644 --- a/KSP/README.md +++ b/KSP/README.md @@ -29,6 +29,11 @@ kotlin { androidTarget() iosX64() //... + + //Might need to add this if you cannot see generated code in your IDE + sourceSets.named("commonMain").configure { + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") + } } dependencies { @@ -38,4 +43,12 @@ dependencies { add("kspJvm", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") add("kspAndroid", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") add("kspIosX64", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") -} \ No newline at end of file +} + +//Might need to add this if you cannot see generated code in your IDE +project.tasks.withType(KotlinCompilationTask::class.java).configureEach { + if(name != "kspCommonMainKotlinMetadata") { + dependsOn("kspCommonMainKotlinMetadata") + } +} +``` \ No newline at end of file From f93b1fceaae0adfa2a35b600b5efadc0e0d24591 Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Fri, 1 Nov 2024 14:53:37 +0100 Subject: [PATCH 18/19] Update docs --- KSP/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/KSP/README.md b/KSP/README.md index 7a232bcbd..0433ecb41 100644 --- a/KSP/README.md +++ b/KSP/README.md @@ -40,6 +40,8 @@ dependencies { //Add KSP compiler to all targets that need processing //Advised to use Gradle Version Catalogs add("kspCommonMainMetadata", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") + + //Note: If only the common target needs processing, you don't need the following lines add("kspJvm", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") add("kspAndroid", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") add("kspIosX64", "io.github.jan-tennert.supabase:ksp-compiler:VERSION") From 2a4421408678008d514aa7a4a7e7ca4ed9bb8b5a Mon Sep 17 00:00:00 2001 From: Jan Tennert Date: Sun, 15 Jun 2025 13:27:04 +0200 Subject: [PATCH 19/19] Update versions --- KSP/README.md | 2 +- .../io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt | 4 ++-- gradle.properties | 2 +- gradle/libs.versions.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/KSP/README.md b/KSP/README.md index 0433ecb41..7f515cc7f 100644 --- a/KSP/README.md +++ b/KSP/README.md @@ -6,7 +6,7 @@ To install it, add the KSP Gradle plugin to your project: ```kotlin plugins { - id("com.google.devtools.ksp") version "2.0.21-1.0.25" //kotlinVersion-kspVersion + id("com.google.devtools.ksp") version "2.1.21-2.0.2" //kotlinVersion-kspVersion } ``` diff --git a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt index 57fa0ee6d..90b02bb4d 100644 --- a/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt +++ b/KSP/src/main/kotlin/io/github/jan/supabase/ksp/SelectableSymbolProcessor.kt @@ -49,7 +49,7 @@ class SelectableSymbolProcessor( logger.error("Primary constructor is null or has no parameter", symbol) return emptyList() } - val columns = parameters.map { processParameters(it, resolver) }.joinToString(",") + val columns = parameters.joinToString(",") { processParameters(it, resolver) } types[qualifiedName] = columns } writePostgrestExtensionFunction(types, symbols.mapNotNull { it.containingFile }.toList()) @@ -98,7 +98,7 @@ class SelectableSymbolProcessor( logger.error("Primary constructor of ${parameterClass.qualifiedName} is null or has no parameter", parameter) return "" } - columns.map { processParameters(it, resolver) }.joinToString(",") + columns.joinToString(",") { processParameters(it, resolver) } } } else "" val alias = parameter.nameAsString diff --git a/gradle.properties b/gradle.properties index 4d73b240f..9c32c898c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,5 +11,5 @@ org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.wasm.enabled=true org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled -supabase-version = 3.2.0-rc-1 +supabase-version = 3.2.0-ksp-b1 base-group = io.github.jan-tennert.supabase diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cbdd07a25..b0ab808a8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -35,7 +35,7 @@ androidx-lifecycle = "2.9.1" filekit = "0.8.8" kotlinx-browser = "0.3" secure-random = "0.5.1" -ksp = "2.0.21-1.0.25" +ksp = "2.1.21-2.0.2" kotlin-poet = "2.0.0" [plugins]