diff --git a/model-api/src/commonMain/kotlin/org/modelix/model/api/INode.kt b/model-api/src/commonMain/kotlin/org/modelix/model/api/INode.kt index 660acfe113..7de9827b58 100644 --- a/model-api/src/commonMain/kotlin/org/modelix/model/api/INode.kt +++ b/model-api/src/commonMain/kotlin/org/modelix/model/api/INode.kt @@ -244,6 +244,7 @@ interface INode { // fun getParentAsFlow(): Flow = flowOf(parent).filterNotNull() fun getPropertyValueAsFlow(role: IProperty): Flow = flowOf(getPropertyValue(role)) + fun getAllPropertiesAsFlow(): Flow> = getAllProperties().asFlow() fun getAllChildrenAsFlow(): Flow = allChildren.asFlow() fun getAllReferenceTargetsAsFlow(): Flow> = getAllReferenceTargets().asFlow() fun getAllReferenceTargetRefsAsFlow(): Flow> = getAllReferenceTargetRefs().asFlow() diff --git a/model-api/src/commonMain/kotlin/org/modelix/model/api/IProperty.kt b/model-api/src/commonMain/kotlin/org/modelix/model/api/IProperty.kt index 588325fc5c..77af283dd1 100644 --- a/model-api/src/commonMain/kotlin/org/modelix/model/api/IProperty.kt +++ b/model-api/src/commonMain/kotlin/org/modelix/model/api/IProperty.kt @@ -15,6 +15,8 @@ package org.modelix.model.api +import kotlinx.serialization.Serializable + /** * Representation of a property within an [IConcept]. */ @@ -24,6 +26,7 @@ interface IProperty : IRole { } } +@Serializable data class PropertyFromName(override val name: String) : RoleFromName(), IProperty { override val isOptional: Boolean get() = throw UnsupportedOperationException() diff --git a/modelql-client/src/jvmTest/kotlin/org/modelix/modelql/client/ModelQLClientTest.kt b/modelql-client/src/jvmTest/kotlin/org/modelix/modelql/client/ModelQLClientTest.kt index 3f2b91cf96..53ba6a1fa2 100644 --- a/modelql-client/src/jvmTest/kotlin/org/modelix/modelql/client/ModelQLClientTest.kt +++ b/modelql-client/src/jvmTest/kotlin/org/modelix/modelql/client/ModelQLClientTest.kt @@ -19,6 +19,7 @@ import io.ktor.server.testing.testApplication import kotlinx.coroutines.withTimeout import org.modelix.model.api.IConceptReference import org.modelix.model.api.INode +import org.modelix.model.api.IProperty import org.modelix.model.api.PBranch import org.modelix.model.api.getRootNode import org.modelix.model.client.IdGenerator @@ -42,6 +43,7 @@ import org.modelix.modelql.core.zip import org.modelix.modelql.server.ModelQLServer import org.modelix.modelql.untyped.addNewChild import org.modelix.modelql.untyped.allChildren +import org.modelix.modelql.untyped.allProperties import org.modelix.modelql.untyped.allReferences import org.modelix.modelql.untyped.children import org.modelix.modelql.untyped.descendants @@ -64,6 +66,7 @@ class ModelQLClientTest { branch.runWrite { val module1 = rootNode.addNewChild("modules", -1, null as IConceptReference?) module1.setPropertyValue("name", "abc") + module1.setPropertyValue("description", "xyz") val model1a = module1.addNewChild("models", -1, null as IConceptReference?) model1a.setPropertyValue("name", "model1a") } @@ -95,6 +98,21 @@ class ModelQLClientTest { assertEquals(listOf("abc"), result) } + @Test + fun test_allProperties() = runTest { httpClient -> + val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build() + val result: Set> = client.query { root -> + root.children("modules").allProperties().toSet() + } + assertEquals( + setOf( + IProperty.fromName("name") to "abc", + IProperty.fromName("description") to "xyz", + ), + result, + ) + } + @Test fun test_zip() = runTest { httpClient -> val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build() diff --git a/modelql-untyped/src/commonMain/kotlin/org/modelix/modelql/untyped/AllPropertiesTraversalStep.kt b/modelql-untyped/src/commonMain/kotlin/org/modelix/modelql/untyped/AllPropertiesTraversalStep.kt new file mode 100644 index 0000000000..8745da2603 --- /dev/null +++ b/modelql-untyped/src/commonMain/kotlin/org/modelix/modelql/untyped/AllPropertiesTraversalStep.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.modelix.modelql.untyped + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.modelix.model.api.INode +import org.modelix.model.api.IProperty +import org.modelix.modelql.core.FluxTransformingStep +import org.modelix.modelql.core.IFlowInstantiationContext +import org.modelix.modelql.core.IFluxStep +import org.modelix.modelql.core.IProducingStep +import org.modelix.modelql.core.IStep +import org.modelix.modelql.core.IStepOutput +import org.modelix.modelql.core.QueryDeserializationContext +import org.modelix.modelql.core.QueryGraphDescriptorBuilder +import org.modelix.modelql.core.SerializationContext +import org.modelix.modelql.core.StepDescriptor +import org.modelix.modelql.core.StepFlow +import org.modelix.modelql.core.asStepFlow +import org.modelix.modelql.core.connect +import org.modelix.modelql.core.stepOutputSerializer + +class AllPropertiesTraversalStep : FluxTransformingStep>() { + @OptIn(ExperimentalCoroutinesApi::class) + override fun createFlow( + input: StepFlow, + context: IFlowInstantiationContext, + ): StepFlow> { + return input.flatMapConcat { it.value.getAllPropertiesAsFlow() }.asStepFlow(this) + } + + override fun getOutputSerializer(serializationContext: SerializationContext): KSerializer>> { + return serializationContext.serializer>().stepOutputSerializer(this) + } + + override fun createDescriptor(context: QueryGraphDescriptorBuilder): StepDescriptor = AllPropertiesStepDescriptor() + + @Serializable + @SerialName("untyped.allProperties") + class AllPropertiesStepDescriptor : StepDescriptor() { + override fun createStep(context: QueryDeserializationContext): IStep { + return AllPropertiesTraversalStep() + } + } + + override fun toString(): String { + return """${getProducer()}.allProperties()""" + } +} + +fun IProducingStep.allProperties(): IFluxStep> = + AllPropertiesTraversalStep().also { connect(it) } diff --git a/modelql-untyped/src/commonMain/kotlin/org/modelix/modelql/untyped/UntypedModelQL.kt b/modelql-untyped/src/commonMain/kotlin/org/modelix/modelql/untyped/UntypedModelQL.kt index 586155a732..8a8f0ded1c 100644 --- a/modelql-untyped/src/commonMain/kotlin/org/modelix/modelql/untyped/UntypedModelQL.kt +++ b/modelql-untyped/src/commonMain/kotlin/org/modelix/modelql/untyped/UntypedModelQL.kt @@ -18,6 +18,8 @@ import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass import org.modelix.model.api.INode +import org.modelix.model.api.IProperty +import org.modelix.model.api.PropertyFromName import org.modelix.model.api.RoleAccessContext import org.modelix.modelql.core.IFluxQuery import org.modelix.modelql.core.IFluxStep @@ -34,6 +36,7 @@ object UntypedModelQL { polymorphic(StepDescriptor::class) { subclass(AddNewChildNodeStep.Descriptor::class) subclass(AllChildrenTraversalStep.AllChildrenStepDescriptor::class) + subclass(AllPropertiesTraversalStep.AllPropertiesStepDescriptor::class) subclass(AllReferencesTraversalStep.Descriptor::class) subclass(ChildrenTraversalStep.ChildrenStepDescriptor::class) subclass(ConceptReferenceTraversalStep.Descriptor::class) @@ -55,6 +58,9 @@ object UntypedModelQL { subclass(SetReferenceStep.Descriptor::class) } polymorphicDefaultSerializer(INode::class) { NodeKSerializer() } + polymorphic(IProperty::class) { + subclass(PropertyFromName::class) + } } val json = Json { serializersModule = UntypedModelQL.serializersModule