Skip to content

Commit

Permalink
Move query framework to inkt subproject
Browse files Browse the repository at this point in the history
  • Loading branch information
ty1824 committed Feb 27, 2023
1 parent d4a51f3 commit b99028b
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 86 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
group = dev.dialector

# Modify this for a custom version for local publishing (will not apply to CI publishing builds)
version =
version = inkt

kotlinVersion = 1.8.10
kspVersion = 1.8.10-1.0.9
Expand Down
13 changes: 13 additions & 0 deletions inkt/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# inkt

An incremental query framework for Kotlin

Heavily inspired by [Salsa](https://github.com/salsa-rs/salsa)

## Quick Start

See [DefinedQueryExample.kt](src/main/kotlin/dev/dialector/inkt/query/example/DefinedQueryExample.kt) for an example of how to define queries and use the database.
A KSP-based code generator will eventually replace manual definition of query database implementations.



95 changes: 95 additions & 0 deletions inkt/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
plugins {
kotlin("jvm")
id("org.jetbrains.kotlinx.kover")
id("maven-publish")
signing
}

dependencies {
implementation(kotlin("reflect"))
}

kotlin {
explicitApiWarning()
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(8))
}
}

java {
withJavadocJar()
withSourcesJar()
}

tasks.withType<Test> {
useJUnitPlatform()
}

kover {
xmlReport {
onCheck.set(true)
}
}

publishing {
repositories {
maven {
name = "OSSRH"
setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = System.getenv("SONATYPE_USERNAME")
password = System.getenv("SONATYPE_PASSWORD")
}
}
maven {
name = "GitHubPackages"
setUrl("https://maven.pkg.github.com/ty1824/dialector")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
repositories.forEach { println((it as MavenArtifactRepository).url)}
publications {
register<MavenPublication>("default") {
from(components["java"])
pom {
name.set("inkt")
description.set("Incremental computation framework for Kotlin")
url.set("http://dialector.dev")
licenses {
license {
name.set("GPL-3.0")
url.set("https://opensource.org/licenses/GPL-3.0")
}
}
issueManagement {
system.set("Github")
url.set("https://github.com/ty1824/dialector/issues")
}
scm {
connection.set("https://github.com/ty1824/dialector.git")
url.set("https://github.com/ty1824/dialector")
}
developers {
developer {
name.set("Tyler Hodgkins")
email.set("[email protected]")
}
}
}
}
}
}

signing {
val gpgPrivateKey = System.getenv("GPG_SIGNING_KEY")
if (!gpgPrivateKey.isNullOrBlank()) {
useInMemoryPgpKeys(
gpgPrivateKey,
System.getenv("GPG_SIGNING_PASSPHRASE")
)
sign(publishing.publications)
}
}
23 changes: 23 additions & 0 deletions inkt/src/main/kotlin/dev/dialector/inkt/QueryData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.dialector.inkt

internal data class QueryKey<K, V>(val queryDef: DatabaseQuery<K, V>, val key: K)

internal sealed interface Value<V> {
var value: V
var changedAt: Int
}

internal data class InputValue<V>(override var value: V, override var changedAt: Int) : Value<V>

internal data class DerivedValue<V>(
override var value: V,
val dependencies: MutableList<QueryKey<*, *>>,
var verifiedAt: Int,
override var changedAt: Int
) : Value<V>

internal class QueryFrame<K>(
val queryKey: QueryKey<K, *>,
var maxRevision: Int = 0,
val dependencies: MutableList<QueryKey<*, *>> = mutableListOf()
)
Original file line number Diff line number Diff line change
@@ -1,78 +1,15 @@
package dev.dialector.query
package dev.dialector.inkt

import kotlin.reflect.KClass

public annotation class QueryGroup

public annotation class Query

public annotation class Input

public annotation class Tracked

public annotation class DatabaseDef(vararg val groups: KClass<*>)

public interface DatabaseDefinition {
public val queryDefinitions: Array<DatabaseQuery<*, *>>
}

internal data class QueryKey<K, V>(val queryDef: DatabaseQuery<K, V>, val key: K)

internal sealed interface Value<V> {
var value: V
var changedAt: Int
}

internal data class InputValue<V>(override var value: V, override var changedAt: Int) : Value<V>

internal data class DerivedValue<V>(
override var value: V,
val dependencies: MutableList<QueryKey<*, *>>,
var verifiedAt: Int,
override var changedAt: Int
) : Value<V>

internal class QueryFrame<K>(
val queryKey: QueryKey<K, *>,
var maxRevision: Int = 0,
val dependencies: MutableList<QueryKey<*, *>> = mutableListOf()
)

public interface DatabaseQuery<K, V> {
public val name: String
public fun get(key: K): V
}

internal fun <K, V> DatabaseQuery<K, V>.createMap(): MutableMap<K, Value<V>> = mutableMapOf()

public data class InputQuery<K, V>(
override val name: String,
private val query: (K) -> V
) : DatabaseQuery<K, V> {
override fun get(key: K): V = query(key)
}

public fun <K, V> inputQuery(name: String, query: (K) -> V): InputQuery<K, V> = InputQuery(name, query)
public fun <K, V> derivedQuery(name: String, query: (K) -> V): DerivedQuery<K, V> = DerivedQuery(name, query)

public data class DerivedQuery<K, V>(
override val name: String,
private val query: (K) -> V
) : DatabaseQuery<K, V> {
override fun get(key: K): V = query(key)
}

public class QueryDatabase(
public val definitions: List<DatabaseQuery<*, *>>
) {
public class QueryDatabase(public val definitions: List<DatabaseQuery<*, *>>) {
private val storage: Map<DatabaseQuery<*, *>, MutableMap<*, *>> = definitions.associateWith { it.createMap() }

private var currentRevision = 0

private val storage: Map<DatabaseQuery<*, *>, MutableMap<*, *>> = definitions.associateWith { it.createMap() }

private val currentlyActiveQuery: MutableList<QueryFrame<*>> = mutableListOf()

private fun <K, V> getQueryStorage(query: DatabaseQuery<K, V>): MutableMap<K, Value<V>> = storage[query] as MutableMap<K, Value<V>>
@Suppress("UNCHECKED_CAST")
private fun <K, V> getQueryStorage(query: DatabaseQuery<K, V>): MutableMap<K, Value<V>> =
storage[query] as MutableMap<K, Value<V>>

private fun <K, V> get(queryKey: QueryKey<K, V>): Value<V>? = getQueryStorage(queryKey.queryDef)[queryKey.key]

Expand Down
47 changes: 47 additions & 0 deletions inkt/src/main/kotlin/dev/dialector/inkt/QueryDsl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package dev.dialector.inkt

import kotlin.reflect.KClass

public annotation class QueryGroup

public annotation class Query

public annotation class Input

public annotation class Tracked

public annotation class DatabaseDef(vararg val groups: KClass<*>)

public class NoInputDefinedException(message: String) : RuntimeException(message)

public interface DatabaseQuery<K, V> {
public val name: String
public fun get(key: K): V
}

/**
* This function is used to create a typesafe storage map for a query.
* The receiver is necessary to properly infer types, even though IntelliJ says otherwise.
*/
internal fun <K, V> DatabaseQuery<K, V>.createMap(): MutableMap<K, Value<V>> = mutableMapOf()

public data class InputQuery<K, V>(
override val name: String,
private val query: (K) -> V
) : DatabaseQuery<K, V> {
override fun get(key: K): V = query(key)
}

public data class DerivedQuery<K, V>(
override val name: String,
private val query: (K) -> V
) : DatabaseQuery<K, V> {
override fun get(key: K): V = query(key)
}

public fun <K, V> inputQuery(
name: String,
query: (K) -> V = { throw NoInputDefinedException("No input exists for query $name($it)") }
): InputQuery<K, V> = InputQuery(name, query)

public fun <K, V> derivedQuery(name: String, query: (K) -> V): DerivedQuery<K, V> = DerivedQuery(name, query)
Original file line number Diff line number Diff line change
@@ -1,23 +1,60 @@
package dev.dialector.query

package dev.dialector.inkt.example

import dev.dialector.inkt.DerivedQuery
import dev.dialector.inkt.InputQuery
import dev.dialector.inkt.NoInputDefinedException
import dev.dialector.inkt.QueryDatabase
import dev.dialector.inkt.derivedQuery
import dev.dialector.inkt.inputQuery

/**
* Inkt queries begin as interface definitions. It is easiest to provide default implementations
* as the query database can then invoke the super method, though this approach is not required.
*/
internal interface HelloWorld {
/**
* Input queries are not required to have an implementation - these will generally depend on
* externally-provided inputs. This function corresponds to a [String] -> [String] mapping
*
* A query database will need to provide means to set these input values.
*/
fun inputString(key: String): String?

/**
* Derived queries are composed from other queries - in this case, [length] returns the length
* of the input string for the given key.
*/
fun length(key: String): Int? {
println("Recomputing length for $key")
return inputString(key)?.length
}

/**
* This is an example of a derived query that depends on another derived query.
*/
fun longest(keys: Set<String>): String? {
println("recomputing longest")
return keys.maxByOrNull { length(it) ?: -1 }?.let { inputString(it) }
}
}

public class NoInputDefinedException(message: String) : RuntimeException(message)

internal class DefinedQueryExample : HelloWorld {
private val inputString: InputQuery<String, String?> = inputQuery("inputString") { throw NoInputDefinedException("Input not provided for inputString($it)") }
/**
* A query database implementation is an implementation of one or more query interfaces, a series of
* [DatabaseQuery] definitions that represent the implemented queries, along with an internal
* [QueryDatabase] to provide incremental behavior.
*
* The [DatabaseQuery] definitions are typesafe handles for the query functionality and must be passed
* to the [QueryDatabase] constructor. This is to allow for internal optimization of query storage.
*
* The [QueryDatabase.inputQuery] and [QueryDatabase.derivedQuery] methods handle fetching different types of data
* from the database and ensuring the queries are incrementalized.
*
* The [QueryDatabase.setInput] method handles assigning input data for input queries.
*/
internal class DefinedQueryExampleDatabase : HelloWorld {
private val inputString: InputQuery<String, String?> = inputQuery("inputString") {
throw NoInputDefinedException("Input not provided for inputString($it)")
}
private val length: DerivedQuery<String, Int?> = derivedQuery("length") { super.length(it) }
private val longest: DerivedQuery<Set<String>, String?> = derivedQuery("longest") { super.longest(it) }
private val database = QueryDatabase(listOf(inputString, length, longest))
Expand All @@ -32,33 +69,31 @@ internal class DefinedQueryExample : HelloWorld {
}

internal fun main() {
val db = DefinedQueryExample()
db.setInputString("foo", "hello world")
val db = DefinedQueryExampleDatabase()
db.setInputString("foo", "hello")

println("foo: Length is ${db.length("foo")}")
println("foo: Length is ${db.length("foo")} shouldn't recompute!")

db.setInputString("bar", "bai")
db.setInputString("bar", "bye")

println("foo: Length is ${db.length("foo")} shouldn't recompute!")
println("bar: Length is ${db.length("bar")}")
println("bar: Length is ${db.length("bar")} shouldn't recompute!")

db.setInputString("foo", "oh wow this is very long")
db.setInputString("foo", "longer")

println("foo: Length is ${db.length("foo")}")
println("bar: Length is ${db.length("bar")} shouldn't recompute!")

println("longest {foo, bar} is: ${db.longest(setOf("foo", "bar"))}")
println("longest {foo, bar} is: ${db.longest(setOf("foo", "bar"))}")
// db.print()
db.setInputString("bar", "super long to verify some stuff hereeeeeeeeee")
// db.print()

db.setInputString("bar", "even longer")
println("longest {foo, bar} is: ${db.longest(setOf("foo", "bar"))}")
// db.print()
println("longest {foo, bar} is: ${db.longest(setOf("foo", "bar"))}")

db.setInputString("baz", "the longest there ever was, because it's criticalllll")
db.setInputString("baz", "definitely the longest")
println("longest {foo, bar, baz} is ${db.longest(setOf("foo", "bar", "baz"))}")
println("longest {foo, bar, baz} is ${db.longest(setOf("foo", "bar", "baz"))}")

Expand Down
Loading

0 comments on commit b99028b

Please sign in to comment.