-
-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce new experimental DSL for Postgrest Columns #761
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
detekt found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt
Fixed
Show fixed
Hide fixed
Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt
Fixed
Show fixed
Hide fixed
Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt
Fixed
Show fixed
Hide fixed
Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt
Fixed
Show fixed
Hide fixed
Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/ColumnsBuilder.kt
Fixed
Show fixed
Hide fixed
infix fun String.withFunction(name: String) = "$this.$name" | ||
|
||
/** | ||
* Casts a column to the given [type] | ||
* @param type The type to cast the column to | ||
*/ | ||
infix fun String.withType(type: String) = "$this::$type" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about the names here, maybe applyFunction
or useFunction
and castAs
would be better
Hey Jan, that is an awesome feature. I was thinking of improving this also on the Swift library, for Swift building a DSL as that is also possible, but I was thinking of another possibility, which is using Macro (I think that would be a custom annotation on Kotlin). So we could have the columns definition also be the model returned by the request, such as: @Selectable
struct Movie {
let id: String
let name: String
@Foreign(table: "studios")
let studio: Studio
// Still need to think about how to express the other cases
} Then on Swift we could use it as: let movies: [Movie] = try await supabase.from("movies").select(Movie.self).execute.value This was my initial thought, what do you think of it? |
That is also a great idea! Upon quick thinking, something like this could be done via KSP. @Selectable
@Serializable
data class Movie(
val id: Int, //no ColumnName annotation means the property name will be used
@ColumnName(name = "movie_title")
val title: String, //"title" is basically the alias and "movie_title" is the actual column name
@Foreign(table = "studios")
val studio: Studio,
@ApplyFunction(function = AggregateFunction.AVG)
@ColumnName("column_name")
val someAverage: Double, //apply "avg()" on "column_name" and use "someAverage" as the alias
@JsonPath("json_data", "array", "0", "key", returnAsText = true) // jsonData->array->0->>key
val jsonKey: String
) And the compiler generates a val movie: Movie = supabase.from("movies").select(MovieColumns).decodeSingle() For casts, we could do some automatic casts, e.g. if the property type is a String, the column value gets cast to a |
Yeah, you got it. Would it be possible to call |
Well, KSP can only generate code. Maybe we can get the generated type via the declared |
I'm going to try out how good this works with KSP, making this PR a draft for now. |
@grdsdev So I think it works pretty well, but as I said the limitation of KSP (in comparison to e.g. a Compiler Plugin) is that it can only generate code (a Compiler Plugin basically way more complex and not really documented well, KSP is recommended). What I did now is to force the creation of a companion object, so that the KSP compiler generates an extension property: @Selectable
@Serializable
data class Movie(
val id: Int,
@ColumnName(name = "movie_title")
val title: String,
@Foreign
@ColumnName("studios")
val studio: Studio,
@ApplyFunction(function = ApplyFunction.AVG)
@ColumnName("column_name")
val someAverage: Double,
@ColumnName("json_data")
@JsonPath("array", "0", "key", returnAsText = true)
val jsonKey: String
) {
companion object // Required, also forced at compile-time
} Usage: val movies: List<Movie> = supabase.from("movies").select(Movie.columns).decodeList() What do you think? |
Can If so, you can create a In Swift would be: protocol Selectable {
static var columns: String { get }
}
@Selectable
struct Movie {
// columns...
}
// Macro generated code:
extension Movie: Selectable {
static var columns: String {
"""
id,
title,
studio:studios(\(Studio.columns))
"""
}
Then func select<S: Selectable>(_ select: S.Type) {
self.select(S.columns)
} |
No, in Kotlin you cannot implement an interface for an external type. I had another idea:
fun getColumnsFor(type: KType): String
val supabase = createSupabaseClient(url, key) {
install(Postgrest)
install(PostgrestColumnRegistry) //this plugin is generated by the KSP compiler
} And for selecting, we just add a new generic variant for supabase.from("movies").select<Movie>() behind the scenes, Postgrest will get the columns via the installed registry plugin. I don't think there is any way to completely make this process invisible (with KSP and without reflection, due to MP support). |
Alternatively, the KSP compiler could also generate an extension function for the Postgrest config (instead of a whole plugin): install(Postgrest) {
addSelectableTypes()
} But the rest would be the same |
extension function on Postgres config is better I think, not a fan of having to install a plugin for this |
Alright, going to implement and PR it, there we can discuss annotation names etc. |
An early draft can be seen in #769. I'm still not sure how we should handle spreading embedded resources. |
Awesome @jan-tennert will review it. I think we can just mark spreading as unsupported for now, not sure how we could support it, in Swift I think is possible due the macro system being a bit more powerful, but I need to make a few tests first. |
What kind of change does this PR introduce?
Feature.
What is the new behavior?
Introduces a new type-safe DSL for specifying columns when making a postgrest request. Example: