Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Introduce KSP compiler for generating columns via @Selectable annotation #769

Open
wants to merge 19 commits into
base: master
Choose a base branch
from

Conversation

jan-tennert
Copy link
Collaborator

@jan-tennert jan-tennert commented Oct 30, 2024

What kind of change does this PR introduce?

Feature

What is the new behavior?

New KSP compiler module which processes @Selectable annotated data classes. I kept the KSP compiler more general, so if we ever want to add a new annotation, we can just use this module.

How it works

@Selectable
@Serializable
data class Movie(
    val id: Int,

    val name: String,

    @ColumnName("created_at") //if the parameter names differs from the column name, add this annotation
    val createdAt: Instant,

    @Foreign //Mark a foreign relationship
    @ColumnName("studio_id") //Either the table name or column name
    val studio: Studio, //This should also be a `@Selectable` type

    @ColumnName("metadata") //query JSON columns
    @JsonPath("video", "resolution") 
    val resolution: String
)
  • Call the generated addSelectableTypes() method in the PostgrestConfig:
install(Postgrest) {
    addSelectableTypes()
}
  • Use the new overloads for selecting:
val movies: List<Movie> = supabase.from("movies").select<Movie>().decodeList()
//or for insert, update, etc.:
//Note that `myNewMovie` is obviously a different type
val updatedMovie: Movie = supabase.from("movies").insert(myNewMovie) { 
    select<Movie>()
}

Design details

There are 6 annotations:

  • @Serializable - Data classes will be processed by the compiler and columns will be generated based of the constructor parameters.
  • @ColumnName - Used to specify the actual column name when the parameter name differs. For foreign columns, this can be either the column name or foreign table.
  • @JsonPath - Used to specify a Json Path to query a JSON column
  • @Foreign - Used for foreign columns so the compiler knows that it has to generate nested columns
  • @ApplyFunction - Used to apply an aggregate function
  • @Cast - Used to cast a column to a type. If no type is specified, then auto casting will be attempted (e.g. if the parameter type is a String then the column will be cast to a text)

Todo:

  • [ ] Find a solution for spreading embedded resources - Will be unsupported for now
  • Tests - To test the annotation processor, maybe use kotlin-compile-testing in the future

Implementation

There is a new Postgrest config property columnRegistry which stores column data under the full class name for annotated data classes. The KSP compiler fills this registry when the generated addSelectableTypes() method is called.
Example for the generated method [Not visible normally]:

/**
 * 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.
 */
@OptIn(SupabaseInternal::class)
public fun Postgrest.Config.addSelectableTypes() {
  columnRegistry.registerColumns("Movie", "id,name,createdAt:created_at,studio:studio_id(id,name),metadata->video->resolution")
  columnRegistry.registerColumns("Studio", "id,name")
}

Additional context

See discussion in #761

github-advanced-security[bot]

This comment was marked as resolved.

@github-actions github-actions bot added the tests label Oct 30, 2024
@jan-tennert jan-tennert marked this pull request as ready for review October 31, 2024 14:33
val types = hashMapOf<String, String>()
symbols.forEach { symbol ->
val className = symbol.simpleName.asString()
val qualifiedName = symbol.simpleName.asString() //Kotlin JS doesn't support qualified names
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Name clashes could be possible due to only using the simple name, but we cannot use qualified names because of Kotlin JS support.

@grdsdev
Copy link
Contributor

grdsdev commented Nov 4, 2024

Awesome @jan-tennert let me try to get roughly the same API in Swift before merging this ok? But it looks awesome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants