Skip to content

Commit 6828245

Browse files
committed
feat: server side execution of history queries
1 parent 3b383d6 commit 6828245

File tree

13 files changed

+348
-46
lines changed

13 files changed

+348
-46
lines changed

datastructures/src/commonMain/kotlin/org/modelix/datastructures/objects/ObjectHash.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package org.modelix.datastructures.objects
22

3+
import kotlinx.serialization.Serializable
34
import org.kotlincrypto.hash.sha2.SHA256
45
import org.modelix.kotlin.utils.base64UrlEncoded
56
import kotlin.jvm.JvmInline
67

78
@JvmInline
9+
@Serializable
810
value class ObjectHash(private val hash: String) : Comparable<ObjectHash> {
911
init {
1012
require(isValidHashString(hash)) { "Not an object hash: $hash" }

model-client/src/commonMain/kotlin/org/modelix/model/client2/ModelClientV2.kt

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ import kotlinx.coroutines.flow.emptyFlow
3636
import kotlinx.coroutines.flow.flow
3737
import kotlinx.coroutines.flow.map
3838
import kotlinx.coroutines.launch
39+
import kotlinx.datetime.Instant
3940
import mu.KotlinLogging
41+
import org.modelix.datastructures.history.HistoryEntry
4042
import org.modelix.datastructures.history.HistoryIndexNode
41-
import org.modelix.datastructures.history.HistoryQueries
43+
import org.modelix.datastructures.history.HistoryInterval
4244
import org.modelix.datastructures.history.IHistoryQueries
4345
import org.modelix.datastructures.objects.IObjectGraph
4446
import org.modelix.datastructures.objects.Object
@@ -336,7 +338,78 @@ class ModelClientV2(
336338
repositoryId: RepositoryId,
337339
headVersion: ObjectHash,
338340
): IHistoryQueries {
339-
return HistoryQueries { getHistoryIndex(repositoryId, headVersion) }
341+
return object : IHistoryQueries {
342+
override suspend fun sessions(
343+
timeRange: ClosedRange<Instant>?,
344+
delay: Duration,
345+
): List<HistoryInterval> {
346+
return httpClient.prepareGet {
347+
url {
348+
takeFrom(baseUrl)
349+
appendPathSegments("repositories", repositoryId.id, "versions", headVersion.toString(), "history", "sessions")
350+
if (timeRange != null) {
351+
parameters["minTime"] = timeRange.start.epochSeconds.toString()
352+
parameters["maxTime"] = timeRange.endInclusive.epochSeconds.toString()
353+
}
354+
parameters["delay"] = delay.inWholeSeconds.toString()
355+
}
356+
}.execute { response ->
357+
response.body<List<HistoryInterval>>()
358+
}
359+
}
360+
361+
override suspend fun intervals(
362+
timeRange: ClosedRange<Instant>?,
363+
interval: Duration,
364+
): List<HistoryInterval> {
365+
return httpClient.prepareGet {
366+
url {
367+
takeFrom(baseUrl)
368+
appendPathSegments("repositories", repositoryId.id, "versions", headVersion.toString(), "history", "intervals")
369+
if (timeRange != null) {
370+
parameters["minTime"] = timeRange.start.epochSeconds.toString()
371+
parameters["maxTime"] = timeRange.endInclusive.epochSeconds.toString()
372+
}
373+
parameters["duration"] = interval.inWholeSeconds.toString()
374+
}
375+
}.execute { response ->
376+
response.body<List<HistoryInterval>>()
377+
}
378+
}
379+
380+
override suspend fun range(
381+
timeRange: ClosedRange<Instant>?,
382+
skip: Long,
383+
limit: Long,
384+
): List<HistoryEntry> {
385+
return httpClient.prepareGet {
386+
url {
387+
takeFrom(baseUrl)
388+
appendPathSegments("repositories", repositoryId.id, "versions", headVersion.toString(), "history", "entries")
389+
if (timeRange != null) {
390+
parameters["minTime"] = timeRange.start.epochSeconds.toString()
391+
parameters["maxTime"] = timeRange.endInclusive.epochSeconds.toString()
392+
}
393+
parameters["skip"] = skip.toString()
394+
parameters["limit"] = limit.toString()
395+
}
396+
}.execute { response ->
397+
response.body<List<HistoryEntry>>()
398+
}
399+
}
400+
401+
override suspend fun splitAt(splitPoints: List<Instant>): List<HistoryInterval> {
402+
return httpClient.preparePost {
403+
url {
404+
takeFrom(baseUrl)
405+
appendPathSegments("repositories", repositoryId.id, "versions", headVersion.toString(), "history", "intervals")
406+
}
407+
setBody(splitPoints.map { it.epochSeconds.toString() })
408+
}.execute { response ->
409+
response.body<List<HistoryInterval>>()
410+
}
411+
}
412+
}
340413
}
341414

342415
suspend fun getHistoryIndex(
@@ -351,7 +424,7 @@ class ModelClientV2(
351424
} else {
352425
appendPathSegments("repositories", repositoryId.id, "versions", versionHash.toString())
353426
}
354-
appendPathSegments("history-index")
427+
appendPathSegments("history", "index")
355428
}
356429
}.execute { response ->
357430
val graph = getObjectGraph(repositoryId).also { it.config = it.config.copy(lazyLoadingEnabled = true) }

model-client/src/jsMain/kotlin/org/modelix/model/client2/ClientJS.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import org.modelix.model.api.INodeReference
1717
import org.modelix.model.api.JSNodeConverter
1818
import org.modelix.model.client.IdGenerator
1919
import org.modelix.model.data.ModelData
20-
import org.modelix.model.lazy.CLVersion
2120
import org.modelix.model.lazy.RepositoryId
2221
import org.modelix.model.lazy.createObjectStoreCache
2322
import org.modelix.model.mutable.DummyIdGenerator
@@ -209,15 +208,14 @@ internal class ClientJSImpl(private val modelClient: ModelClientV2) : ClientJS {
209208
RepositoryId(repositoryId),
210209
ObjectHash(headVersion),
211210
).range(
212-
skip.toLong(),
213-
limit.toLong(),
211+
skip = skip.toLong(),
212+
limit = limit.toLong(),
214213
)
215-
.filterIsInstance<CLVersion>()
216214
.map {
217215
VersionInformationJS(
218216
it.author,
219-
it.getTimestamp()?.toJSDate(),
220-
it.getObjectHash().toString(),
217+
it.time.toJSDate(),
218+
it.versionHash.toString(),
221219
)
222220
}
223221
.toTypedArray()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.modelix.datastructures.history
2+
3+
import kotlinx.datetime.Instant
4+
import kotlinx.serialization.Serializable
5+
import org.modelix.datastructures.objects.ObjectHash
6+
7+
@Serializable
8+
data class HistoryEntry(
9+
val versionHash: ObjectHash,
10+
val time: Instant,
11+
val author: String?,
12+
)

model-datastructure/src/commonMain/kotlin/org/modelix/datastructures/history/HistoryIndexNode.kt

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ sealed class HistoryIndexNode : IObjectData {
3737
abstract fun getAllVersions(): IStream.Many<ObjectReference<CPVersion>>
3838
abstract fun getAllVersionsReversed(): IStream.Many<ObjectReference<CPVersion>>
3939
abstract fun getRange(indexRange: LongRange): IStream.Many<HistoryIndexNode>
40+
abstract fun getRange(self: Object<HistoryIndexNode>, selectedTimeRange: ClosedRange<Instant>): IStream.ZeroOrOne<Object<HistoryIndexNode>>
4041
fun getRange(indexRange: IntRange) = getRange(indexRange.first.toLong()..indexRange.last.toLong())
4142
abstract fun merge(self: Object<HistoryIndexNode>, otherObj: Object<HistoryIndexNode>): IStream.One<Object<HistoryIndexNode>>
4243

@@ -111,7 +112,7 @@ sealed class HistoryIndexNode : IObjectData {
111112
}
112113

113114
fun of(version: Object<CPVersion>): HistoryIndexNode {
114-
val time = CLVersion(version).getTimestamp() ?: Instant.Companion.fromEpochMilliseconds(0L)
115+
val time = CLVersion(version).getTimestamp() ?: Instant.fromEpochMilliseconds(0L)
115116
return HistoryIndexLeafNode(
116117
versions = listOf(version.ref),
117118
authors = setOfNotNull(version.data.author),
@@ -171,6 +172,10 @@ data class HistoryIndexLeafNode(
171172
}
172173
}
173174

175+
override fun getRange(self: Object<HistoryIndexNode>, selectedTimeRange: ClosedRange<Instant>): IStream.ZeroOrOne<Object<HistoryIndexNode>> {
176+
return if (selectedTimeRange.contains(time)) IStream.of(self) else IStream.empty()
177+
}
178+
174179
override fun getContainmentReferences(): List<ObjectReference<IObjectData>> {
175180
return versions
176181
}
@@ -382,6 +387,30 @@ data class HistoryIndexRangeNode(
382387
}.flatten()
383388
}
384389

390+
override fun getRange(self: Object<HistoryIndexNode>, selectedTimeRange: ClosedRange<Instant>): IStream.ZeroOrOne<Object<HistoryIndexNode>> {
391+
if (selectedTimeRange.contains(this.timeRange.start) && selectedTimeRange.contains(this.timeRange.endInclusive)) return IStream.of(self)
392+
if (!selectedTimeRange.intersects(this.timeRange)) return IStream.empty()
393+
return child1.requestBoth(child2) { child1Obj, child2Obj ->
394+
val range1 = child1Obj.getRange(selectedTimeRange)
395+
val range2 = child2Obj.getRange(selectedTimeRange)
396+
range1.orNull().zipWith(range2.orNull()) { range1, range2 ->
397+
if (range1 == null) {
398+
if (range2 == null) {
399+
IStream.empty()
400+
} else {
401+
IStream.of(range2)
402+
}
403+
} else {
404+
if (range2 == null) {
405+
IStream.of(range1)
406+
} else {
407+
range1.merge(range2)
408+
}
409+
}
410+
}.flatten()
411+
}.flatten()
412+
}
413+
385414
override fun serialize(): String {
386415
return "R" +
387416
Separators.LEVEL1 + firstVersion.getHashString() + Separators.LEVEL2 + lastVersion.getHashString() +
@@ -447,3 +476,7 @@ private fun IStream.One<Object<HistoryIndexNode>>.concatBalanced(otherObj: Objec
447476
private fun Object<HistoryIndexNode>.concatBalanced(otherObj: IStream.One<Object<HistoryIndexNode>>): IStream.One<Object<HistoryIndexNode>> {
448477
return otherObj.flatMapOne { this.concatBalanced(it) }
449478
}
479+
480+
fun Object<HistoryIndexNode>.getRange(selectedTimeRange: ClosedRange<Instant>): IStream.ZeroOrOne<Object<HistoryIndexNode>> {
481+
return data.getRange(this, selectedTimeRange)
482+
}

model-datastructure/src/commonMain/kotlin/org/modelix/datastructures/history/HistoryInterval.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package org.modelix.datastructures.history
22

33
import kotlinx.datetime.Instant
4+
import kotlinx.serialization.Serializable
45
import org.modelix.datastructures.objects.ObjectHash
56

67
/**
78
* A summary of a range of versions.
89
*/
10+
@Serializable
911
data class HistoryInterval(
1012
val firstVersionHash: ObjectHash,
1113
val lastVersionHash: ObjectHash,

model-datastructure/src/commonMain/kotlin/org/modelix/datastructures/history/HistoryQueries.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package org.modelix.datastructures.history
22

33
import kotlinx.datetime.Instant
44
import org.modelix.datastructures.objects.Object
5-
import org.modelix.model.IVersion
65
import org.modelix.model.lazy.CLVersion
76
import org.modelix.streams.getSuspending
87
import org.modelix.streams.iterateSuspending
@@ -98,14 +97,24 @@ class HistoryQueries(val historyIndex: suspend () -> Object<HistoryIndexNode>) :
9897
}
9998

10099
override suspend fun range(
100+
timeRange: ClosedRange<Instant>?,
101101
skip: Long,
102102
limit: Long,
103-
): List<IVersion> {
103+
): List<HistoryEntry> {
104104
val index: Object<HistoryIndexNode> = historyIndex()
105-
return index.data.getRange(skip until (limit + skip))
105+
val inTimeRange = if (timeRange == null) index else index.getRange(timeRange).orNull().getSuspending(index.graph)
106+
if (inTimeRange == null) return emptyList()
107+
return inTimeRange.data.getRange(skip until (limit + skip))
106108
.flatMapOrdered { it.getAllVersionsReversed() }
107109
.flatMapOrdered { it.resolve() }
108-
.map { CLVersion(it) }
110+
.map {
111+
val version = CLVersion(it)
112+
HistoryEntry(
113+
versionHash = it.getHash(),
114+
time = version.getTimestamp() ?: Instant.fromEpochSeconds(0L),
115+
author = version.getAuthor(),
116+
)
117+
}
109118
.toList()
110119
.getSuspending(index.graph)
111120
}

model-datastructure/src/commonMain/kotlin/org/modelix/datastructures/history/IHistoryQueries.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.modelix.datastructures.history
22

33
import kotlinx.datetime.Instant
4-
import org.modelix.model.IVersion
54
import kotlin.time.Duration
65
import kotlin.time.Duration.Companion.minutes
76

@@ -37,9 +36,10 @@ interface IHistoryQueries {
3736
* is complete.
3837
*/
3938
suspend fun range(
39+
timeRange: ClosedRange<Instant>? = null,
4040
skip: Long = 0L,
4141
limit: Long = 1000L,
42-
): List<IVersion>
42+
): List<HistoryEntry>
4343

4444
/**
4545
* Split the history at the specified [splitPoints]. The split point itself is part of the interval that ends at

0 commit comments

Comments
 (0)