Skip to content

mnbjhu/surrealdb-kotlin-driver

Repository files navigation

SurrealDB Kotlin Driver

Maven Central Java CI with Gradle

Overview

A simple Kotlin Multiplatform driver for SurrealDB.

  • Covers all of the SurrealDB Websocket endpoints: Websocket Docs.
  • Support for kotlinx.serialization meaning seamless integration between Kotlin and SurrealDB types.
  • Database calls can be made asynchronously

Usage

Dependency

build.gradle.kts

repositories {
    mavenCentral()
}

dependencies {
    implementation("uk.gibby:surrealdb-kotlin-driver:$kotlin_driver_version")
}

build.gradle

repositories {
    mavenCentral()
}

dependencies {
    implementation "uk.gibby:surrealdb-kotlin-driver:$kotlin_driver_version"
}

Example

Connecting to a SurrealDB instance

val db = Surreal("localhost", 8000)
db.connect()
db.signin("root", "root")
// db.signin("ns", "db", "scope", bind("username", "John"), bind("password", "1234"))
db.use("ns", "db")

Creating a records

// Create a record from a JSON object
db.create("user").content(buildJsonObject{ put("username", "John"); put("password", "1234")})

// Create a record from a @Serializable object
@Serializable
data class User(val username: String, val password: String)

db.create("user").content(User("John", "1234"))

Note: All functions have both a JsonObject and @Serializable variant so you can interact with SurrealDB in both a shemafull and schemaless way.

Reading records

// Select a record by ID
val record = db.select<User>("user", "123")
assert(record.username == "John")
assert(record.password == "1234")

// Select all records
val records = db.select<User>("user")
assert(records.size == 1)
assert(records[0].username == "John")
assert(records[0].password == "1234")

Updating records

// Update a record by ID
db.update("user", "123").content(User("John Updated", "1234"))

// Update all records
db.update("user").content(User("John Updated", "1234"))

// Update with a merge
db.update("user", "123").merge(
    bind(username, "John Updated"),
)

// Update with a Json patch
db.update("John", "123").patch { 
    // Json patch builder
    replace("username", "John Updated")    
}

Deleting records

// Delete a record by ID
db.delete("user", "123")

// Delete all records
db.delete("user")

Querying records

val result = db.query(
    """
    SELECT *
    FROM user
    WHERE username = $username AND password = $password
    ORDER BY username;
    """,
    bind("username", "John"),
    bind("password", "1234")
)
assert(result.size == 1)
val users = result.first().data<List<User>>()

Using Record Links

In order to interact with id's in a type safe way, you can use Thing type.

@Serializable
data class User(
    val id: Thing<User> = unknown(),
    val username: String,
    val password: String
)

@Serializable
data class Post(
    val id: Thing<Post> = unknown(),
    val author: Thing<User>,
    val content: String,
)

val user = create("user").content(User(username = "John", password = "1234"))
val post = create("post").content(Post(author = user.id, content = "Hello World!"))

A Thing can be a Reference (an id) or a Record (a full record). You can use SurrealDB's 'FETCH' statement to fetch a record from a reference.

// By default, a Thing is a reference
val post = select<Post>("post", "123")

assert(post.author is Thing.Reference<User>)
assert(post.author.id == "user:123")

// You can fetch a record from a reference
val queryResult = query(
    """
    SELECT * FROM post WHERE author = $author
    FETCH author LIMIT 1;
    """,
    bind("author", "John")
)
val post = queryResult.first().data<List<Post>>()[0]

assert(post.author is Thing.Record<User>)

post as Thing.Record<User>
val author = post.author.data<User>()

assert(author.username == "John")
assert(author.password == "1234")

Live Queries

Basic Usage:

// You can listen to changes in a table using the observeLiveQuery function
val incoming: LiveQueryFlow<User> = db.observeLiveQuery("user")
// Each frame contains the type of change and the updated record
incoming.collect { frame ->
    when (frame) {
        is LiveQueryAction.Create -> {
            println("New record created")
        }
        is LiveQueryAction.Update -> {
            println("Record updated")
        }
        is LiveQueryAction.Delete -> {
            println("Record deleted")
        }
    }
}
// LiveQueryFlow implements to Closeable interface
incoming.close()

For more control you can use the regular query method and subscribe to updates manually:

val result = db.query(
    "LIVE SELECT * FROM user WHERE username = \$username",
    bind("username", "John")
)
val liveQueryId = result.first().data<String>()
val incoming = db.subscribe<TestClass>(liveQueryId)
db.kill(liveQueryId)
db.unsubscribe(liveQueryId)

Links