diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 53bf319..fe63bb6 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 9e55231..4cc6529 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/build.gradle.kts b/build.gradle.kts
index d8b7d94..31e69b1 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,9 +1,9 @@
plugins {
- kotlin("jvm") version "2.0.0-RC2"
+ kotlin("jvm") version "1.9.23"
}
group = "nl.astraeus"
-version = "1.0-SNAPSHOT"
+version = "1.0.0-SNAPSHOT"
repositories {
mavenCentral()
@@ -16,6 +16,7 @@ dependencies {
tasks.test {
useJUnitPlatform()
}
+
kotlin {
jvmToolchain(17)
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/nl/astraeus/persistence/Datastore.kt b/src/main/kotlin/nl/astraeus/persistence/Datastore.kt
index d9c58f1..399baa7 100644
--- a/src/main/kotlin/nl/astraeus/persistence/Datastore.kt
+++ b/src/main/kotlin/nl/astraeus/persistence/Datastore.kt
@@ -6,6 +6,7 @@ import java.io.ObjectOutputStream
import java.io.Serializable
import java.text.DecimalFormat
import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicLong
import kotlin.reflect.KClass
enum class ActionType {
@@ -14,8 +15,8 @@ enum class ActionType {
}
class TypeData(
- var nextId: Long = 1L,
- val data: MutableMap = ConcurrentHashMap(),
+ var nextId: AtomicLong = AtomicLong(1L),
+ val data: MutableMap = ConcurrentHashMap(),
) : Serializable
class Action(
@@ -43,6 +44,23 @@ class Datastore(
loadTransactions()
}
+ fun getNextId(javaClass: Class): Long {
+ if (data[javaClass] == null) {
+ synchronized(this) {
+ if (data[javaClass] == null) {
+ data[javaClass] = TypeData()
+ }
+ }
+ }
+
+ return data[javaClass]!!.nextId.getAndIncrement()
+ }
+
+ fun setMaxId(javaClass: Class, id: Long) {
+ val nextId = data.getOrPut(javaClass) { TypeData() }.nextId
+ if (nextId.get() <= id) nextId.set(id + 1)
+ }
+
override fun toString(): String {
return "Datastore(directory=${fileManager.directory}, classes=${data.keys.size}, indexes=${indexes.keys.size})"
}
@@ -95,14 +113,15 @@ class Datastore(
when (action.type) {
ActionType.STORE -> {
- if (action.obj.id == 0L) {
- action.obj.id = typeData.nextId++
- }
typeData.data[action.obj.id] = action.obj
+ if (action.obj.id >= typeData.nextId.get()) {
+ typeData.nextId.set(action.obj.id + 1)
+ }
+
for (index in indexes[action.obj::class.java]?.values ?: listOf()) {
index.remove(action.obj.id)
- index.add(action.obj as Persistable)
+ index.add(action.obj)
}
}
@@ -118,6 +137,15 @@ class Datastore(
}
}
+
+ fun count(clazz: KClass): Int {
+ val typeData = data.getOrPut(clazz.java) {
+ TypeData()
+ }
+
+ return typeData.data.size
+ }
+
fun find(clazz: KClass, id: Long): T? {
val typeData = data.getOrPut(clazz.java) {
TypeData()
@@ -148,15 +176,18 @@ class Datastore(
return indexes[kClass.java]?.get(indexName)
}
- fun storeActions(actions: MutableList) {
+ fun storeAndExecute(actions: MutableList) {
if (actions.isNotEmpty()) {
synchronized(this) {
writeTransaction(actions)
+ execute(actions)
}
}
}
private fun readTransaction(ois: ObjectInputStream) {
+ val versionNumber = ois.readInt()
+ check (versionNumber == 1) { "Unsupported version number: $versionNumber" }
val transactionNumber = ois.readLong()
nextTransactionNumber = transactionNumber + 1
val actions = ois.readObject() as MutableList
@@ -167,6 +198,8 @@ class Datastore(
val number = transactionFormatter.format(nextTransactionNumber)
val file = File(directory, "transaction-$number.trn")
ObjectOutputStream(file.outputStream()).use { oos ->
+ // version number
+ oos.writeInt(1)
oos.writeLong(nextTransactionNumber++)
oos.writeObject(actions)
}
@@ -178,6 +211,8 @@ class Datastore(
val number = transactionFormatter.format(nextTransactionNumber)
val file = File(directory, "transaction-$number.snp")
ObjectOutputStream(file.outputStream()).use { oos ->
+ // version number
+ oos.writeInt(1)
oos.writeLong(nextTransactionNumber++)
oos.writeObject(data)
oos.writeInt(indexes.size)
@@ -195,6 +230,8 @@ class Datastore(
}
private fun readSnapshot(ois: ObjectInputStream) {
+ val versionNumber = ois.readInt()
+ check (versionNumber == 1) { "Unsupported version number: $versionNumber" }
nextTransactionNumber = ois.readLong() + 1
data.clear()
data.putAll(ois.readObject() as MutableMap, TypeData>)
diff --git a/src/main/kotlin/nl/astraeus/persistence/Indexing.kt b/src/main/kotlin/nl/astraeus/persistence/Indexing.kt
index b501495..40f9117 100644
--- a/src/main/kotlin/nl/astraeus/persistence/Indexing.kt
+++ b/src/main/kotlin/nl/astraeus/persistence/Indexing.kt
@@ -5,14 +5,28 @@ import kotlin.reflect.KClass
typealias PersistableIndex = Index
+inline fun index(
+ name: String,
+ noinline value: (Persistable) -> Serializable?
+): Index = Index(
+ T::class,
+ name,
+ value
+)
+
class Index(
- kcls: KClass,
+ val cls: Class,
val name: String,
val value: (Persistable) -> Serializable?,
) : Serializable {
- val cls: Class = kcls.java
val index = mutableMapOf>()
+ constructor(
+ cls: KClass,
+ name: String,
+ value: (Persistable) -> Serializable?
+ ) : this(cls.java, name, value)
+
fun add(obj: Persistable) {
val key = value(obj)
diff --git a/src/main/kotlin/nl/astraeus/persistence/Persistable.kt b/src/main/kotlin/nl/astraeus/persistence/Persistable.kt
index 523f810..dcdb282 100644
--- a/src/main/kotlin/nl/astraeus/persistence/Persistable.kt
+++ b/src/main/kotlin/nl/astraeus/persistence/Persistable.kt
@@ -6,7 +6,7 @@ import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
-interface Persistable : Serializable, Cloneable {
+interface Persistable : Serializable {
var id: Long
var version: Long
@@ -23,3 +23,13 @@ interface Persistable : Serializable, Cloneable {
}
}
}
+
+abstract class AbstractPersistable : Persistable {
+ override fun copy(): Persistable {
+ return super.copy()
+ }
+
+ companion object {
+ private const val serialVersionUID: Long = 1L
+ }
+}
diff --git a/src/main/kotlin/nl/astraeus/persistence/Persistent.kt b/src/main/kotlin/nl/astraeus/persistence/Persistent.kt
index eddf633..a0e59fd 100644
--- a/src/main/kotlin/nl/astraeus/persistence/Persistent.kt
+++ b/src/main/kotlin/nl/astraeus/persistence/Persistent.kt
@@ -14,6 +14,10 @@ class Persistent(
) {
val datastore: Datastore = Datastore(directory, indexes)
+ fun query(block: Query.() -> T): T {
+ return block(Query(this))
+ }
+
fun transaction(block: Transaction.() -> Unit) {
var cleanup = false
if (transactions.get() == null) {
diff --git a/src/main/kotlin/nl/astraeus/persistence/Transaction.kt b/src/main/kotlin/nl/astraeus/persistence/Transaction.kt
index 255db02..51e3099 100644
--- a/src/main/kotlin/nl/astraeus/persistence/Transaction.kt
+++ b/src/main/kotlin/nl/astraeus/persistence/Transaction.kt
@@ -3,12 +3,105 @@ package nl.astraeus.nl.astraeus.persistence
import java.io.Serializable
import kotlin.reflect.KClass
-class Transaction(
+inline fun Query.count(): Int = this.count(T::class)
+inline fun Query.find(id: Long): T? = this.find(T::class, id)
+inline fun Query.search(noinline search: (T) -> Boolean): List =
+ this.search(T::class, search)
+inline fun Query.findByIndex(
+ indexName: String,
+ search: Serializable
+): List = this.findByIndex(T::class, indexName, search)
+inline fun Query.searchIndex(
+ indexName: String,
+ noinline search: (Serializable) -> Boolean,
+): List = this.searchIndex(T::class, indexName, search)
+
+inline fun Transaction.count(): Int = this.count(T::class)
+inline fun Transaction.find(id: Long): T? = this.find(T::class, id)
+inline fun Transaction.search(noinline search: (T) -> Boolean): List =
+ this.search(T::class, search)
+inline fun Transaction.findByIndex(
+ indexName: String,
+ search: Serializable
+): List = this.findByIndex(T::class, indexName, search)
+inline fun Transaction.searchIndex(
+ indexName: String,
+ noinline search: (Serializable) -> Boolean,
+): List = this.searchIndex(T::class, indexName, search)
+
+open class Query(
val persistent: Persistent,
) : Serializable {
+
+ fun count(clazz: Class): Int = count(clazz.kotlin)
+ fun count(clazz: KClass): Int = persistent.datastore.count(clazz)
+
+ fun find(clazz: Class, id: Long): T? {
+ return find(clazz.kotlin, id)
+ }
+
+ open fun find(clazz: KClass, id: Long): T? = persistent.datastore.find(clazz, id)
+
+ open fun search(clazz: KClass, search: (T) -> Boolean): List = persistent.datastore.search(clazz, search)
+
+ fun findByIndex(
+ kcls: KClass,
+ indexName: String,
+ search: Serializable
+ ): List = findByIndex(kcls.java, indexName, search)
+
+ open fun findByIndex(
+ cls: Class,
+ indexName: String,
+ search: Serializable
+ ): List {
+ val result = mutableListOf()
+ val index = persistent.datastore.findIndex(cls.kotlin, indexName)
+ ?: throw IllegalArgumentException("Index with name $indexName not found for class ${cls.simpleName}")
+
+ index.find(search).forEach { id ->
+ result.add(id as T)
+ }
+
+ return result
+ }
+
+ fun searchIndex(
+ kcls: KClass,
+ indexName: String,
+ search: (Serializable) -> Boolean,
+ ): List = searchIndex(kcls.java, indexName, search)
+
+ open fun searchIndex(
+ cls: Class,
+ indexName: String,
+ search: (Serializable) -> Boolean,
+ ): List {
+ val result = mutableListOf()
+ val index = persistent.datastore.findIndex(cls.kotlin, indexName) ?: throw IllegalArgumentException("Index not found")
+
+ index.index.keys.forEach { key ->
+ if (search(key)) {
+ index.find(key).forEach { id ->
+ result.add(id as T)
+ }
+ }
+ }
+
+ return result
+ }
+}
+
+class Transaction(
+ persistent: Persistent,
+) : Query(persistent), Serializable {
private val actions = mutableListOf()
fun store(obj: Persistable) {
+ if (obj.id == 0L) {
+ obj.id = persistent.datastore.getNextId(obj.javaClass)
+ }
+
actions.add(Action(ActionType.STORE, obj))
}
@@ -16,23 +109,23 @@ class Transaction(
actions.add(Action(ActionType.DELETE, obj))
}
- fun find(clazz: KClass, id: Long): T? {
- var result: T? = persistent.datastore.find(clazz, id)
+ fun commit() {
+ persistent.datastore.storeAndExecute(actions)
+ actions.clear()
+ }
+
+ override fun find(clazz: KClass, id: Long): T? {
+ var result = super.find(clazz, id)
for (action in actions) {
if (action.obj::class == clazz && action.obj.id == id) {
- result = when {
- action.type == ActionType.DELETE -> {
+ result = when(action.type) {
+ ActionType.DELETE -> {
null
}
-
- action.type == ActionType.STORE -> {
+ ActionType.STORE -> {
action.obj as? T
}
-
- else -> {
- result
- }
}
}
}
@@ -40,10 +133,9 @@ class Transaction(
return result
}
- fun search(clazz: KClass, search: (T) -> Boolean): List {
- val fromDatastore: List = persistent.datastore.search(clazz, search)
+ override fun search(clazz: KClass, search: (T) -> Boolean): List {
val result = mutableListOf()
- result.addAll(fromDatastore)
+ result.addAll(super.search(clazz, search))
for (obj in result) {
for (action in actions) {
@@ -61,27 +153,13 @@ class Transaction(
return result
}
- fun commit() {
- persistent.datastore.storeActions(actions)
- persistent.datastore.execute(actions)
- actions.clear()
- }
-
- fun findByIndex(
- kClass: KClass,
- indexName: String,
- search: Serializable
- ): List {
+ override fun findByIndex(cls: Class, indexName: String, search: Serializable): List {
val result = mutableListOf()
- val index = persistent.datastore.findIndex(kClass, indexName)
- ?: throw IllegalArgumentException("Index with name $indexName not found for class ${kClass.simpleName}")
-
- index.find(search).forEach { id ->
- result.add(id as T)
- }
+ val index = persistent.datastore.findIndex(cls.kotlin, indexName) ?: throw IllegalArgumentException("Index not found")
+ result.addAll(super.findByIndex(cls, indexName, search))
for (action in actions) {
- if (action.obj::class == kClass) {
+ if (action.obj::class == cls.kotlin) {
if (action.type == ActionType.DELETE) {
if (index.matches(action.obj, search)) {
result.remove(action.obj as T)
@@ -98,24 +176,13 @@ class Transaction(
return result
}
- fun searchIndex(
- kClass: KClass,
- indexName: String,
- search: (Serializable) -> Boolean,
- ): List {
+ override fun searchIndex(cls: Class, indexName: String, search: (Serializable) -> Boolean): List {
val result = mutableListOf()
- val index = persistent.datastore.findIndex(kClass, indexName) ?: throw IllegalArgumentException("Index not found")
-
- index.index.keys.forEach { key ->
- if (search(key)) {
- index.find(key).forEach { id ->
- result.add(id as T)
- }
- }
- }
+ val index = persistent.datastore.findIndex(cls.kotlin, indexName) ?: throw IllegalArgumentException("Index not found")
+ result.addAll(super.searchIndex(cls, indexName, search))
for (action in actions) {
- if (action.obj::class == kClass) {
+ if (action.obj::class == cls.kotlin) {
val indexedValue = index.value(action.obj)
if (indexedValue != null && index.matches(action.obj, indexedValue)) {
if (action.type == ActionType.DELETE) {
diff --git a/src/test/java/nl/astraeus/persistence/TestPersistenceJava.java b/src/test/java/nl/astraeus/persistence/TestPersistenceJava.java
new file mode 100644
index 0000000..e6579de
--- /dev/null
+++ b/src/test/java/nl/astraeus/persistence/TestPersistenceJava.java
@@ -0,0 +1,126 @@
+package nl.astraeus.persistence;
+
+import nl.astraeus.nl.astraeus.persistence.AbstractPersistable;
+import nl.astraeus.nl.astraeus.persistence.Index;
+import nl.astraeus.nl.astraeus.persistence.Persistable;
+import nl.astraeus.nl.astraeus.persistence.Persistent;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.List;
+
+public class TestPersistenceJava {
+
+ static class Person extends AbstractPersistable {
+ private static Long serialVersionUID = 1L;
+
+ private long id = 0;
+ private long version = 0;
+ private String name;
+ private int age;
+
+ public Person(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ @Override
+ public long getVersion() {
+ return version;
+ }
+
+ @Override
+ public void setVersion(long version) {
+ this.version = version;
+ }
+ }
+
+ @Test
+ void testPersistence() {
+ System.out.println("TestPersistenceJava.testPersistence");
+
+ Persistent persistent = new Persistent(
+ new File("data", "java-test"),
+ new Index[] {
+ new Index<>(
+ Person.class,
+ "name",
+ (p) -> ((Person)p).getName()
+ )
+ }
+ );
+
+ persistent.transaction((t) -> {
+ Person person = t.find(Person.class, 1L);
+
+ if (person != null) {
+ System.out.println(
+ "Person: "
+ + person.getName() + " is "
+ + person.getAge() + " years old."
+ );
+ }
+
+ return null;
+ });
+
+ persistent.transaction((t) -> {
+ Person person = new Person("John Doe", 42);
+
+ t.store(person);
+
+ return null;
+ });
+
+
+ persistent.transaction((t) -> {
+ List persons = t.findByIndex(
+ Person.class,
+ "name",
+ "John Doe"
+ );
+
+ for (Person person : persons) {
+ System.out.println(
+ "Person: "
+ + person.getName() + " is "
+ + person.getAge() + " years old."
+ );
+ }
+
+ return null;
+ });
+
+ persistent.snapshot();
+ persistent.getDatastore().printStatus();
+ persistent.removeOldFiles();
+ }
+
+}
diff --git a/src/test/kotlin/nl/astraeus/persistence/TestPersistence.kt b/src/test/kotlin/nl/astraeus/persistence/TestPersistence.kt
index 7a30f46..e89f137 100644
--- a/src/test/kotlin/nl/astraeus/persistence/TestPersistence.kt
+++ b/src/test/kotlin/nl/astraeus/persistence/TestPersistence.kt
@@ -1,9 +1,13 @@
package nl.astraeus.persistence
-import nl.astraeus.nl.astraeus.persistence.Index
import nl.astraeus.nl.astraeus.persistence.Persistable
import nl.astraeus.nl.astraeus.persistence.Persistent
import nl.astraeus.nl.astraeus.persistence.Reference
+import nl.astraeus.nl.astraeus.persistence.find
+import nl.astraeus.nl.astraeus.persistence.findByIndex
+import nl.astraeus.nl.astraeus.persistence.index
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.junit.jupiter.api.Assertions.assertNull
import java.io.File
import kotlin.test.Test
@@ -39,31 +43,31 @@ class TestPersistence {
println("Test persistence")
val pst = Persistent(
- directory = File("data"),
+ directory = File("data", "test-persistence"),
arrayOf(
- Index(Person::class, "name") { p -> (p as? Person)?.name ?: "" },
- Index(Person::class, "age") { p -> (p as? Person)?.age ?: -1 },
- Index(Person::class, "ageGt20") { p -> ((p as? Person)?.age ?: 0) > 20 },
- Index(Person::class, "ageGt23") { p -> ((p as? Person)?.age ?: 0) > 23 },
- Index(Person::class, "ageOnlyGt20") { p ->
+ index("name") { p -> (p as? Person)?.name ?: "" },
+ index("age") { p -> (p as? Person)?.age ?: -1 },
+ index("ageGt20") { p -> ((p as? Person)?.age ?: 0) > 20 },
+ index("ageGt23") { p -> ((p as? Person)?.age ?: 0) > 23 },
+ index("ageOnlyGt20") { p ->
if (((p as? Person)?.age ?: 0) > 20) {
true
} else {
null
}
},
- Index(Company::class, "name") { p -> (p as? Company)?.name ?: "" },
+ index("name") { p -> (p as? Company)?.name ?: "" },
)
)
pst.transaction {
- val person = find(Person::class, 1L) ?: Person(
+ val person = find(1L) ?: Person(
id = 1L,
name = "John Doe",
age = 25
)
- val company = find(Company::class, 1L) ?: Company(
+ val company = find(1L) ?: Company(
id = 1L,
name = "ACME"
)
@@ -83,14 +87,19 @@ class TestPersistence {
age = 18
))
- findByIndex(Person::class, "name", "John Doe").forEach { p ->
+ findByIndex("name", "John Doe").forEach { p ->
println("Found person by name: ${p.name} - ${p.age}")
}
- findByIndex(Person::class, "age", 23).forEach { p ->
+ val persons: List = findByIndex("age", 23)
+
+ persons.forEach { p ->
println("Found person by age: ${p.name} - ${p.age}")
}
+ val companies: List = findByIndex("name", "ACME")
+ assert(companies.isNotEmpty())
+
findByIndex(Person::class, "ageGt20", true).forEach { p ->
println("Found person by age > 20: ${p.name} - ${p.age}")
}
@@ -112,6 +121,12 @@ class TestPersistence {
assert(c2 != null)
}
+ pst.query {
+ val person = find(1L)
+
+ assertNotNull(person)
+ }
+
pst.transaction {
val person = find(Person::class, 1L)
@@ -140,14 +155,19 @@ class TestPersistence {
age = 23
)
)
- store(
- Person(
- id = 11L,
- name = "John Clown",
- age = 18
- )
+ val person = Person(
+ id = 11L,
+ name = "John Clown",
+ age = 18
)
+ store(person)
+ assertNotNull(find(Person::class, person.id))
+ delete(person)
+ assertNull(find(Person::class, person.id))
+ store(person)
+ assertNotNull(find(Person::class, person.id))
+
searchIndex(Person::class, "name") {
name -> (name as? String)?.startsWith("John") == true
}.forEach { p ->
diff --git a/src/test/kotlin/nl/astraeus/persistence/TestThreaded.kt b/src/test/kotlin/nl/astraeus/persistence/TestThreaded.kt
new file mode 100644
index 0000000..2946ccf
--- /dev/null
+++ b/src/test/kotlin/nl/astraeus/persistence/TestThreaded.kt
@@ -0,0 +1,129 @@
+package nl.astraeus.persistence
+
+import nl.astraeus.nl.astraeus.persistence.Persistable
+import nl.astraeus.nl.astraeus.persistence.Persistent
+import nl.astraeus.nl.astraeus.persistence.Reference
+import nl.astraeus.nl.astraeus.persistence.count
+import nl.astraeus.nl.astraeus.persistence.index
+import nl.astraeus.nl.astraeus.persistence.searchIndex
+import java.io.File
+import kotlin.random.Random
+import kotlin.test.Test
+
+class TestThreaded {
+
+ class Company(
+ override var id: Long = 0,
+ override var version: Long = 0,
+ val name: String,
+ val adres: String = "Blaat"
+ ) : Persistable, Cloneable {
+ //var persons: MutableList by ListReference(Person::class.java)
+
+ companion object {
+ private const val serialVersionUID: Long = 1L
+ }
+ }
+
+ class Person(
+ override var id: Long = 0,
+ override var version: Long = 0,
+ val name: String,
+ val age: Int,
+ ) : Persistable, Cloneable {
+ var company: Company by Reference(Company::class.java)
+
+ companion object {
+ private const val serialVersionUID: Long = 1L
+ }
+ }
+
+ @Test
+ fun testThreaded() {
+ println("Test threaded")
+
+ val pst = Persistent(
+ directory = File("data", "test-threaded"),
+ arrayOf(
+ index("name") { p -> (p as? Person)?.name ?: "" },
+ index("age") { p -> (p as? Person)?.age ?: -1 },
+ index("ageGt20") { p -> ((p as? Person)?.age ?: 0) > 20 },
+ index("ageGt23") { p -> ((p as? Person)?.age ?: 0) > 23 },
+ index("ageOnlyGt20") { p ->
+ if (((p as? Person)?.age ?: 0) > 20) {
+ true
+ } else {
+ null
+ }
+ },
+ index("nameAndAge") { p ->
+ val person = p as? Person
+
+ if (person == null) {
+ null
+ } else {
+ person.name to person.age
+ }
+ },
+ index("name") { p -> (p as? Company)?.name ?: "" },
+ )
+ )
+
+ val companyNames = arrayOf("Company A", "Company B", "Company C", "Company D", "Company E")
+ val names = arrayOf("John Doe", "Jane Doe", "John Smith", "Jane Smith", "John Johnson", "Jane Johnson")
+ val random = Random(System.currentTimeMillis())
+
+ val empty = pst.query {
+ count() == 0
+ }
+
+ if (empty) {
+ val runnable = {
+ repeat(10) {
+ pst.transaction {
+ val company = Company(
+ id = 0L,
+ name = companyNames[random.nextInt(companyNames.size)]
+ )
+ repeat(10) {
+ val person = Person(
+ id = 0L,
+ name = names[random.nextInt(names.size)],
+ age = random.nextInt(0, 100),
+ )
+ person.company = company
+
+ store(person)
+ }
+ }
+ }
+ }
+
+ val threads = Array(25) {
+ Thread(runnable)
+ }
+
+ for (thread in threads) {
+ thread.start()
+ }
+
+ for (thread in threads) {
+ thread.join()
+ }
+ }
+
+ pst.query {
+ searchIndex("nameAndAge") { nameAndAge ->
+ val (name, age) = nameAndAge as Pair
+
+ name.contains("mit") && age > 80
+ }.forEach { p ->
+ println("Found person by name and age: ${p.id}: ${p.name} - ${p.age}")
+ }
+ }
+
+ pst.snapshot()
+ pst.datastore.printStatus()
+ pst.removeOldFiles()
+ }
+}