Skip to content

Commit

Permalink
Support simple typed value reference. This so they can be used in ind…
Browse files Browse the repository at this point in the history
…ices and simple comparisons.
  • Loading branch information
jurmous committed Feb 25, 2024
1 parent 42e59ab commit 313335c
Show file tree
Hide file tree
Showing 14 changed files with 345 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package maryk.core.processors.datastore.matchers
import maryk.core.processors.datastore.matchers.FuzzyMatchResult.MATCH
import maryk.core.processors.datastore.matchers.FuzzyMatchResult.NO_MATCH
import maryk.core.processors.datastore.matchers.FuzzyMatchResult.OUT_OF_RANGE
import maryk.core.properties.references.IsPropertyReference
import maryk.lib.extensions.compare.compareToWithOffsetLength

/** Defines a matcher for a qualifier. */
Expand All @@ -13,6 +14,7 @@ sealed class IsQualifierMatcher
* Optionally set [referencedQualifierMatcher] to match values behind a reference
*/
class QualifierExactMatcher(
val reference: IsPropertyReference<*, *, *>?,
val qualifier: ByteArray,
val referencedQualifierMatcher: ReferencedQualifierMatcher? = null
) : IsQualifierMatcher() {
Expand All @@ -30,6 +32,7 @@ enum class FuzzyMatchResult {
* Optionally set [referencedQualifierMatcher] to match values behind a reference
*/
class QualifierFuzzyMatcher(
val reference: IsPropertyReference<*, *, *>?,
val qualifierParts: List<ByteArray>,
val fuzzyMatchers: List<IsFuzzyMatcher>,
val referencedQualifierMatcher: ReferencedQualifierMatcher? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import maryk.core.properties.references.AnyOutPropertyReference
import maryk.core.properties.references.CanHaveComplexChildReference
import maryk.core.properties.references.IsPropertyReference
import maryk.core.properties.references.ReferenceType.TYPE
import maryk.core.properties.references.SimpleTypedValueReference
import maryk.core.properties.references.TypeReference
import maryk.core.properties.references.TypedValueReference
import maryk.core.properties.types.TypedValue
Expand Down Expand Up @@ -65,6 +66,13 @@ interface IsMultiTypeDefinition<E : TypeEnum<T>, T: Any, in CX : IsPropertyConte
fun typedValueRef(type: E, parentReference: CanHaveComplexChildReference<*, *, *, *>?) =
TypedValueReference(type, this, parentReference)

/**
* Creates a reference referring to a value of [type] of multi type below [parentReference]
* so reference can be strongly typed
*/
fun simpleTypedValueRef(type: E, parentReference: CanHaveComplexChildReference<*, *, *, *>?) =
SimpleTypedValueReference(type, this, parentReference)

/** Creates a reference referring to any type of multi type below [parentReference] */
@Suppress("UNCHECKED_CAST")
fun typeRef(parentReference: AnyOutPropertyReference? = null) =
Expand All @@ -79,13 +87,17 @@ interface IsMultiTypeDefinition<E : TypeEnum<T>, T: Any, in CX : IsPropertyConte
parentReference: CanHaveComplexChildReference<*, *, *, *>? = null
): IsPropertyReference<Any, *, *> {
val index = initIntByVar(reader)
if (index != 0) throw UnexpectedValueException("Index in multi type reference other than 0 ($index) is not supported")
// index value 8 means it is a VarInt with tag 1
if (index != 0 && index != 8) throw UnexpectedValueException("Index in multi type reference other than 0/8 ($index) is not supported")
val typeIndex = initUIntByVar(reader)
@Suppress("UNCHECKED_CAST")
return if (typeIndex == 0u) {
this.typeRef(
parentReference as CanHaveComplexChildReference<TypedValue<E, T>, IsMultiTypeDefinition<E, T, *>, *, *>?
) as IsPropertyReference<Any, *, *>
} else if (index == 8) {
val type = this.typeEnum.resolve(typeIndex) ?: throw UnexpectedValueException("Type $typeIndex is not known")
simpleTypedValueRef(type, parentReference) as IsPropertyReference<Any, *, *>
} else {
val type = this.typeEnum.resolve(typeIndex) ?: throw UnexpectedValueException("Type $typeIndex is not known")
typedValueRef(type, parentReference) as IsPropertyReference<Any, *, *>
Expand Down Expand Up @@ -132,6 +144,16 @@ interface IsMultiTypeDefinition<E : TypeEnum<T>, T: Any, in CX : IsPropertyConte
typedValueRef(type, parentReference) as IsPropertyReference<Any, *, *>
}
}
'>' -> {
if (name.length == 1) {
throw ParseException("Not supported")
} else {
val type = this.typeEnum.resolve(name.substring(1))
?: throw UnexpectedValueException("Type ${name.substring(1)} is not known")
@Suppress("UNCHECKED_CAST")
simpleTypedValueRef(type, parentReference) as IsPropertyReference<Any, *, *>
}
}
else -> throw ParseException("Unknown Type type $name[0]")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import maryk.core.properties.references.AnyPropertyReference
import maryk.core.properties.references.CanHaveComplexChildReference
import maryk.core.properties.references.IsPropertyReference
import maryk.core.properties.references.MultiTypePropertyReference
import maryk.core.properties.references.SimpleTypedValueReference
import maryk.core.properties.references.TypeReference
import maryk.core.properties.references.TypedValueReference
import maryk.core.properties.types.TypedValue
Expand Down Expand Up @@ -45,6 +46,8 @@ data class MultiTypeDefinitionWrapper<E : TypeEnum<T>, T: Any, TO : Any, in CX :
atomic(null)
val typeValueRefCache: AtomicRef<Array<IsPropertyReference<*, *, *>>?> =
atomic(null)
val simpleTypeValueRefCache: AtomicRef<Array<IsPropertyReference<*, *, *>>?> =
atomic(null)

override fun ref(parentRef: AnyPropertyReference?) = cacheRef(parentRef) {
MultiTypePropertyReference(this, parentRef)
Expand All @@ -62,10 +65,21 @@ data class MultiTypeDefinitionWrapper<E : TypeEnum<T>, T: Any, TO : Any, in CX :
}
}

private fun simpleTypedValueReference(type: E, parentReference: AnyPropertyReference?): SimpleTypedValueReference<E, T, CX> = this.ref(parentReference).let { ref ->
cacheRef(ref, simpleTypeValueRefCache, { (it.parentReference as MultiTypePropertyReference<*, *, *, *, *>).parentReference === parentReference && it.type == type}) {
super.simpleTypedValueRef(type, ref)
}
}

/** For quick notation to get a [type] reference */
infix fun refAtType(type: E): (AnyOutPropertyReference?) -> TypedValueReference<E, T, CX> =
{ this.typedValueReference(type, it) }


/** For quick notation to get a [type] reference */
infix fun simpleRefAtType(type: E): (AnyOutPropertyReference?) -> SimpleTypedValueReference<E, T, CX> =
{ this.simpleTypedValueReference(type, it as? CanHaveComplexChildReference<*, *, *, *>) }

/** For quick notation to get an any type reference */
fun refToType(): (AnyOutPropertyReference?) -> TypeReference<E, T, CX> = {
@Suppress("UNCHECKED_CAST")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface MultiTypeEnum<T: Any>: TypeEnum<T> {
companion object {
internal operator fun invoke(index: UInt, name: String, definition: IsUsableInMultiType<out Any, *>?, alternativeNames: Set<String>? = null) = object : IndexedEnumImpl<IndexedEnumComparable<Any>>(index, alternativeNames), MultiTypeEnum<Any> {
init {
// Only allows higher than 0 because 0 is used to identify type references
require(index > 0u) { "Only indices of 1 and higher are allowed" }
}
override val name = name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,13 @@ interface IsPropertyReference<T : Any, out D : IsPropertyDefinition<T>, V : Any>

val resultingMatcher = if (fuzzyMatchers.isEmpty()) {
QualifierExactMatcher(
reference = this,
qualifier = bytes.toByteArray(),
referencedQualifierMatcher = childMatcher
)
} else {
QualifierFuzzyMatcher(
reference = this,
qualifierParts = byteArrays,
fuzzyMatchers = fuzzyMatchers,
referencedQualifierMatcher = childMatcher
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package maryk.core.properties.references

import maryk.core.exceptions.StorageException
import maryk.core.exceptions.UnexpectedValueException
import maryk.core.extensions.bytes.calculateVarByteLength
import maryk.core.extensions.bytes.writeVarBytes
import maryk.core.models.IsRootDataModel
import maryk.core.properties.IsPropertyContext
import maryk.core.properties.definitions.IsChangeableValueDefinition
import maryk.core.properties.definitions.IsMultiTypeDefinition
import maryk.core.properties.definitions.IsSimpleValueDefinition
import maryk.core.properties.definitions.index.IndexKeyPartType
import maryk.core.properties.definitions.index.toReferenceStorageByteArray
import maryk.core.properties.enum.MultiTypeEnum
import maryk.core.properties.enum.TypeEnum
import maryk.core.properties.exceptions.RequiredException
import maryk.core.properties.types.Bytes
import maryk.core.properties.types.TypedValue
import maryk.core.protobuf.ProtoBuf
import maryk.core.protobuf.WireType.VAR_INT
import maryk.core.protobuf.WriteCacheReader
import maryk.core.protobuf.WriteCacheWriter
import maryk.core.query.pairs.ReferenceValuePair
import maryk.core.values.IsValuesGetter

/**
* Reference to a simple value by [type] [E] on [parentReference]
* Can be a reference to a type below a multi type wrapper or for like multi types within lists
*/
class SimpleTypedValueReference<E : TypeEnum<T>, T: Any, in CX : IsPropertyContext> internal constructor(
val type: E,
multiTypeDefinition: IsMultiTypeDefinition<E, T, CX>,
parentReference: CanHaveComplexChildReference<*, *, *, *>?
) : CanHaveSimpleChildReference<
T,
IsSimpleValueDefinition<T, CX>,
CanHaveComplexChildReference<*, *, *, *>,
TypedValue<E, T>
>(
multiTypeDefinition.definition(type) as IsSimpleValueDefinition<T, CX>,
parentReference
),
IsIndexablePropertyReference<T>,
IsPropertyReferenceWithParent<T, IsSimpleValueDefinition<T, CX>, CanHaveComplexChildReference<*, *, *, *>, TypedValue<E, T>> {

override val indexKeyPartType = IndexKeyPartType.Reference
override val referenceStorageByteArray by lazy { Bytes(this.toReferenceStorageByteArray()) }

override val completeName: String
get() = this.parentReference?.let {
"${it.completeName}.>${type.name}"
} ?: ">${type.name}"

override fun resolveFromAny(value: Any) = (value as? TypedValue<*, *>)?.let {
@Suppress("UNCHECKED_CAST")
if (it.type == type) it.value as T? else null
} ?: throw UnexpectedValueException("Expected typed value to get value by reference")

/** Convenience infix method to create reference [value] pairs */
@Suppress("UNCHECKED_CAST")
infix fun with(value: T): ReferenceValuePair<T> =
ReferenceValuePair(this as IsPropertyReference<T, IsChangeableValueDefinition<T, IsPropertyContext>, *>, value)

override fun calculateTransportByteLength(cacher: WriteCacheWriter): Int {
val parentLength = parentReference?.calculateTransportByteLength(cacher) ?: 0
return parentLength + 1 + type.index.calculateVarByteLength()
}

override fun writeTransportBytes(cacheGetter: WriteCacheReader, writer: (byte: Byte) -> Unit) {
this.parentReference?.writeTransportBytes(cacheGetter, writer)
ProtoBuf.writeKey(1u, VAR_INT, writer)
type.index.writeVarBytes(writer)
}

override fun calculateSelfStorageByteLength() = 0

override fun writeSelfStorageBytes(writer: (byte: Byte) -> Unit) {
// The storage bytes are purely for getting the stored value and cannot be converted back to reference
}

override fun resolve(values: TypedValue<E, T>): T? = if (values.type == type) values.value else null

override fun getValue(values: IsValuesGetter): T {
@Suppress("UNCHECKED_CAST")
val typedValue = values[parentReference as IsPropertyReference<Any, *, *>]
?: throw RequiredException(parentReference)
return if (typedValue is TypedValue<*, *>) {
if (typedValue.type == type) {
@Suppress("UNCHECKED_CAST")
typedValue.value as T
} else throw RequiredException(this)
} else if (typedValue is MultiTypeEnum<*>) {
throw RequiredException(this)
} else throw StorageException("Unknown type for $typedValue")
}

override fun isForPropertyReference(propertyReference: AnyPropertyReference): Boolean =
propertyReference == this

override fun toQualifierStorageByteArray() = parentReference?.toStorageByteArray()

override fun calculateReferenceStorageByteLength(): Int {
return this.parentReference?.calculateStorageByteLength() ?: 0
}

override fun writeReferenceStorageBytes(writer: (Byte) -> Unit) {
this.parentReference?.writeStorageBytes(writer)
}

override fun isCompatibleWithModel(dataModel: IsRootDataModel): Boolean =
dataModel.compatibleWithReference(this)

override fun readStorageBytes(length: Int, reader: () -> Byte): T =
comparablePropertyDefinition.readStorageBytes(length, reader)

override fun calculateStorageByteLength(value: T): Int =
comparablePropertyDefinition.calculateStorageByteLength(value)

override fun writeStorageBytes(value: T, writer: (byte: Byte) -> Unit) {
comparablePropertyDefinition.writeStorageBytes(value, writer)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class QualifierMatcherTest {
@Test
fun exactMatch() {
val qualifierMatcher = QualifierExactMatcher(
reference = null,
initByteArrayByHex("BBBB")
)
expect(0) { qualifierMatcher.compareTo(initByteArrayByHex("BBBB"), 0) }
Expand All @@ -23,6 +24,7 @@ class QualifierMatcherTest {
@Test
fun fuzzyMatch() {
val qualifierMatcher = QualifierFuzzyMatcher(
reference = null,
listOf(
initByteArrayByHex("bbbb"),
initByteArrayByHex("cccc")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package maryk.core.properties.references

import maryk.core.exceptions.UnexpectedValueException
import maryk.core.properties.types.invoke
import maryk.core.protobuf.WriteCache
import maryk.lib.extensions.toHex
import maryk.test.ByteCollector
import maryk.test.models.MarykTypeEnum.T1
import maryk.test.models.Measurement
import maryk.test.models.MeasurementType
import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertSame
import kotlin.test.expect

class SimpleTypedValueReferenceTest {
private val typedValueReference =
Measurement { measurement simpleRefAtType MeasurementType.Number }

@Test
fun cacheReferenceTest() {
assertSame(typedValueReference, Measurement { measurement simpleRefAtType MeasurementType.Number })
}

@Test
fun getValueFromMap() {
val typedValueWrong = T1("string")

assertFailsWith<UnexpectedValueException> {
this.typedValueReference.resolveFromAny(typedValueWrong)
}

val typedValue = MeasurementType.Number(15.toShort())

expect(15.toShort()) { this.typedValueReference.resolveFromAny(typedValue) }

assertFailsWith<UnexpectedValueException> {
this.typedValueReference.resolveFromAny("wrongInput")
}
}

@Test
fun testCompleteName() {
expect("measurement.>Number") { typedValueReference.completeName }
}

@Test
fun writeAndReadStringValue() {
expect(typedValueReference) { Measurement.getPropertyReferenceByName(typedValueReference.completeName) }
}

@Test
fun writeAndReadTransportBytes() {
val bc = ByteCollector()
val cache = WriteCache()

bc.reserve(
typedValueReference.calculateTransportByteLength(cache)
)
typedValueReference.writeTransportBytes(cache, bc::write)

expect("020803") { bc.bytes!!.toHex() }

expect(typedValueReference) { Measurement.getPropertyReferenceByBytes(bc.size, bc::read) }
}

@Test
fun writeAndReadStorageBytes() {
val bc = ByteCollector()

bc.reserve(
typedValueReference.calculateStorageByteLength()
)
typedValueReference.writeStorageBytes(bc::write)

expect("11") { bc.bytes!!.toHex() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import maryk.core.processors.datastore.matchers.QualifierFuzzyMatcher
import maryk.core.properties.definitions.IsSimpleValueDefinition
import maryk.core.properties.definitions.IsStorageBytesEncodable
import maryk.core.properties.enum.MultiTypeEnum
import maryk.core.properties.references.SimpleTypedValueReference
import maryk.core.properties.references.TypeReference
import maryk.core.query.filters.And
import maryk.core.query.filters.Equals
Expand Down Expand Up @@ -216,7 +217,14 @@ private fun convertToSingleColumnValueFilter(it: ReferenceValuePair<Any>, compar
val value = it.value

@Suppress("UNCHECKED_CAST")
val valueComparator = when (it.reference) {
val valueComparator = when (val comparedRef = it.reference) {
is SimpleTypedValueReference<*, *, *> -> {
val prependBytes = ByteArray(comparedRef.type.index.calculateVarIntWithExtraInfoByteSize())
var writeIndex = 0
comparedRef.type.index.writeVarIntWithExtraInfo(TypeIndicator.SimpleTypeIndicator.byte) { prependBytes[writeIndex++] = it }
val valueBytes = (comparedRef.comparablePropertyDefinition as IsStorageBytesEncodable<Any>).toStorageBytes(value, *prependBytes)
BinaryComparator(valueBytes)
}
is TypeReference<*, *, *> -> {
var index = 0
val type = value as MultiTypeEnum<*>
Expand Down
Loading

0 comments on commit 313335c

Please sign in to comment.