From 80d8d27a843c8f7aedcd10ab11dbb7b857789f78 Mon Sep 17 00:00:00 2001 From: Marty Pitt Date: Fri, 20 Feb 2026 06:34:32 +0000 Subject: [PATCH 1/6] fix(io): move PropelAuthApiKeyValidator to a different dispatcher - address starvation issue spotted in prod --- .../security/authentication/oidc/PropelAuthApiKeyValidator.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cockpit-core/src/main/java/com/orbitalhq/cockpit/core/security/authentication/oidc/PropelAuthApiKeyValidator.kt b/cockpit-core/src/main/java/com/orbitalhq/cockpit/core/security/authentication/oidc/PropelAuthApiKeyValidator.kt index 0c8d12608..bf7c67d63 100644 --- a/cockpit-core/src/main/java/com/orbitalhq/cockpit/core/security/authentication/oidc/PropelAuthApiKeyValidator.kt +++ b/cockpit-core/src/main/java/com/orbitalhq/cockpit/core/security/authentication/oidc/PropelAuthApiKeyValidator.kt @@ -19,6 +19,7 @@ import org.springframework.stereotype.Component import org.springframework.web.client.RestClient import org.springframework.web.client.toEntity import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers import java.time.Instant @ConditionalOnProperty("vyne.security.openIdp.roles.format", havingValue = CloudPropelAuthClaimsExtractor.PropelAuthJwtKind, matchIfMissing = false) @@ -83,7 +84,7 @@ class PropelAuthApiKeyValidator( // sink.success(JwtAuthenticationToken( // jwt // )) - } + }.subscribeOn(Schedulers.boundedElastic()) as Mono } From 167fa309b6d0ea67ce54e6d1e1c81897729cb97a Mon Sep 17 00:00:00 2001 From: Marty Pitt Date: Fri, 20 Feb 2026 06:41:36 +0000 Subject: [PATCH 2/6] fix: address DefaultDispatcher starvation in TypedObjectFactory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All runBlocking {} call sites in TypedObjectFactory (handleTypeNotFound, evaluateLambdaExpression, queryForParentType) now execute on a dedicated blockingBridgeDispatcher backed by an unbounded cached thread pool. Under concurrent load, multiple factory instances building simultaneously would exhaust DefaultDispatcher workers — each blocked in runBlocking waiting for coroutines that themselves needed DefaultDispatcher threads to execute. The system would stall until restart. This is a holding measure. The correct fix is to make TypedObjectFactory fully async (suspend functions throughout). More detail in original issue. See ORB-1077 --- .../TypedObjectFactoryDeadlockSpec.kt | 118 ++++++++++++++++++ .../orbitalhq/models/TypedObjectFactory.kt | 92 +++++++++++--- 2 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 taxiql-query-engine/src/test/java/com/orbitalhq/TypedObjectFactoryDeadlockSpec.kt diff --git a/taxiql-query-engine/src/test/java/com/orbitalhq/TypedObjectFactoryDeadlockSpec.kt b/taxiql-query-engine/src/test/java/com/orbitalhq/TypedObjectFactoryDeadlockSpec.kt new file mode 100644 index 000000000..969221cfc --- /dev/null +++ b/taxiql-query-engine/src/test/java/com/orbitalhq/TypedObjectFactoryDeadlockSpec.kt @@ -0,0 +1,118 @@ +package com.orbitalhq + +import com.orbitalhq.models.json.parseJson +import com.orbitalhq.models.json.parseJsonCollection +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.withTimeout + +class TypedObjectFactoryDeadlockSpec : DescribeSpec({ + + describe("A dead lock that occurred in TypedObjectFactory") { + it("should run a query designed to produce a deadlock, without producing a deadlock") { + // Constrain Default dispatcher to make deadlock reproducible + val threadCount = Runtime.getRuntime().availableProcessors() + + val (vyne, stubService) = testVyne( + """ + type TradeId inherits String + type Isin inherits String + type UnresolvableType inherits String // intentionally never provided + + model Trade { + id: TradeId + isin: Isin + } + + model EnrichedTrade { + id: TradeId + isin: Isin + mystery: UnresolvableType? // will trigger handleTypeNotFound + } + + @Datasource + service TradeService { + operation getTrades(): Trade[] + } + """.trimIndent() + ) + + // Generate enough records to saturate the Default thread pool + val trades = (1..(threadCount * 3)).map { i -> + """{ "id": "trade$i", "isin": "US00000$i" }""" + }.joinToString(",", "[", "]") + + stubService.addResponse( + "getTrades", + vyne.parseJson("Trade[]", trades) + ) + + // withTimeout will expose a deadlock that would otherwise hang the test suite forever + withTimeout(10_000) { + val result = vyne.query( + """ + find { Trade[] } as EnrichedTrade[] + """.trimIndent() + ) + result.rawObjects() // force collection, triggering concurrent object construction + } + } + + + it("should run another query designed to produce a deadlock, wihtout producing a deadlock") { + val (vyne, stubService) = testVyne( + """ + type Isin inherits String + type Ric inherits String + + model InputModel { + ric: Ric + } + + model OutputModel { + isin: Isin + } + + parameter model RicRequest { + ric: Ric + } + + model RicResponse { + isin: Isin + } + + @Datasource + service InputService { + operation getInputs(): InputModel[] + } + + service IsinLookupService { + operation lookup(@RequestBody req: RicRequest): RicResponse + } + """.trimIndent() + ) + + // Generate enough rows to exhaust the DefaultDispatcher thread pool + // The pool size is typically Runtime.availableProcessors(), so generate more than that + val rowCount = Runtime.getRuntime().availableProcessors() * 3 + + stubService.addResponse( + "getInputs", vyne.parseJsonCollection( + "InputModel[]", + (1..rowCount).joinToString(",", "[", "]") { """{"ric": "ric$it"}""" } + ) + ) + + // IsinLookupService is NOT stubbed - it will trigger handleTypeNotFound + // which calls runBlocking { findType() } + + val result = withTimeout(10_000) { + val queryResult = vyne.query("find { InputModel[] } as OutputModel[]") + queryResult.rawObjects() + } + + // If we get here without timeout, the fix works + result.size shouldBe rowCount + } + } +}) diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt index fb05a5817..997ae8bda 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt @@ -19,6 +19,7 @@ import com.orbitalhq.schemas.* import com.orbitalhq.schemas.taxi.toVyneQualifiedName import com.orbitalhq.utils.timeBucket import com.orbitalhq.utils.xtimed +import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking @@ -32,6 +33,7 @@ import lang.taxi.types.FormatsAndZoneOffset import mu.KotlinLogging import org.apache.commons.csv.CSVRecord import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.Executors import java.util.stream.Collectors @@ -85,10 +87,61 @@ class TypedObjectFactory( ) : EvaluationValueSupplier, ValueProjector, OperationInvokerContainer { - companion object { + private companion object { private val logger = KotlinLogging.logger {} + + /* + * THREADING MODEL - READ BEFORE MODIFYING + * More detail in ORB-1077 + * + * Background + * ---------- + * TypedObjectFactory uses per-field `lazy` delegates (LazyThreadSafetyMode.SYNCHRONIZED) + * to handle cross-field dependencies during expression evaluation (e.g. `total = quantity * price`). + * This was intentional: ConcurrentHashMap.computeIfAbsent is not re-entrant, so lazy was chosen + * to allow safe recursive field access within a single factory instance. + * + * The problem + * ----------- + * Several methods (handleTypeNotFound, evaluateLambdaExpression, queryForParentType) bridge + * async operations back to synchronous code via runBlocking { }. When these are invoked from + * a DefaultDispatcher worker thread, the worker blocks waiting for a coroutine to complete. + * That coroutine also needs a DefaultDispatcher thread to run. Under concurrent load (multiple + * factory instances building simultaneously), all DefaultDispatcher workers become blocked, + * the coroutines they're waiting on can never be scheduled, and the system stalls indefinitely. + * This was confirmed by a production thread dump showing all DefaultDispatcher workers parked + * in runBlocking { } inside handleTypeNotFound, each holding a lazy lock on a different + * factory instance. + * + * Short-term fix + * -------------- + * All runBlocking { } call sites in this class use a dedicated blockingBridgeDispatcher + * (unbounded cached thread pool) rather than inheriting the calling thread's dispatcher. + * This moves the blocking onto threads outside the DefaultDispatcher pool, freeing coroutine + * workers to complete the async operations being waited on. + * + * Why this is still a sticking plaster + * ------------------------------------- + * The calling thread is still blocked — we've just moved the blockage off the bounded pool. + * Under sustained high concurrency the bridge pool will grow unboundedly (one thread per + * concurrent blocked call). This is acceptable in practice because it is bounded by upstream + * request concurrency, but it is not a principled fix. + * + * The correct long-term fix + * ------------------------- + * Make the entire call chain properly async: replace the lazy + runBlocking pattern with + * suspend functions (or Deferred), propagating suspension all the way up through getValue(), + * buildField(), getOrBuild(), and buildAsync(). This eliminates thread blocking entirely — + * coroutines suspend and yield their workers back to the pool while waiting. This requires + * changes to the classes that call into TypedObjectFactory and is a non-trivial refactor, + * hence the bridge approach in the interim. + */ + private val blockingBridgeDispatcher = Executors.newCachedThreadPool( + Thread.ofVirtual().name("typed-obj-factory-bridge-", 0).factory() + ).asCoroutineDispatcher() } + private val buildSpecProvider = TypedInstancePredicateFactory() private val valueReader = ValueReader() @@ -147,9 +200,13 @@ class TypedObjectFactory( private val attributesToMap = type.attributes + private val fieldInitializers: Map> by lazy { attributesToMap.map { (attributeName, field) -> - attributeName to lazy { + + // Do not use locks here, as it leads to thread starvation and deadlock + // when we hit handleTypeNotFound() + attributeName to lazy(LazyThreadSafetyMode.NONE) { val fieldValue = xtimed("build field $attributeName ${field.typeDisplayName}") { @@ -167,7 +224,8 @@ class TypedObjectFactory( // Do not start a projection if the type we're projecting to is the same as the value we have. // This happens if the projection was processed internally already within the object construction - if (field.fieldProjection != null && !field.fieldProjection.projectedType.isAssignableTo(fieldValue.type.taxiType, + if (field.fieldProjection != null && !field.fieldProjection.projectedType.isAssignableTo( + fieldValue.type.taxiType, // Structural compatability is useful when assigning a value, but not // when determining if we want to project. // At projection time, the goal is to create an object that looks exactly like the provided spec. @@ -178,7 +236,9 @@ class TypedObjectFactory( // model Person { Name } // If we project EnhancedPerson to Person, we want the Age field dropped, even though // the two types match from a structuralCompatability perspective. - permitStructurallyCompatible = false)) { + permitStructurallyCompatible = false + ) + ) { val projection = xtimed("Project field $attributeName") { projectField( field, @@ -556,7 +616,9 @@ class TypedObjectFactory( ) } val searchFailureBehaviour: QueryFailureBehaviour = QueryFailureBehaviour.defaultBehaviour(type) - return runBlocking { + // Be careful - this blocking behaviour can cause thread starvation. + // See comment on blockingBridgeDispatcher. The fix is to move this code to be fully async + return runBlocking(blockingBridgeDispatcher) { logger.debug { "Initiating query to search for closed type ${type.name.shortDisplayName}" } queryForType(type, searchFailureBehaviour, AlwaysGoodSpec, attributeName = null) } @@ -683,13 +745,9 @@ class TypedObjectFactory( } return when { queryIfNotFound && inPlaceQueryEngine != null -> { - // TODO : Remove the blocking behaviour here. - // TypedObjectFactory has always been blocking (but - // historically hasn't invoked services), so leaving as - // blocking when introducing type expressions with lookups. - // However, in future, we need to mkae the TypedObjectFactory - // async up the chain. - runBlocking { + // Be careful - this blocking behaviour can cause thread starvation. + // See comment on blockingBridgeDispatcher. The fix is to move this code to be fully async + runBlocking(blockingBridgeDispatcher) { if (requestedType.isStream) { error("Cannot perform an inner search for a stream") } @@ -886,7 +944,10 @@ class TypedObjectFactory( // wrt/ coroutines vs flux atm. val argumentExpressionReturnType = schema.type(argumentTypeExpression.type) val scopedFacts = this.getCurrentScopedFacts() - val typedInstance = runBlocking { + + // Be careful - this blocking behaviour can cause thread starvation. + // See comment on blockingBridgeDispatcher. The fix is to move this code to be fully async + val typedInstance = runBlocking(blockingBridgeDispatcher) { // If we're doing nested traversal of lambda expressions, // there could be scoped facts we've been passed that will // be needed as inputs @@ -1311,7 +1372,10 @@ class TypedObjectFactory( } val (additionalFacts, additionalScope) = getFactsInScopeForSearch() - val buildResult = runBlocking { + + // Be careful - this blocking behaviour can cause thread starvation. + // See comment on blockingBridgeDispatcher. The fix is to move this code to be fully async + val buildResult = runBlocking(blockingBridgeDispatcher) { logger.debug { "Initiating query to search for attribute $attributeName (${searchType.name.shortDisplayName})" } inPlaceQueryEngine.withAdditionalFacts(additionalFacts, additionalScope) .findType( From 62254bf0c9fba078e492083b81f57a491c5a7944 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Feb 2026 07:42:17 +0000 Subject: [PATCH 3/6] refactor: convert TypedObjectFactory call chain from runBlocking to suspend Remove all runBlocking calls from TypedObjectFactory and its entire call chain to eliminate DefaultDispatcher thread pool starvation under concurrent load. The blockingBridgeDispatcher workaround is also removed. Key changes: - TypedObjectFactory: all methods now suspend, lazy field initializers replaced with HashMap cache, parallelStream replaced with coroutineScope - Interfaces made suspend: EvaluationValueSupplier.getValue/readAccessor, ValueProjector.project, AccessorHandler.process, InPlaceQueryEngine.evaluate, FunctionInvoker.invoke, DeferredTypedInstance.evaluate - AccessorReader: all read/evaluate methods now suspend - All 24 NullSafeInvoker implementations: doInvoke now suspend - All 7 direct NamedFunctionInvoker implementations: invoke now suspend - Collection stdlib functions (filter, map, fold, reduce, groupBy) rewritten from non-suspend lambdas to explicit for loops - External callers (TypedObject.fromValue, Vyne.evaluate, CSV parsers) use runBlocking at the call site as bridge pattern https://claude.ai/code/session_016NsNPSNfFCADAi4RsyAD4F --- .../functions/scanner/BoundFunction.kt | 2 +- .../ResultStreamAuthorizationDecorator.kt | 2 +- .../src/main/java/com/orbitalhq/Vyne.kt | 18 +- .../java/com/orbitalhq/query/ObjectBuilder.kt | 4 +- .../java/com/orbitalhq/query/QueryContext.kt | 4 +- .../CollectionProjectionBuilder.kt | 15 +- .../query/graph/edges/ParameterFactory.kt | 45 +- ...wareOperationInvocationServiceDecorator.kt | 2 +- .../query/policyManager/PolicyEvaluator.kt | 8 +- .../projection/LocalProjectionProvider.kt | 2 +- .../formats/csv/CsvCollectionParser.kt | 25 +- .../com/orbitalhq/models/AccessorReader.kt | 55 ++- .../orbitalhq/models/DeferredExpression.kt | 6 +- .../models/EvaluationValueSupplier.kt | 10 +- .../orbitalhq/models/FactBagValueSupplier.kt | 8 +- .../orbitalhq/models/InPlaceQueryEngine.kt | 4 +- .../ProjectionFunctionScopeEvaluator.kt | 7 +- .../java/com/orbitalhq/models/TypedObject.kt | 34 +- .../orbitalhq/models/TypedObjectFactory.kt | 397 +++++++----------- .../com/orbitalhq/models/ValueProjector.kt | 2 +- .../ConditionalFieldSetEvaluator.kt | 2 +- .../models/conditional/WhenBlockEvaluator.kt | 8 +- .../models/functions/FunctionRegistry.kt | 2 +- .../orbitalhq/models/functions/Functions.kt | 8 +- .../functions/stdlib/BaseMathFunction.kt | 2 +- .../models/functions/stdlib/Collections.kt | 12 +- .../models/functions/stdlib/EnumFunctions.kt | 4 +- .../models/functions/stdlib/Functional.kt | 48 +-- .../functions/stdlib/ObjectFunctions.kt | 6 +- .../models/functions/stdlib/Strings.kt | 38 +- .../functions/stdlib/collections/Append.kt | 2 +- .../CollectionFilteringFunction.kt | 30 +- .../CollectionPredicateFunctions.kt | 17 +- .../functions/stdlib/collections/Filter.kt | 2 +- .../stdlib/collections/FilterEach.kt | 2 +- .../functions/stdlib/collections/First.kt | 2 +- .../functions/stdlib/collections/IfEmpty.kt | 2 +- .../functions/stdlib/collections/IndexOf.kt | 2 +- .../stdlib/collections/Intersection.kt | 2 +- .../stdlib/collections/JoinToString.kt | 2 +- .../functions/stdlib/collections/ListOf.kt | 2 +- .../functions/stdlib/collections/OrEmpty.kt | 2 +- .../functions/stdlib/collections/Single.kt | 2 +- .../functions/stdlib/collections/SingleBy.kt | 8 +- .../stdlib/dates/BaseDateMathFunction.kt | 2 +- .../models/functions/stdlib/dates/Now.kt | 2 +- .../functions/stdlib/dates/ParseDate.kt | 2 +- .../models/functions/stdlib/errors/Throw.kt | 2 +- .../models/functions/stdlib/math/Average.kt | 2 +- .../models/functions/stdlib/math/Round.kt | 2 +- .../functions/stdlib/transform/Convert.kt | 21 +- .../functions/stdlib/transform/ToRawType.kt | 2 +- .../caching/AbstractMergingStateStore.kt | 28 +- .../orbitalhq/formats/csv/CsvImporterUtil.kt | 20 +- 54 files changed, 410 insertions(+), 530 deletions(-) diff --git a/function-loading/functions-binding/src/main/java/com/orbitalhq/functions/scanner/BoundFunction.kt b/function-loading/functions-binding/src/main/java/com/orbitalhq/functions/scanner/BoundFunction.kt index 967625135..3ff570aea 100644 --- a/function-loading/functions-binding/src/main/java/com/orbitalhq/functions/scanner/BoundFunction.kt +++ b/function-loading/functions-binding/src/main/java/com/orbitalhq/functions/scanner/BoundFunction.kt @@ -44,7 +44,7 @@ class BoundFunction( override val functionName: QualifiedName = QualifiedName(namespace, name) - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/pipelines/stream-engine/src/main/java/com/orbitalhq/pipelines/jet/streams/ResultStreamAuthorizationDecorator.kt b/pipelines/stream-engine/src/main/java/com/orbitalhq/pipelines/jet/streams/ResultStreamAuthorizationDecorator.kt index 8f437928f..f018e6011 100644 --- a/pipelines/stream-engine/src/main/java/com/orbitalhq/pipelines/jet/streams/ResultStreamAuthorizationDecorator.kt +++ b/pipelines/stream-engine/src/main/java/com/orbitalhq/pipelines/jet/streams/ResultStreamAuthorizationDecorator.kt @@ -47,7 +47,7 @@ class ResultStreamAuthorizationDecorator( return stream.mapNotNull { value -> // first, parse back to a typed instance val valueAsTypedInstance = TypedInstance.from(instanceType, value, querySchema, source = Provided) - val evaluatedTypedInstance = policyEvaluator.evaluate(valueAsTypedInstance, queryContext, executionScope) + val evaluatedTypedInstance = kotlinx.coroutines.runBlocking { policyEvaluator.evaluate(valueAsTypedInstance, queryContext, executionScope) } // Convert back to a raw object, since that's what we started with evaluatedTypedInstance.toRawObject() } diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt index c4d0628e0..c8566a52f 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt @@ -423,14 +423,16 @@ class Vyne( // to our predicate. // That's wrong, as generally the collection will be the input, especially if our predciate / expression // is a contains(...) - val buildResult = TypedObjectFactory( - expressionType, - queryContext.facts, - schemaWithType, - source = Provided, - inPlaceQueryEngine = queryContext, - functionResultCache = queryContext.functionResultCache - ).build() + val buildResult = kotlinx.coroutines.runBlocking { + TypedObjectFactory( + expressionType, + queryContext.facts, + schemaWithType, + source = Provided, + inPlaceQueryEngine = queryContext, + functionResultCache = queryContext.functionResultCache + ).build() + } return buildResult } diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/ObjectBuilder.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/ObjectBuilder.kt index 14cdf31b7..1d43365be 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/ObjectBuilder.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/ObjectBuilder.kt @@ -192,7 +192,7 @@ class ObjectBuilder( } } - private fun evaluateExpressionType(targetType: Type): TypedInstance { + private suspend fun evaluateExpressionType(targetType: Type): TypedInstance { return TypedObjectFactory( targetType, // Note: This used to be an empty list, @@ -232,7 +232,7 @@ class ObjectBuilder( * items: Thing[] by [ThingToIterate[] with { CustomerName }] * }[] */ - private fun buildCollectionWithProjectionExpression(targetType: Type): TypedInstance { + private suspend fun buildCollectionWithProjectionExpression(targetType: Type): TypedInstance { val collectionProjectionBuilder = accessorReaders.filterIsInstance().firstOrNull() ?: error("No CollectionProjectionBuilder was present in the acessor readers") return collectionProjectionBuilder.process( diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryContext.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryContext.kt index 64781cd6b..3e0c01a06 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryContext.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryContext.kt @@ -596,7 +596,7 @@ data class QueryContext( } - override fun evaluate(expression: Expression, facts: FactBag, source: DataSource): TypedInstance { + override suspend fun evaluate(expression: Expression, facts: FactBag, source: DataSource): TypedInstance { return TypedObjectFactory( schema.type(expression.returnType), facts.withAdditionalScopedFacts(this.scopedFacts, schema), @@ -607,7 +607,7 @@ data class QueryContext( ).evaluateExpression(expression) } - override fun evaluate(expression: Expression, value: TypedInstance, source: DataSource): TypedInstance { + override suspend fun evaluate(expression: Expression, value: TypedInstance, source: DataSource): TypedInstance { return TypedObjectFactory( schema.type(expression.returnType), value, diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/collections/CollectionProjectionBuilder.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/collections/CollectionProjectionBuilder.kt index 68c7b0883..ac0b69313 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/collections/CollectionProjectionBuilder.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/collections/CollectionProjectionBuilder.kt @@ -10,7 +10,6 @@ import com.orbitalhq.schemas.taxi.toVyneType import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.runBlocking import lang.taxi.accessors.CollectionProjectionExpressionAccessor import lang.taxi.types.Arrays import kotlin.reflect.KClass @@ -29,7 +28,7 @@ class CollectionProjectionBuilder(val queryContext: QueryContext) : override val accessorType: KClass = CollectionProjectionExpressionAccessor::class - override fun process( + override suspend fun process( accessor: CollectionProjectionExpressionAccessor, objectFactory: EvaluationValueSupplier, schema: Schema, @@ -56,13 +55,11 @@ class CollectionProjectionBuilder(val queryContext: QueryContext) : } ?: emptyList() val targetMemberType = targetType.collectionType ?: targetType - val buildResults = runBlocking { - collectionToIterate.asFlow() - .flatMapConcat { collectionMember -> - queryContext.only(listOf(collectionMember) + additionalScopeFacts) - .build(TypeQueryExpression(targetMemberType)).results - }.toList() - } + val buildResults = collectionToIterate.asFlow() + .flatMapConcat { collectionMember -> + queryContext.only(listOf(collectionMember) + additionalScopeFacts) + .build(TypeQueryExpression(targetMemberType)).results + }.toList() val builtCollection = TypedCollection.from(buildResults, source) return builtCollection diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/graph/edges/ParameterFactory.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/graph/edges/ParameterFactory.kt index 0b6862c8f..63c91d721 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/graph/edges/ParameterFactory.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/graph/edges/ParameterFactory.kt @@ -29,7 +29,6 @@ import com.orbitalhq.utils.log import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.runBlocking import lang.taxi.expressions.Expression import lang.taxi.services.operations.constraints.Constraint import lang.taxi.types.PrimitiveType @@ -119,33 +118,27 @@ class ParameterFactory { // } // experiment: Are there any collections in the context we can iterate? - val builtFromCollection = context.facts.filter { it.type.isCollection } - .asSequence() - .mapNotNull { collection: TypedInstance -> - require(collection is TypedCollection) { "Expected to recieve a TypedCollection" } - var exceptionThrown = false - val built = collection - .takeWhile { !exceptionThrown } - .mapNotNull { member -> - runBlocking { - try { - val memberOnlyQueryContext = context.only(member) - val builtFromMember = - attemptToConstruct(paramType.collectionType!!, memberOnlyQueryContext, operation, member) - builtFromMember - } catch (e: UnresolvedOperationParametersException) { - exceptionThrown = true - null - } - } - } - if (exceptionThrown) { - null - } else { - built + var builtFromCollection: List? = null + for (collection in context.facts.filter { it.type.isCollection }) { + require(collection is TypedCollection) { "Expected to recieve a TypedCollection" } + var exceptionThrown = false + val built = mutableListOf() + for (member in collection) { + if (exceptionThrown) break + try { + val memberOnlyQueryContext = context.only(member) + val builtFromMember = + attemptToConstruct(paramType.collectionType!!, memberOnlyQueryContext, operation, member) + built.add(builtFromMember) + } catch (e: UnresolvedOperationParametersException) { + exceptionThrown = true } } - .firstOrNull() + if (!exceptionThrown) { + builtFromCollection = built + break + } + } if (builtFromCollection != null) { return TypedCollection.from(builtFromCollection, MixedSources) } else { diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/policyManager/PolicyAwareOperationInvocationServiceDecorator.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/policyManager/PolicyAwareOperationInvocationServiceDecorator.kt index 32e1dc841..8f8e17e07 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/policyManager/PolicyAwareOperationInvocationServiceDecorator.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/policyManager/PolicyAwareOperationInvocationServiceDecorator.kt @@ -43,7 +43,7 @@ class PolicyAwareOperationInvocationServiceDecorator(private val operationServic } - private fun applyPolicyInstruction( + private suspend fun applyPolicyInstruction( value: TypedInstance, context: QueryContext, executionScope: ExecutionScope diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/policyManager/PolicyEvaluator.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/policyManager/PolicyEvaluator.kt index bb97eb124..022ddfa55 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/policyManager/PolicyEvaluator.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/policyManager/PolicyEvaluator.kt @@ -47,7 +47,7 @@ class PolicyEvaluator { private val logger = KotlinLogging.logger {} } - fun evaluate(instance: TypedInstance, context: QueryContext, operationScope: ExecutionScope): TypedInstance { + suspend fun evaluate(instance: TypedInstance, context: QueryContext, operationScope: ExecutionScope): TypedInstance { val schema = context.schema val policyType = getPolicyType(instance, context) val policies = findPolicies(schema, policyType) @@ -60,7 +60,7 @@ class PolicyEvaluator { return evaluationResult } - private fun evaluate( + private suspend fun evaluate( applicablePolicies: List, instance: TypedInstance, context: QueryContext, @@ -111,7 +111,7 @@ class PolicyEvaluator { } } - private fun evaluatePolicyAtPath( + private suspend fun evaluatePolicyAtPath( policyWithPath: PolicyWithPath, rootInstance: TypedInstance, context: QueryContext, @@ -136,7 +136,7 @@ class PolicyEvaluator { } } - private fun evaluatePolicy( + private suspend fun evaluatePolicy( policy: Policy, rule: PolicyRule, instance: TypedInstance, diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/projection/LocalProjectionProvider.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/projection/LocalProjectionProvider.kt index d1ca56acf..a20434fb5 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/projection/LocalProjectionProvider.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/projection/LocalProjectionProvider.kt @@ -294,7 +294,7 @@ class LocalProjectionProvider : ProjectionProvider { * * This code returns the actual fact, selecting the value from the inbound value. */ - private fun buildScopedProjectionFacts( + private suspend fun buildScopedProjectionFacts( projection: Projection, emittedResult: TypedInstance, context: QueryContext diff --git a/vyne-core-types/src/main/java/com/orbitalhq/formats/csv/CsvCollectionParser.kt b/vyne-core-types/src/main/java/com/orbitalhq/formats/csv/CsvCollectionParser.kt index 061949a53..b09f67181 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/formats/csv/CsvCollectionParser.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/formats/csv/CsvCollectionParser.kt @@ -26,17 +26,20 @@ class CsvCollectionParser(val content: String, } fun parse(): TypedInstance { - val typedInstances = content.lineSequence() - .drop(1) // Ignore the header - .filter { it.isNotBlank() && it.isNotEmpty() } - .map { TypedObjectFactory(memberType,it,schema, - source = source, - functionRegistry = functionRegistry, - inPlaceQueryEngine = inPlaceQueryEngine, - formatSpecs = emptyList(), - metadata = metadata - ).build() } - .toList() + val typedInstances = kotlinx.coroutines.runBlocking { + content.lineSequence() + .drop(1) // Ignore the header + .filter { it.isNotBlank() && it.isNotEmpty() } + .map { line -> TypedObjectFactory(memberType,line,schema, + source = source, + functionRegistry = functionRegistry, + inPlaceQueryEngine = inPlaceQueryEngine, + formatSpecs = emptyList(), + metadata = metadata + ) } + .toList() + .map { it.build() } + } return TypedCollection.from(typedInstances, source) } diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/AccessorReader.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/AccessorReader.kt index 32fbc804d..98cac97cb 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/AccessorReader.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/AccessorReader.kt @@ -29,7 +29,6 @@ import com.orbitalhq.utils.timeBucket import com.orbitalhq.utils.xtimed import kotlinx.coroutines.flow.toCollection import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.runBlocking import lang.taxi.accessors.Accessor import lang.taxi.accessors.ColumnAccessor import lang.taxi.accessors.ConditionalAccessor @@ -85,7 +84,7 @@ object Parsers { interface AccessorHandler { val accessorType: KClass - fun process( + suspend fun process( accessor: T, objectFactory: EvaluationValueSupplier, schema: Schema, @@ -130,7 +129,7 @@ class AccessorReader( ) } - fun read( + suspend fun read( value: Any, targetTypeRef: QualifiedName, accessor: Accessor, @@ -145,7 +144,7 @@ class AccessorReader( return read(value, targetType, accessor, schema, nullValues, source, format, nullable, allowContextQuerying) } - fun read( + suspend fun read( value: Any, targetType: Type, accessor: Accessor, @@ -436,7 +435,7 @@ class AccessorReader( } } - private fun evaluateOperationExpression( + private suspend fun evaluateOperationExpression( value: Any, type: Type, accessor: OperationInvocationExpression, @@ -461,10 +460,8 @@ class AccessorReader( parameter to scopedFact.fact } require(operation.operationKind != OperationKind.Stream) { "Operation ${operation.name} returns a stream, which is not supported for in-place operation calls. Try querying with stream { ${operation.returnType.typeParameters[0].paramaterizedName} }"} - val result = runBlocking { - objectFactory.invokeOperation(service, operation, parameters) + val result = objectFactory.invokeOperation(service, operation, parameters) .toList() - } val accessorResult = when { operation.returnType.isCollection -> TypedCollection.arrayOf(operation.returnType, result) result.size == 1 -> result.single() @@ -474,7 +471,7 @@ class AccessorReader( return accessorResult } - private fun evaluateNegatedExpression( + private suspend fun evaluateNegatedExpression( value: Any, type: Type, accessor: NegatedExpression, @@ -514,7 +511,7 @@ class AccessorReader( } } - private fun evaluateProjectingExpression( + private suspend fun evaluateProjectingExpression( value: Any, targetType: Type, accessor: ProjectingExpression, @@ -577,7 +574,7 @@ class AccessorReader( } - private fun readModelAttributeSelector( + private suspend fun readModelAttributeSelector( accessor: MemberTypeReferenceExpression, allowContextQuerying: Boolean, schema: Schema, @@ -672,7 +669,7 @@ class AccessorReader( * - A TypeExpression: Foo (requesting a type) * - An ExpressionType: Foo = Thing + 2 (A type that has an expression) */ - private fun evaluateExpressionType( + private suspend fun evaluateExpressionType( requestedType: Type, source: Any ): TypedInstance { @@ -687,7 +684,7 @@ class AccessorReader( * - A request for an instance of that type, looked up from the objectFactory (if the return type is T) * - A request for a reference to the type itself, for reflection purposes (if the return type is Type) */ - private fun readTypeExpression( + private suspend fun readTypeExpression( accessor: TypeExpression, allowContextQuerying: Boolean, targetType: Type, @@ -719,7 +716,7 @@ class AccessorReader( return result } - private fun readScopedReferenceSelector(value: Any, accessor: ArgumentSelector): TypedInstance { + private suspend fun readScopedReferenceSelector(value: Any, accessor: ArgumentSelector): TypedInstance { val scopedInstance = if (value is FactBag) { value.getScopedFactOrNull(accessor.scope)?.fact @@ -745,7 +742,7 @@ class AccessorReader( return result } - private fun evaluateFieldReference( + private suspend fun evaluateFieldReference( targetType: Type, selectors: List, source: DataSource, @@ -815,7 +812,7 @@ class AccessorReader( } } - private fun evaluateFunctionAccessor( + private suspend fun evaluateFunctionAccessor( value: Any, targetType: Type, schema: Schema, @@ -882,7 +879,7 @@ class AccessorReader( return functionResult } - private fun collateInputsForAccessor( + private suspend fun collateInputsForAccessor( accessor: CallableInvocationExpression, schema: Schema, value: Any, @@ -973,7 +970,7 @@ class AccessorReader( return declaredInputs } - private fun invokeFunctionBody( + private suspend fun invokeFunctionBody( varArgsParam: Parameter?, varArgsValue: List, declaredInputs: List, @@ -1015,7 +1012,7 @@ class AccessorReader( } } - private fun evaluateFunctionExpressionAccessor( + private suspend fun evaluateFunctionExpressionAccessor( value: Any, targetType: Type, schema: Schema, @@ -1060,7 +1057,7 @@ class AccessorReader( } } - private fun evaluateReadFunctionAccessor( + private suspend fun evaluateReadFunctionAccessor( value: Any, targetType: Type, schema: Schema, @@ -1094,7 +1091,7 @@ class AccessorReader( return TypedInstance.from(targetType, builder.toString(), schema, source = source) } - private fun evaluateConditionalAccessor( + private suspend fun evaluateConditionalAccessor( value: Any, targetType: Type, schema: Schema, @@ -1140,7 +1137,7 @@ class AccessorReader( } } - private fun parseDestructured( + private suspend fun parseDestructured( value: Any, targetType: Type, schema: Schema, @@ -1237,7 +1234,7 @@ class AccessorReader( } - fun evaluate( + suspend fun evaluate( value: Any, returnType: Type, expression: Expression, @@ -1417,7 +1414,7 @@ class AccessorReader( } - private fun evaluateMemberAccessExpression( + private suspend fun evaluateMemberAccessExpression( value: Any, returnType: Type, expression: MemberAccessExpression, @@ -1435,7 +1432,7 @@ class AccessorReader( ) } - private fun evaluateExtensionFunctionExpression( + private suspend fun evaluateExtensionFunctionExpression( value: Any, returnType: Type, expression: ExtensionFunctionExpression, @@ -1457,7 +1454,7 @@ class AccessorReader( ) } - private fun evaluateLambdaExpression( + private suspend fun evaluateLambdaExpression( value: Any, returnType: Type, expression: LambdaExpression, @@ -1471,7 +1468,7 @@ class AccessorReader( return evaluate(value, returnType, expression.expression, schema, nullValues, dataSource, format) } - private fun evaluateFunctionExpression( + private suspend fun evaluateFunctionExpression( value: Any, returnType: Type, expression: FunctionExpression, @@ -1494,11 +1491,11 @@ class AccessorReader( ) } - private fun evaluateTypeExpression(expression: TypeExpression, schema: Schema): TypedInstance { + private suspend fun evaluateTypeExpression(expression: TypeExpression, schema: Schema): TypedInstance { return objectFactory.getValue(expression.type.toVyneQualifiedName(), queryIfNotFound = true) } - private fun evaluateOperatorExpression( + private suspend fun evaluateOperatorExpression( returnType: Type, expression: OperatorExpression, schema: Schema, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/DeferredExpression.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/DeferredExpression.kt index dc3ca5c2d..882a3336e 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/DeferredExpression.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/DeferredExpression.kt @@ -35,7 +35,7 @@ class DeferredExpression( return equality.isEqualTo(other) } - override fun evaluate( + override suspend fun evaluate( input: TypedInstance, dataSource: DataSource, evaluationValueSupplier: EvaluationValueSupplier, @@ -94,7 +94,7 @@ data class DeferredProjection( return equality.isEqualTo(other) } - override fun evaluate( + override suspend fun evaluate( input: TypedInstance, dataSource: DataSource, evaluationValueSupplier: EvaluationValueSupplier, @@ -132,7 +132,7 @@ interface DeferredTypedInstance : TypedInstance { TODO("Not yet implemented") } - fun evaluate( + suspend fun evaluate( input: TypedInstance, dataSource: DataSource, evaluationValueSupplier: EvaluationValueSupplier, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/EvaluationValueSupplier.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/EvaluationValueSupplier.kt index 2b3b18895..017bb437f 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/EvaluationValueSupplier.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/EvaluationValueSupplier.kt @@ -21,15 +21,15 @@ import lang.taxi.types.FormatsAndZoneOffset * Generally a TypedObjectFactory */ interface EvaluationValueSupplier : ScopedValueProvider { - fun getValue( + suspend fun getValue( typeName: QualifiedName, queryIfNotFound: Boolean = false, allowAccessorEvaluation: Boolean = true, constraints: List = emptyList() ): TypedInstance - fun readAccessor(type: Type, accessor: Accessor, format: FormatsAndZoneOffset?): TypedInstance - fun readAccessor(type: QualifiedName, accessor: Accessor, nullable: Boolean, format: FormatsAndZoneOffset?): TypedInstance + suspend fun readAccessor(type: Type, accessor: Accessor, format: FormatsAndZoneOffset?): TypedInstance + suspend fun readAccessor(type: QualifiedName, accessor: Accessor, nullable: Boolean, format: FormatsAndZoneOffset?): TypedInstance /** * If the EvaluationValueSupplier has access to a queryEngine, it should @@ -50,7 +50,7 @@ interface EvaluationValueSupplier : ScopedValueProvider { interface ScopedValueProvider { fun getScopedFact(scope: Argument): TypedInstance fun getScopedFactOrNull(scope: Argument): TypedInstance? - fun getValue(attributeName: AttributeName): TypedInstance + suspend fun getValue(attributeName: AttributeName): TypedInstance fun withAdditionalScopedFacts(scopedFacts: List):ScopedValueProvider } @@ -65,7 +65,7 @@ class FactBagScopeValueProvider(private val factBag: FactBag, private val schema } - override fun getValue(attributeName: AttributeName): TypedInstance { + override suspend fun getValue(attributeName: AttributeName): TypedInstance { TODO("Not yet implemented") } diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/FactBagValueSupplier.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/FactBagValueSupplier.kt index f79dee44e..cf6702c54 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/FactBagValueSupplier.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/FactBagValueSupplier.kt @@ -67,7 +67,7 @@ class FactBagValueSupplier( } } - override fun getValue( + override suspend fun getValue( typeName: QualifiedName, queryIfNotFound: Boolean, allowAccessorEvaluation: Boolean, @@ -88,7 +88,7 @@ class FactBagValueSupplier( ) } - override fun getValue(attributeName: AttributeName): TypedInstance { + override suspend fun getValue(attributeName: AttributeName): TypedInstance { return scopedValueProvider.getValue(attributeName) } @@ -100,12 +100,12 @@ class FactBagValueSupplier( return scopedValueProvider.getScopedFactOrNull(scope) } - override fun readAccessor(type: Type, accessor: Accessor, format: FormatsAndZoneOffset?): TypedInstance { + override suspend fun readAccessor(type: Type, accessor: Accessor, format: FormatsAndZoneOffset?): TypedInstance { // This method shouldn't be called. error("readAccessor is not supported by this class") } - override fun readAccessor( + override suspend fun readAccessor( type: QualifiedName, accessor: Accessor, nullable: Boolean, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/InPlaceQueryEngine.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/InPlaceQueryEngine.kt index 2f05901bd..ae0ccf07a 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/InPlaceQueryEngine.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/InPlaceQueryEngine.kt @@ -54,8 +54,8 @@ interface InPlaceQueryEngine : FactBag, QueryContextSchemaProvider { fun withAdditionalFacts(facts: List, scopedFacts: List): InPlaceQueryEngine - fun evaluate(expression: Expression, facts: FactBag = FactBag.empty(), source:DataSource = Provided): TypedInstance - fun evaluate(expression: Expression, value:TypedInstance, source:DataSource = Provided): TypedInstance + suspend fun evaluate(expression: Expression, facts: FactBag = FactBag.empty(), source:DataSource = Provided): TypedInstance + suspend fun evaluate(expression: Expression, value:TypedInstance, source:DataSource = Provided): TypedInstance // See ProjectionFunctionScopeEvaluator.queryContextForFact // this method will need to be implemented eventually diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/ProjectionFunctionScopeEvaluator.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/ProjectionFunctionScopeEvaluator.kt index 02be9cba1..28c0d2e4d 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/ProjectionFunctionScopeEvaluator.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/ProjectionFunctionScopeEvaluator.kt @@ -5,7 +5,6 @@ import com.orbitalhq.models.facts.FactDiscoveryStrategy import com.orbitalhq.models.facts.ScopedFact import com.orbitalhq.schemas.Type import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.runBlocking import lang.taxi.accessors.Argument import lang.taxi.accessors.ProjectionFunctionScope import lang.taxi.types.ArrayType @@ -34,7 +33,7 @@ import lang.taxi.types.StreamType * */ object ProjectionFunctionScopeEvaluator { - fun build( + suspend fun build( inputs: List, primaryFacts: List, context: InPlaceQueryEngine, @@ -57,9 +56,7 @@ object ProjectionFunctionScopeEvaluator { val fact = FactBag.of(primaryFacts, schema) .getFactOrNull(scopeType, FactDiscoveryStrategy.ANY_DEPTH_EXPECT_ONE_DISTINCT) // If that didn't work, do a proper search - fact ?: runBlocking { - queryContextForFact(context, primaryFacts, scopeType) - } + fact ?: queryContextForFact(context, primaryFacts, scopeType) } } catch (e: Exception) { diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObject.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObject.kt index 2fac03c7c..69b9053fe 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObject.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObject.kt @@ -97,22 +97,24 @@ data class TypedObject( valueSuppliers: List = emptyList(), parsingOptions: ParsingOptions = ParsingOptions.DEFAULT ): TypedInstance { - return TypedObjectFactory( - type, - value, - schema, - nullValues, - source, - evaluateAccessors = evaluateAccessors, - functionRegistry = functionRegistry, - inPlaceQueryEngine = inPlaceQueryEngine, - formatSpecs = formatSpecs, - parsingErrorBehaviour = parsingErrorBehaviour, - functionResultCache = functionResultCache, - metadata = metadata, - valueSuppliers = valueSuppliers, - parsingOptions = parsingOptions - ).build() + return kotlinx.coroutines.runBlocking { + TypedObjectFactory( + type, + value, + schema, + nullValues, + source, + evaluateAccessors = evaluateAccessors, + functionRegistry = functionRegistry, + inPlaceQueryEngine = inPlaceQueryEngine, + formatSpecs = formatSpecs, + parsingErrorBehaviour = parsingErrorBehaviour, + functionResultCache = functionResultCache, + metadata = metadata, + valueSuppliers = valueSuppliers, + parsingOptions = parsingOptions + ).build() + } } } diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt index 997ae8bda..f67a8bb92 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt @@ -19,10 +19,11 @@ import com.orbitalhq.schemas.* import com.orbitalhq.schemas.taxi.toVyneQualifiedName import com.orbitalhq.utils.timeBucket import com.orbitalhq.utils.xtimed -import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.runBlocking import lang.taxi.accessors.* import lang.taxi.expressions.Expression import lang.taxi.expressions.LambdaExpression @@ -33,8 +34,6 @@ import lang.taxi.types.FormatsAndZoneOffset import mu.KotlinLogging import org.apache.commons.csv.CSVRecord import java.util.concurrent.CopyOnWriteArrayList -import java.util.concurrent.Executors -import java.util.stream.Collectors /** @@ -91,54 +90,22 @@ class TypedObjectFactory( private val logger = KotlinLogging.logger {} /* - * THREADING MODEL - READ BEFORE MODIFYING - * More detail in ORB-1077 + * THREADING MODEL * - * Background - * ---------- - * TypedObjectFactory uses per-field `lazy` delegates (LazyThreadSafetyMode.SYNCHRONIZED) - * to handle cross-field dependencies during expression evaluation (e.g. `total = quantity * price`). - * This was intentional: ConcurrentHashMap.computeIfAbsent is not re-entrant, so lazy was chosen - * to allow safe recursive field access within a single factory instance. + * All methods that perform async operations (querying the InPlaceQueryEngine, invoking + * operations, etc.) are suspend functions. This means no thread is ever blocked waiting + * for a coroutine — callers suspend and yield their worker back to the pool. * - * The problem - * ----------- - * Several methods (handleTypeNotFound, evaluateLambdaExpression, queryForParentType) bridge - * async operations back to synchronous code via runBlocking { }. When these are invoked from - * a DefaultDispatcher worker thread, the worker blocks waiting for a coroutine to complete. - * That coroutine also needs a DefaultDispatcher thread to run. Under concurrent load (multiple - * factory instances building simultaneously), all DefaultDispatcher workers become blocked, - * the coroutines they're waiting on can never be scheduled, and the system stalls indefinitely. - * This was confirmed by a production thread dump showing all DefaultDispatcher workers parked - * in runBlocking { } inside handleTypeNotFound, each holding a lazy lock on a different - * factory instance. + * Cross-field dependencies (e.g. `total = quantity * price`) are handled via a simple + * HashMap cache: when getOrBuild() is called for a field, we check the cache first. If + * the field hasn't been computed yet, we compute it (calling suspend buildField()), cache + * the result, and return it. Since a single factory instance is only ever accessed from + * one coroutine at a time, no synchronization is needed. * - * Short-term fix - * -------------- - * All runBlocking { } call sites in this class use a dedicated blockingBridgeDispatcher - * (unbounded cached thread pool) rather than inheriting the calling thread's dispatcher. - * This moves the blocking onto threads outside the DefaultDispatcher pool, freeing coroutine - * workers to complete the async operations being waited on. - * - * Why this is still a sticking plaster - * ------------------------------------- - * The calling thread is still blocked — we've just moved the blockage off the bounded pool. - * Under sustained high concurrency the bridge pool will grow unboundedly (one thread per - * concurrent blocked call). This is acceptable in practice because it is bounded by upstream - * request concurrency, but it is not a principled fix. - * - * The correct long-term fix - * ------------------------- - * Make the entire call chain properly async: replace the lazy + runBlocking pattern with - * suspend functions (or Deferred), propagating suspension all the way up through getValue(), - * buildField(), getOrBuild(), and buildAsync(). This eliminates thread blocking entirely — - * coroutines suspend and yield their workers back to the pool while waiting. This requires - * changes to the classes that call into TypedObjectFactory and is a non-trivial refactor, - * hence the bridge approach in the interim. + * Callers in suspend contexts (e.g. ObjectBuilder.buildAsync) call build() directly. + * Callers in non-suspend contexts (e.g. TypedObject.fromValue) use runBlocking at the + * call site — but crucially, no runBlocking exists inside this class or its call chain. */ - private val blockingBridgeDispatcher = Executors.newCachedThreadPool( - Thread.ofVirtual().name("typed-obj-factory-bridge-", 0).factory() - ).asCoroutineDispatcher() } @@ -201,58 +168,12 @@ class TypedObjectFactory( private val attributesToMap = type.attributes - private val fieldInitializers: Map> by lazy { - attributesToMap.map { (attributeName, field) -> - - // Do not use locks here, as it leads to thread starvation and deadlock - // when we hit handleTypeNotFound() - attributeName to lazy(LazyThreadSafetyMode.NONE) { - - val fieldValue = - xtimed("build field $attributeName ${field.typeDisplayName}") { - buildField( - field, - attributeName - ) - }.let { constructedField -> - if (policyEngine != null && inPlaceQueryEngine != null) { - policyEngine.evaluate(constructedField, inPlaceQueryEngine) - } else { - constructedField - } - } - - // Do not start a projection if the type we're projecting to is the same as the value we have. - // This happens if the projection was processed internally already within the object construction - if (field.fieldProjection != null && !field.fieldProjection.projectedType.isAssignableTo( - fieldValue.type.taxiType, - // Structural compatability is useful when assigning a value, but not - // when determining if we want to project. - // At projection time, the goal is to create an object that looks exactly like the provided spec. - // If the source value (ie., fieldValue) is a superset of the projectedType, that would pass structural - // compatability, but isn't what we want. - // eg: - // model EnhancedPerson { Name, Age } - // model Person { Name } - // If we project EnhancedPerson to Person, we want the Age field dropped, even though - // the two types match from a structuralCompatability perspective. - permitStructurallyCompatible = false - ) - ) { - val projection = xtimed("Project field $attributeName") { - projectField( - field, - fieldValue, - ) - } - projection - - } else { - fieldValue - } - } - }.toMap() - } + /** + * Cache for computed field values. Replaces the previous lazy delegate map. + * Since a single factory instance is only accessed from one coroutine at a time, + * a plain HashMap is sufficient — no synchronization needed. + */ + private val fieldValues: MutableMap = HashMap() /** * Where a field has an inline projection defined ( @@ -260,7 +181,7 @@ class TypedObjectFactory( * a : A, b: B * } */ - private fun projectField( + private suspend fun projectField( field: Field, fieldValue: TypedInstance, ): TypedInstance { @@ -291,7 +212,7 @@ class TypedObjectFactory( ) } - override fun project( + override suspend fun project( valueToProject: TypedInstance, projection: FieldProjection, targetType: Type, @@ -315,21 +236,22 @@ class TypedObjectFactory( ) } val projectedFieldValue = if (valueToProject is TypedCollection && targetType.isCollection) { - // Project each member of the collection seperately - valueToProject - .parallelStream() - .map { collectionMember -> - newFactory( - targetType.collectionType!!, - collectionMember, - scopedArguments = projection.projectionFunctionScope - ) - .build() - }.collect(Collectors.toList()) - .let { projectedCollection -> - // Use arrayOf (instead of from), as the collection may be empty, so we want to be explicit about it's type - TypedCollection.arrayOf(targetType.collectionType!!, projectedCollection, source) - } + // Project each member of the collection using coroutines instead of parallelStream + coroutineScope { + valueToProject.map { collectionMember -> + async { + newFactory( + targetType.collectionType!!, + collectionMember, + scopedArguments = projection.projectionFunctionScope + ) + .build() + } + }.awaitAll() + }.let { projectedCollection -> + // Use arrayOf (instead of from), as the collection may be empty, so we want to be explicit about it's type + TypedCollection.arrayOf(targetType.collectionType!!, projectedCollection, source) + } } else { newFactory(targetType, valueToProject, scopedArguments = projection.projectionFunctionScope).build() } @@ -479,7 +401,7 @@ class TypedObjectFactory( return TypedObject(type, attributes, source, metadata) } - fun build(decorator: (attributeMap: Map) -> Map = { attributesToMap -> attributesToMap }): TypedInstance { + suspend fun build(decorator: suspend (attributeMap: Map) -> Map = { attributesToMap -> attributesToMap }): TypedInstance { return timeBucket("Build ${this.type.name.shortDisplayName}") { doBuild(decorator) } @@ -520,7 +442,7 @@ class TypedObjectFactory( ) } - private fun doBuild(decorator: (attributeMap: Map) -> Map = { attributesToMap -> attributesToMap }): TypedInstance { + private suspend fun doBuild(decorator: suspend (attributeMap: Map) -> Map = { attributesToMap -> attributesToMap }): TypedInstance { val metadataAndFormat = formatDetector.getFormatType(type) if (metadataAndFormat != null) { val (metadata, modelFormatSpec) = metadataAndFormat @@ -608,7 +530,7 @@ class TypedObjectFactory( /** * Called because the parent type is closed, so construction isn't possible */ - private fun queryForParentType(): TypedInstance { + private suspend fun queryForParentType(): TypedInstance { if (inPlaceQueryEngine == null) { return TypedNull.create( type, @@ -616,31 +538,50 @@ class TypedObjectFactory( ) } val searchFailureBehaviour: QueryFailureBehaviour = QueryFailureBehaviour.defaultBehaviour(type) - // Be careful - this blocking behaviour can cause thread starvation. - // See comment on blockingBridgeDispatcher. The fix is to move this code to be fully async - return runBlocking(blockingBridgeDispatcher) { - logger.debug { "Initiating query to search for closed type ${type.name.shortDisplayName}" } - queryForType(type, searchFailureBehaviour, AlwaysGoodSpec, attributeName = null) - } - - + logger.debug { "Initiating query to search for closed type ${type.name.shortDisplayName}" } + return queryForType(type, searchFailureBehaviour, AlwaysGoodSpec, attributeName = null) } - private fun getOrBuild(attributeName: AttributeName, allowAccessorEvaluation: Boolean = true): TypedInstance { - // Originally we used a concurrentHashMap.computeIfAbsent { ... } approach here. - // However, functions on accessors can access other fields, which can cause recursive access. - // Therefore, migrated to using initializers with kotlin Lazy functions - val initializer = fieldInitializers[attributeName] - ?: error("Cannot request field $attributeName as no initializer has been prepared") + private suspend fun getOrBuild(attributeName: AttributeName, allowAccessorEvaluation: Boolean = true): TypedInstance { + // Check cache first — if the field was already computed (e.g. via cross-field reference), return it + fieldValues[attributeName]?.let { return it } + + val field = attributesToMap[attributeName] + ?: error("Cannot request field $attributeName as no attribute definition exists") val accessorEvaluationWasSupressed = accessorEvaluationSupressed accessorEvaluationSupressed = !allowAccessorEvaluation - // Reading the value will trigger population the first time. - val value = initializer.value + val fieldValue = + xtimed("build field $attributeName ${field.typeDisplayName}") { + buildField(field, attributeName) + }.let { constructedField -> + if (policyEngine != null && inPlaceQueryEngine != null) { + policyEngine.evaluate(constructedField, inPlaceQueryEngine) + } else { + constructedField + } + } + + // Do not start a projection if the type we're projecting to is the same as the value we have. + // This happens if the projection was processed internally already within the object construction + val result = if (field.fieldProjection != null && !field.fieldProjection.projectedType.isAssignableTo( + fieldValue.type.taxiType, + permitStructurallyCompatible = false + ) + ) { + xtimed("Project field $attributeName") { + projectField(field, fieldValue) + } + } else { + fieldValue + } accessorEvaluationSupressed = accessorEvaluationWasSupressed - return value + + // Cache the result for cross-field references + fieldValues[attributeName] = result + return result } override fun getScopedFact(scope: Argument): TypedInstance { @@ -682,7 +623,7 @@ class TypedObjectFactory( /** * Returns a value looked up by it's type */ - override fun getValue( + override suspend fun getValue( typeName: QualifiedName, queryIfNotFound: Boolean, allowAccessorEvaluation: Boolean, @@ -727,7 +668,7 @@ class TypedObjectFactory( } } - private fun handleTypeNotFound( + private suspend fun handleTypeNotFound( requestedType: Type, queryIfNotFound: Boolean, constraints: List @@ -745,71 +686,47 @@ class TypedObjectFactory( } return when { queryIfNotFound && inPlaceQueryEngine != null -> { - // Be careful - this blocking behaviour can cause thread starvation. - // See comment on blockingBridgeDispatcher. The fix is to move this code to be fully async - runBlocking(blockingBridgeDispatcher) { - if (requestedType.isStream) { - error("Cannot perform an inner search for a stream") + if (requestedType.isStream) { + error("Cannot perform an inner search for a stream") + } + val resultsFromSearch = try { + val queryEngine = if (value is FactBag) { + inPlaceQueryEngine.withAdditionalFacts(value.rootFacts(), value.scopedFacts) + } else { + inPlaceQueryEngine } - val resultsFromSearch = try { - // MP: 29-Jan-25: Found that facts from the context - // are not being passed when doing an in-place search - // So, in a nested projection, we were not searching with the facts - // from the current scope. - val queryEngine = if (value is FactBag) { - inPlaceQueryEngine.withAdditionalFacts(value.rootFacts(), value.scopedFacts) - } else { - inPlaceQueryEngine - } - queryEngine.findType(requestedType, constraints = constraints) - .toList() - } catch (e: Exception) { - // OrbitalQueryException comes from a policy expression: e.g.when below `else` branch is triggered: - /*policy AdminRestrictedInstrument against Instrument (userInfo : UserInfo) -> { - read { - when { - userInfo.roles.contains('QueryRunner') -> Instrument - else -> throw((NotAuthorizedError) { message: 'Not Authorized' }) - } - } - }*/ - // so, we need to catch OrbitalQueryException and re-throw to halt the execution. - // a query like find { fist(Instrument[]) } against the above policy would hit the debug point placed here. - if (e is OrbitalQueryException) { - throw e - } - - // handle com.orbitalhq.query.UnresolvedTypeInQueryException - emptyList() + queryEngine.findType(requestedType, constraints = constraints) + .toList() + } catch (e: Exception) { + if (e is OrbitalQueryException) { + throw e } - when { - resultsFromSearch.isEmpty() -> createTypedNull( - "No attribute with type ${requestedType.name.parameterizedName} is present on type ${type.name.parameterizedName} and attempts to discover a value from the query engine did not find any approaches that produced a result" - ) - - resultsFromSearch.size == 1 && !requestedType.isCollection -> resultsFromSearch.first() + emptyList() + } + when { + resultsFromSearch.isEmpty() -> createTypedNull( + "No attribute with type ${requestedType.name.parameterizedName} is present on type ${type.name.parameterizedName} and attempts to discover a value from the query engine did not find any approaches that produced a result" + ) - // MP: 15-Jul-25: A search for T[] that produced null, hits here with a single TypedNull -- don't wrap that into a collection - resultsFromSearch.size == 1 && requestedType.isCollection && resultsFromSearch.single() is TypedNull && resultsFromSearch.single().type.isCollection -> resultsFromSearch.single() - resultsFromSearch.size >= 1 && requestedType.isCollection -> TypedCollection.from( - resultsFromSearch, - MixedSources.singleSourceOrMixedSources(resultsFromSearch) - ) + resultsFromSearch.size == 1 && !requestedType.isCollection -> resultsFromSearch.first() - resultsFromSearch.size > 1 && !requestedType.isCollection -> { - val errorMessage = - "Search for ${requestedType.name.shortDisplayName} returned ${resultsFromSearch.size} results, which is invalid for non-array types. Returning null." - logger.warn { errorMessage } - createTypedNull(errorMessage) - } + resultsFromSearch.size == 1 && requestedType.isCollection && resultsFromSearch.single() is TypedNull && resultsFromSearch.single().type.isCollection -> resultsFromSearch.single() + resultsFromSearch.size >= 1 && requestedType.isCollection -> TypedCollection.from( + resultsFromSearch, + MixedSources.singleSourceOrMixedSources(resultsFromSearch) + ) - else -> createTypedNull( - "No attribute with type ${requestedType.name.parameterizedName} is present on type ${type.name.parameterizedName} and attempts to discover a value from the query engine returned ${resultsFromSearch.size} results. Given this is ambiguous, returning null" - ) + resultsFromSearch.size > 1 && !requestedType.isCollection -> { + val errorMessage = + "Search for ${requestedType.name.shortDisplayName} returned ${resultsFromSearch.size} results, which is invalid for non-array types. Returning null." + logger.warn { errorMessage } + createTypedNull(errorMessage) } + else -> createTypedNull( + "No attribute with type ${requestedType.name.parameterizedName} is present on type ${type.name.parameterizedName} and attempts to discover a value from the query engine returned ${resultsFromSearch.size} results. Given this is ambiguous, returning null" + ) } - } queryIfNotFound && inPlaceQueryEngine == null -> { @@ -824,18 +741,18 @@ class TypedObjectFactory( } - private fun getValue(attributeName: AttributeName, allowAccessorEvaluation: Boolean): TypedInstance { + private suspend fun getValue(attributeName: AttributeName, allowAccessorEvaluation: Boolean): TypedInstance { return getOrBuild(attributeName, allowAccessorEvaluation) } /** * Returns a value looked up by it's name */ - override fun getValue(attributeName: AttributeName): TypedInstance { + override suspend fun getValue(attributeName: AttributeName): TypedInstance { return getValue(attributeName, allowAccessorEvaluation = true) } - override fun readAccessor(type: Type, accessor: Accessor, format: FormatsAndZoneOffset?): TypedInstance { + override suspend fun readAccessor(type: Type, accessor: Accessor, format: FormatsAndZoneOffset?): TypedInstance { // MP 24-Jan-25: // Changed this to allowContextQuerying = true. // Otherwise, types on projections-with-expressions were not triggering querying - eg: @@ -857,7 +774,7 @@ class TypedObjectFactory( ) } - override fun readAccessor( + override suspend fun readAccessor( type: QualifiedName, accessor: Accessor, nullable: Boolean, @@ -876,7 +793,7 @@ class TypedObjectFactory( ) } - fun evaluateExpressionType(expressionType: Type, format: FormatsAndZoneOffset?): TypedInstance { + suspend fun evaluateExpressionType(expressionType: Type, format: FormatsAndZoneOffset?): TypedInstance { val expression = expressionType.expression!! return if (expression is LambdaExpression) { // Lambda Expression types are evaluated more like functions. @@ -894,7 +811,7 @@ class TypedObjectFactory( } - fun evaluateLambdaExpression(expression: LambdaExpression, format: FormatsAndZoneOffset?): TypedInstance { + suspend fun evaluateLambdaExpression(expression: LambdaExpression, format: FormatsAndZoneOffset?): TypedInstance { // Lambda Expression types are evaluated more like functions. // Their inputs are declared independent of queries where they're evaluated // eg: @@ -940,32 +857,24 @@ class TypedObjectFactory( // The inputs into this expression have constraints defined. // We can't search directly for the type, we need to do a search for the type with the provided constraints. val argumentTypeExpression = argument.expression as TypeExpression - // TODO : Fix runblocking, but that requires a big change, and not sure the direction of travel - // wrt/ coroutines vs flux atm. val argumentExpressionReturnType = schema.type(argumentTypeExpression.type) val scopedFacts = this.getCurrentScopedFacts() - // Be careful - this blocking behaviour can cause thread starvation. - // See comment on blockingBridgeDispatcher. The fix is to move this code to be fully async - val typedInstance = runBlocking(blockingBridgeDispatcher) { - // If we're doing nested traversal of lambda expressions, - // there could be scoped facts we've been passed that will - // be needed as inputs - - val queryEngineWithScopedFacts = - valueSupplier.inPlaceQueryEngine!!.withAdditionalFacts(emptyList(), scopedFacts) - val collectedList = queryEngineWithScopedFacts.findType( - argumentExpressionReturnType, constraints = argumentTypeExpression.constraints - ).toList() - when { - argumentExpressionReturnType.isCollection -> TypedCollection.from(collectedList) - collectedList.isEmpty() -> TypedNull.create(argumentExpressionReturnType) - collectedList.size == 1 -> collectedList.single() - else -> { - error("Expected a single value returned for the expression type ${argumentTypeExpression}, but got ${collectedList.size}") - } + // If we're doing nested traversal of lambda expressions, + // there could be scoped facts we've been passed that will + // be needed as inputs + val queryEngineWithScopedFacts = + valueSupplier.inPlaceQueryEngine!!.withAdditionalFacts(emptyList(), scopedFacts) + val collectedList = queryEngineWithScopedFacts.findType( + argumentExpressionReturnType, constraints = argumentTypeExpression.constraints + ).toList() + val typedInstance = when { + argumentExpressionReturnType.isCollection -> TypedCollection.from(collectedList) + collectedList.isEmpty() -> TypedNull.create(argumentExpressionReturnType) + collectedList.size == 1 -> collectedList.single() + else -> { + error("Expected a single value returned for the expression type ${argumentTypeExpression}, but got ${collectedList.size}") } - } // val typedInstance = TypedInstance.from(argumentExpressionReturnType, result, schema) ScopedFact(argument, typedInstance) @@ -1003,7 +912,7 @@ class TypedObjectFactory( return result } - fun evaluateExpression(expression: Expression): TypedInstance { + suspend fun evaluateExpression(expression: Expression): TypedInstance { return accessorReader.evaluate( value, schema.type(expression.returnType), @@ -1015,13 +924,13 @@ class TypedObjectFactory( ) } - private fun evaluateExpressionType(typeName: QualifiedName): TypedInstance { + private suspend fun evaluateExpressionType(typeName: QualifiedName): TypedInstance { val type = schema.type(typeName) return evaluateExpressionType(type, null) } - private fun buildField(field: Field, attributeName: AttributeName): TypedInstance { + private suspend fun buildField(field: Field, attributeName: AttributeName): TypedInstance { // When we're building a field, if there's a projection on it, // we build the source type initially. Once the source is built, we // then project to the target type. @@ -1229,7 +1138,7 @@ class TypedObjectFactory( * We need to ensure that the value we're using * satisfies that constraint. Otherwise, search for it. */ - private fun verifyValueSatisfiesConstraints( + private suspend fun verifyValueSatisfiesConstraints( value: TypedInstance, field: Field, type: Type, @@ -1295,7 +1204,7 @@ class TypedObjectFactory( * That creates challenges, as we need to pass the ObjectBuilder in the TypedObjectFactory, * which becomes recursive. */ - private fun attemptToBuildFieldObject( + private suspend fun attemptToBuildFieldObject( fieldType: Type, constraints: List ): TypedInstance? { @@ -1330,7 +1239,7 @@ class TypedObjectFactory( ) } - private fun queryForFieldValue( + private suspend fun queryForFieldValue( field: Field, type: Type, attributeName: AttributeName, @@ -1351,7 +1260,7 @@ class TypedObjectFactory( } } - private fun queryForType( + private suspend fun queryForType( searchType: Type, searchFailureBehaviour: QueryFailureBehaviour, instanceValidPredicate: TypedInstanceValidPredicate, @@ -1373,20 +1282,16 @@ class TypedObjectFactory( val (additionalFacts, additionalScope) = getFactsInScopeForSearch() - // Be careful - this blocking behaviour can cause thread starvation. - // See comment on blockingBridgeDispatcher. The fix is to move this code to be fully async - val buildResult = runBlocking(blockingBridgeDispatcher) { - logger.debug { "Initiating query to search for attribute $attributeName (${searchType.name.shortDisplayName})" } - inPlaceQueryEngine.withAdditionalFacts(additionalFacts, additionalScope) - .findType( - searchType, - instanceValidPredicate, - PermittedQueryStrategies.EXCLUDE_BUILDER_AND_MODEL_SCAN, - searchFailureBehaviour, - constraint - ) - .toList() - } + logger.debug { "Initiating query to search for attribute $attributeName (${searchType.name.shortDisplayName})" } + val buildResult = inPlaceQueryEngine.withAdditionalFacts(additionalFacts, additionalScope) + .findType( + searchType, + instanceValidPredicate, + PermittedQueryStrategies.EXCLUDE_BUILDER_AND_MODEL_SCAN, + searchFailureBehaviour, + constraint + ) + .toList() val attributeNameErrorMessagePart = if (attributeName != null) { " (attempting to build attribute $attributeName of ${this.type.name}" } else "" @@ -1501,7 +1406,7 @@ class TypedObjectFactory( } - private fun readWithValueReader( + private suspend fun readWithValueReader( attributeName: AttributeName, type: Type, format: FormatsAndZoneOffset? diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/ValueProjector.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/ValueProjector.kt index bdd307da1..a4e4dbdf4 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/ValueProjector.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/ValueProjector.kt @@ -16,7 +16,7 @@ import lang.taxi.types.FormatsAndZoneOffset * which include queries etc. */ interface ValueProjector { - fun project( + suspend fun project( valueToProject: TypedInstance, projection: FieldProjection, // because the projection.targetType is a Taxi Type diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/ConditionalFieldSetEvaluator.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/ConditionalFieldSetEvaluator.kt index 282ef8b0d..a9546fde8 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/ConditionalFieldSetEvaluator.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/ConditionalFieldSetEvaluator.kt @@ -16,7 +16,7 @@ class ConditionalFieldSetEvaluator(private val factory: EvaluationValueSupplier, // fun evaluate(readCondition: FieldSetExpression, targetType: Type): TypedInstance { // return evaluate(readCondition, attributeName = null, targetType = targetType) // } - fun evaluate(value:Any,readCondition: FieldSetExpression, attributeName: AttributeName?, targetType: Type, datasource:DataSource): TypedInstance { + suspend fun evaluate(value:Any,readCondition: FieldSetExpression, attributeName: AttributeName?, targetType: Type, datasource:DataSource): TypedInstance { TODO("Refactor when blocks") // return try { // when (readCondition) { diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/WhenBlockEvaluator.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/WhenBlockEvaluator.kt index 66e30260e..c7f76f4fa 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/WhenBlockEvaluator.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/WhenBlockEvaluator.kt @@ -19,7 +19,7 @@ class WhenBlockEvaluator( private val schema: Schema, private val accessorReader: AccessorReader ) { - fun evaluate( + suspend fun evaluate( value: Any, readCondition: WhenExpression, source: DataSource, @@ -51,7 +51,7 @@ class WhenBlockEvaluator( return result } - private fun selectCaseBlock( + private suspend fun selectCaseBlock( selectorValue: TypedInstance, readCondition: WhenExpression, value: Any, @@ -88,7 +88,7 @@ class WhenBlockEvaluator( return selectedCase to EvaluatedWhenCaseSelection(readCondition.asTaxi(), selectorValue, selectedCase?.asTaxi(), evaluations) } - private fun evaluateExpression( + private suspend fun evaluateExpression( matchExpression: Expression, type: Type, value: Any, @@ -98,7 +98,7 @@ class WhenBlockEvaluator( return accessorReader.evaluate(value, type, matchExpression, dataSource = UndefinedSource, format = format) } - private fun evaluateSelector( + private suspend fun evaluateSelector( selectorExpression: WhenSelectorExpression, format: FormatsAndZoneOffset? ): TypedInstance { diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/FunctionRegistry.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/FunctionRegistry.kt index 2893fe7d6..a670218a5 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/FunctionRegistry.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/FunctionRegistry.kt @@ -16,7 +16,7 @@ import mu.KotlinLogging class FunctionRegistry(private val invokers: List) { private val invokersByName = invokers.associateBy { it.functionName } - fun invoke( + suspend fun invoke( function: Function, declaredInputs: List, schema: Schema, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/Functions.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/Functions.kt index e5d1d0fa6..71e90a126 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/Functions.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/Functions.kt @@ -24,7 +24,7 @@ typealias FunctionHandler = ( ) -> TypedInstance interface FunctionInvoker { - fun invoke( + suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, @@ -59,7 +59,7 @@ interface SelfDescribingFunction : NamedFunctionInvoker { * Helper class which will return TypedNull if any of the provided arguments were null. */ abstract class NullSafeInvoker : NamedFunctionInvoker { - protected abstract fun doInvoke( + protected abstract suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -70,7 +70,7 @@ abstract class NullSafeInvoker : NamedFunctionInvoker { resultCache: MutableMap ): TypedInstance - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, @@ -141,7 +141,7 @@ class InlineFunctionInvoker(override val functionName: QualifiedName, val handle NullSafeInvoker() { constructor(functionName: String, handler: FunctionHandler) : this(QualifiedName.from(functionName), handler) - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/BaseMathFunction.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/BaseMathFunction.kt index 206f081b4..29bdd629f 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/BaseMathFunction.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/BaseMathFunction.kt @@ -20,7 +20,7 @@ import java.math.BigDecimal abstract class MathIteratingFunction : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Collections.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Collections.kt index e890c30bc..c01e7c642 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Collections.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Collections.kt @@ -61,7 +61,7 @@ abstract class BooleanPredicateEvaluator( ) : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -114,7 +114,7 @@ private fun expectAllBoolean( object Contains : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Contains.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -133,7 +133,7 @@ object Contains : NullSafeInvoker() { } object ContainsAll : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -161,7 +161,7 @@ object ContainsAll : NullSafeInvoker() { } object ContainsAny : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -197,7 +197,7 @@ object ContainsAny : NullSafeInvoker() { object Size : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -220,7 +220,7 @@ object Size : NullSafeInvoker() { object IsNullOrEmpty : NamedFunctionInvoker { override val functionName: QualifiedName = lang.taxi.functions.stdlib.IsNullOrEmpty.name - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/EnumFunctions.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/EnumFunctions.kt index 07fd7fffe..5938dc265 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/EnumFunctions.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/EnumFunctions.kt @@ -28,7 +28,7 @@ object EnumFunctions { object EnumForName : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.EnumForName.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -68,7 +68,7 @@ object EnumForName : NullSafeInvoker() { object HasEnumNamed : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.HasEnumNamed.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Functional.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Functional.kt index d2333de18..ca007dcd0 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Functional.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Functional.kt @@ -35,7 +35,7 @@ object Functional { object Fold : NamedFunctionInvoker { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Fold.name - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, @@ -54,21 +54,20 @@ object Fold : NamedFunctionInvoker { function.asTaxi(), inputValues ) - val foldedValue = sourceCollection.fold(initialValue) { acc, typedInstance -> + var accumulator: TypedValue = initialValue + for (typedInstance in sourceCollection) { val factBagValueSupplier = FactBagValueSupplier.of( - listOf(acc, typedInstance), + listOf(accumulator, typedInstance), schema, objectFactory, - // Exact match so that the accumulated value (which is likely an INT) doesn't conflict with semantic subtypes. - // We should be smarter about this. TypeMatchingStrategy.EXACT_MATCH ) val reader = AccessorReader(factBagValueSupplier, schema.functionRegistry, schema) val evaluated = reader.evaluate(typedInstance, expressionReturnType, expression, dataSource = dataSource, format = null) - evaluated as TypedValue + accumulator = evaluated as TypedValue } - return foldedValue + return accumulator } } @@ -77,7 +76,7 @@ object MapFunction : NullSafeInvoker() { private val logger = KotlinLogging.logger {} - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -102,31 +101,16 @@ object MapFunction : NullSafeInvoker() { error("Cannot evaluate expression ${function.asTaxi()} as the function declares multiple inputs. This should've been detected by the compiler") } val mapFunctionInput = lambdaExpression.inputs[0] - val result = sourceCollection.map { typedInstance -> - // Before we can evaluate the actual map, we need to resolve the instance in the collection against - // the arguments to the lambda. - // eg: - // Foo[].map( (Foo) -> ...... // the 2nd (Foo) there - // If they're the same, as in that example, then it's fine. - // But we also need to consider: - // Foo[].map ( (f:Foo) -> // named args + val result = mutableListOf() + for (typedInstance in sourceCollection) { val scopedFact = if (typedInstance.type.taxiType.isAssignableTo(mapFunctionInput.type)) { ScopedFact(mapFunctionInput, typedInstance) } else { - // It's not assignable. - // This is an error, as the function is defined as; - // declare extension function map(collection: T[], callback: (T) -> A):A[] - // Therefore, this MUST be a T. - // However, if the compiler didn't enforce it, we have a problem - // ORB-1004 logger.error { "Map function expected iterable values of ${mapFunctionInput.type.toVyneQualifiedName().shortDisplayName}, but found an incompatible value of ${typedInstance.type.qualifiedName.shortDisplayName}. This should've been detected by the compiler. Mapping may not behave as expected" } ScopedFact(mapFunctionInput, typedInstance) } - // The type of expression determines how we should behave - // (which is annoying)... val evaluated = if (lambdaExpression.expression is TypeExpression) { - // If the expression is in the form of T1[].map((T1) -> T2), then we should build T2 from T1 thisScopeValueSupplier.newFactory( schema.type(lambdaExpression.expression.returnType), typedInstance, factsToExclude = setOf(sourceCollection), @@ -134,13 +118,11 @@ object MapFunction : NullSafeInvoker() { ) .build() } else { - // If the expression is in the form of T1[].map((T1) -> T1.someOtherExpression()), then we should evaluate the - // expression against the scope of T1 val factBag = FactBag.of(emptyList(), schema).withAdditionalScopedFacts(listOf(scopedFact), schema) thisScopeValueSupplier.newFactoryWithOnly(expressionReturnType, factBag) .evaluateExpression(lambdaExpression) } - evaluated + result.add(evaluated) } return if (result.isEmpty()) { TypedCollection.empty(returnType) @@ -152,7 +134,7 @@ object MapFunction : NullSafeInvoker() { } object Reduce : NamedFunctionInvoker { - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, @@ -170,11 +152,11 @@ object Reduce : NamedFunctionInvoker { function.asTaxi(), inputValues ) - sourceCollection.reduce { acc, typedInstance -> + var acc: TypedInstance = sourceCollection.first() + for (i in 1 until sourceCollection.size) { + val typedInstance = sourceCollection.toList()[i] val reader = AccessorReader.forFacts(listOf(acc, typedInstance), schema) - val evaluated = - reader.evaluate(typedInstance, expressionReturnType, expression, dataSource = dataSource, format = null) - evaluated + acc = reader.evaluate(typedInstance, expressionReturnType, expression, dataSource = dataSource, format = null) } sourceCollection.forEach { instance -> // val reader = AccessorReader(SimpleValueStore(listOf(instance), schema), schema.functionRegistry, schema) diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/ObjectFunctions.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/ObjectFunctions.kt index 668bc83aa..2ec0244c1 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/ObjectFunctions.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/ObjectFunctions.kt @@ -33,7 +33,7 @@ object ObjectFunctions { object CollectAllInstances : NullSafeInvoker() { private val logger = KotlinLogging.logger {} - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -69,7 +69,7 @@ object CollectAllInstances : NullSafeInvoker() { object Equals : NamedFunctionInvoker { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Equals.name - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, @@ -91,7 +91,7 @@ object Equals : NamedFunctionInvoker { object EmptyInstance : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Strings.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Strings.kt index dabeb7285..f9bbc45e2 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Strings.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Strings.kt @@ -45,7 +45,7 @@ object Strings { object Concat : NamedFunctionInvoker { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Concat.name - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, @@ -68,7 +68,7 @@ object Concat : NamedFunctionInvoker { object Trim : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Trim.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -113,7 +113,7 @@ private fun String.substringOrTypedNull( object Left : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Left.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -140,7 +140,7 @@ object Left : NullSafeInvoker() { object Right : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Right.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -167,7 +167,7 @@ object Right : NullSafeInvoker() { object Mid : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Mid.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -194,7 +194,7 @@ object Mid : NullSafeInvoker() { object Uppercase : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Uppercase.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -219,7 +219,7 @@ object Uppercase : NullSafeInvoker() { object Lowercase : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Lowercase.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -243,7 +243,7 @@ object Lowercase : NullSafeInvoker() { object Length : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Length.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -267,7 +267,7 @@ object Length : NullSafeInvoker() { object Find : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.IndexOf.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -290,7 +290,7 @@ object Find : NullSafeInvoker() { } object ContainsString : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -316,7 +316,7 @@ object ContainsString : NullSafeInvoker() { object Coalesce : NamedFunctionInvoker { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Coalesce.name - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, @@ -333,7 +333,7 @@ object Coalesce : NamedFunctionInvoker { object Replace : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Replace.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -359,7 +359,7 @@ object Replace : NullSafeInvoker() { } object PadStart : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -384,7 +384,7 @@ object PadStart : NullSafeInvoker() { object PadEnd : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -408,7 +408,7 @@ object PadEnd : NullSafeInvoker() { } object ApplyFormat : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -430,7 +430,7 @@ object ApplyFormat : NullSafeInvoker() { } object StartsWith : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -452,7 +452,7 @@ object StartsWith : NullSafeInvoker() { } object EndsWith : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -474,7 +474,7 @@ object EndsWith : NullSafeInvoker() { } object Matches : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -496,7 +496,7 @@ object Matches : NullSafeInvoker() { } object ContainsPattern : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Append.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Append.kt index 6d2ad9472..4376ea918 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Append.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Append.kt @@ -13,7 +13,7 @@ import lang.taxi.types.FormatsAndZoneOffset import lang.taxi.types.QualifiedName object Append : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionFilteringFunction.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionFilteringFunction.kt index fa06da3b5..c9eb476d3 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionFilteringFunction.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionFilteringFunction.kt @@ -57,7 +57,7 @@ abstract class CollectionFilteringFunction : NullSafeInvoker() { return Triple(collection, deferredInstance, dataSource).right() } - protected fun applyFilter( + protected suspend fun applyFilter( inputValues: List, schema: Schema, returnType: Type, @@ -65,10 +65,13 @@ abstract class CollectionFilteringFunction : NullSafeInvoker() { objectFactory: EvaluationValueSupplier, rawMessageBeingParsed: Any? ): Either> { - return extractAndValidateInputs(inputValues, schema, returnType, function) - .map { (collection, deferredExpression, dataSource) -> - val filtered = collection.filter { collectionMember -> - val filtered = evaluatePredicateAgainstMember( + return when (val extracted = extractAndValidateInputs(inputValues, schema, returnType, function)) { + is Either.Left -> extracted + is Either.Right -> { + val (collection, deferredExpression, dataSource) = extracted.value + val filtered = mutableListOf() + for (collectionMember in collection) { + val filterResult = evaluatePredicateAgainstMember( collectionMember, schema, objectFactory, @@ -78,22 +81,17 @@ abstract class CollectionFilteringFunction : NullSafeInvoker() { function, inputValues ) - when (filtered) { - // If the evaluation returned a typedNull, it indicates it failed, so - // bail out of the evaluation, returning at the top level - is Either.Left -> return filtered.value.left() - // Otherwise, return the filter result - is Either.Right -> return@filter filtered.value + when (filterResult) { + is Either.Left -> return filterResult.value.left() + is Either.Right -> if (filterResult.value) filtered.add(collectionMember) } - } - filtered + filtered.right() } - - + } } - protected fun evaluatePredicateAgainstMember( + protected suspend fun evaluatePredicateAgainstMember( collectionMember: TypedInstance, schema: Schema, objectFactory: EvaluationValueSupplier, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionPredicateFunctions.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionPredicateFunctions.kt index 4e1123d4d..34e3f1d8f 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionPredicateFunctions.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionPredicateFunctions.kt @@ -30,7 +30,7 @@ enum class CollectionOperationType( abstract class BaseCollectionPredicateInvoker(val operationType: CollectionOperationType) : CollectionFilteringFunction(), NamedFunctionInvoker { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -40,9 +40,11 @@ abstract class BaseCollectionPredicateInvoker(val operationType: CollectionOpera returnTypeFormat: FormatsAndZoneOffset?, resultCache: MutableMap ): TypedInstance { - return extractAndValidateInputs(inputValues, schema, returnType, function) - .map { (collection, deferredExpression, dataSource) -> - collection.map { instance -> + return when (val extracted = extractAndValidateInputs(inputValues, schema, returnType, function)) { + is Either.Left -> extracted.value + is Either.Right -> { + val (collection, deferredExpression, dataSource) = extracted.value + for (instance in collection) { val eval = evaluatePredicateAgainstMember( instance, schema, @@ -54,8 +56,6 @@ abstract class BaseCollectionPredicateInvoker(val operationType: CollectionOpera inputValues ) when (eval) { - // If the evaluation returned a typedNull, it indicates it failed, so - // bail out of the evaluation, returning at the top level is Either.Left -> return eval.value is Either.Right -> { if (eval.value == operationType.terminateWhenEvaluatesAs) { @@ -65,14 +65,13 @@ abstract class BaseCollectionPredicateInvoker(val operationType: CollectionOpera schema, source = dataSource ) - } else { - eval.value } } } } TypedInstance.from(returnType, operationType.valueIfAllEvaluated, schema, source = dataSource) - }.getOrHandle { it } + } + } } } diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Filter.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Filter.kt index d0b443c4a..1391891fd 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Filter.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Filter.kt @@ -18,7 +18,7 @@ import lang.taxi.types.QualifiedName object Filter : CollectionFilteringFunction() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Filter.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/FilterEach.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/FilterEach.kt index eb4eea5ad..e414d0136 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/FilterEach.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/FilterEach.kt @@ -16,7 +16,7 @@ import lang.taxi.types.QualifiedName object FilterEach : CollectionFilteringFunction() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.FilterEach.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/First.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/First.kt index 4dd5ac5e0..bd08d8b9c 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/First.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/First.kt @@ -17,7 +17,7 @@ import lang.taxi.types.QualifiedName abstract class CollectionNavigatingFunction : NamedFunctionInvoker, CollectionFilteringFunction() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/IfEmpty.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/IfEmpty.kt index 87aacd258..4bc5eb119 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/IfEmpty.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/IfEmpty.kt @@ -16,7 +16,7 @@ object IfEmpty : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.IfEmpty.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/IndexOf.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/IndexOf.kt index aae5b1980..936ba5aa4 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/IndexOf.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/IndexOf.kt @@ -15,7 +15,7 @@ import lang.taxi.types.FormatsAndZoneOffset import lang.taxi.types.QualifiedName object IndexOf : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Intersection.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Intersection.kt index db02c6e31..3349a2d3b 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Intersection.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Intersection.kt @@ -33,7 +33,7 @@ object Intersection: NamedFunctionInvoker, CollectionFilteringFunction() { } } - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/JoinToString.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/JoinToString.kt index e678295db..d1c46f9e7 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/JoinToString.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/JoinToString.kt @@ -14,7 +14,7 @@ import lang.taxi.types.QualifiedName object JoinToString : NamedFunctionInvoker { override val functionName: QualifiedName = lang.taxi.functions.stdlib.JoinToString.name - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/ListOf.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/ListOf.kt index e24c87948..9c4f129ef 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/ListOf.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/ListOf.kt @@ -17,7 +17,7 @@ import lang.taxi.types.QualifiedName object ListOf : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.ListOf.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/OrEmpty.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/OrEmpty.kt index b70bd3891..d341e760c 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/OrEmpty.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/OrEmpty.kt @@ -16,7 +16,7 @@ import lang.taxi.types.QualifiedName object OrEmpty : NamedFunctionInvoker { // Intentionally not NullSafeInvoker() as the first argument is often null override val functionName: QualifiedName = lang.taxi.functions.stdlib.OrEmpty.name - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Single.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Single.kt index f64c9736d..a17facbbb 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Single.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/Single.kt @@ -16,7 +16,7 @@ import mu.KotlinLogging object Single : NamedFunctionInvoker, CollectionFilteringFunction() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Single.name private val logger = KotlinLogging.logger {} - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/SingleBy.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/SingleBy.kt index cde97cf33..5fa230cd4 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/SingleBy.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/SingleBy.kt @@ -15,7 +15,7 @@ import mu.KotlinLogging object SingleBy : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.SingleBy.name private val logger = KotlinLogging.logger {} - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -43,15 +43,15 @@ object SingleBy : NullSafeInvoker() { resultCacheKey ) { val stopwatch = Stopwatch.createStarted() - val grouped = collection.groupBy { collectionMember -> + val grouped = mutableMapOf>() + for (collectionMember in collection) { val factBag = FactBagValueSupplier.of(listOf(collectionMember), schema, thisScopeValueSupplier = thisScopeValueSupplier) -// val reader = AccessorReader(factBag, schema.functionRegistry, schema, functionResultCache = resultCache) val evaluated = deferredInstance.evaluate(collectionMember, dataSource, factBag, functionResultCache = resultCache) if (evaluated is TypedNull) { deferredInstance.evaluate(collectionMember, dataSource, factBag, functionResultCache = resultCache) } - evaluated + grouped.getOrPut(evaluated) { mutableListOf() }.add(collectionMember) } logger.debug { "singleBy grouping function took ${stopwatch.elapsed().toMillis()}ms" } grouped diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/BaseDateMathFunction.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/BaseDateMathFunction.kt index 51bd4834b..6ac8d0723 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/BaseDateMathFunction.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/BaseDateMathFunction.kt @@ -26,7 +26,7 @@ import java.time.temporal.TemporalAmount import java.time.temporal.TemporalUnit abstract class BaseDateMathFunction(private val unit: TemporalUnit) : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/Now.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/Now.kt index 28ba95b8c..e8ed9a8f6 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/Now.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/Now.kt @@ -24,7 +24,7 @@ import java.time.LocalTime import java.time.temporal.Temporal abstract class BaseCurrentTimeInvoker(private val valueProvider: () -> Temporal) : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/ParseDate.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/ParseDate.kt index 40687f9cb..43d34dd6a 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/ParseDate.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/dates/ParseDate.kt @@ -16,7 +16,7 @@ import lang.taxi.types.PrimitiveType import lang.taxi.types.QualifiedName object ParseDate : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/errors/Throw.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/errors/Throw.kt index 7cb152b3d..a8fa97e85 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/errors/Throw.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/errors/Throw.kt @@ -16,7 +16,7 @@ import lang.taxi.types.QualifiedName object Throw : NamedFunctionInvoker { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Throw.name - override fun invoke( + override suspend fun invoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/math/Average.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/math/Average.kt index 1d2624d78..17793a965 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/math/Average.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/math/Average.kt @@ -19,7 +19,7 @@ import java.math.RoundingMode object Average : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.Average.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/math/Round.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/math/Round.kt index 18248e143..e0e97e919 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/math/Round.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/math/Round.kt @@ -15,7 +15,7 @@ import java.math.BigDecimal import java.math.RoundingMode object Round : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/Convert.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/Convert.kt index 28251af5b..33f579be9 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/Convert.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/Convert.kt @@ -14,7 +14,7 @@ import lang.taxi.types.QualifiedName import mu.KotlinLogging object Convert : NullSafeInvoker() { - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, @@ -48,19 +48,20 @@ object Convert : NullSafeInvoker() { val dataSource = EvaluatedExpression(function.asTaxi(), inputValues) val converted = resultCache.getOrPut(resultCacheKey) { - if (targetType.isCollection && source is Collection<*>) { - val typedInstances = source.map { member -> - TypedObjectFactory(targetType.collectionType!!, FactBag.of(member as TypedInstance, schema), schema, source = dataSource, functionRegistry = schema.functionRegistry) + kotlinx.coroutines.runBlocking { + if (targetType.isCollection && source is Collection<*>) { + val typedInstances = source.map { member -> + TypedObjectFactory(targetType.collectionType!!, FactBag.of(member as TypedInstance, schema), schema, source = dataSource, functionRegistry = schema.functionRegistry) + .build() + .convertToRawTypeIfRequired() + } + TypedCollection.arrayOf(targetType.collectionType!!, typedInstances, dataSource) + } else { + TypedObjectFactory(targetType, FactBag.of(source, schema), schema, source = dataSource, functionRegistry = schema.functionRegistry) .build() .convertToRawTypeIfRequired() } - TypedCollection.arrayOf(targetType.collectionType!!, typedInstances, dataSource) - } else { - TypedObjectFactory(targetType, FactBag.of(source, schema), schema, source = dataSource, functionRegistry = schema.functionRegistry) - .build() - .convertToRawTypeIfRequired() } - } return converted as TypedInstance } diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/ToRawType.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/ToRawType.kt index 7490404ff..dfb483bb3 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/ToRawType.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/ToRawType.kt @@ -21,7 +21,7 @@ import lang.taxi.types.QualifiedName object ToRawType : NullSafeInvoker() { override val functionName: QualifiedName = lang.taxi.functions.stdlib.ToRawType.name - override fun doInvoke( + override suspend fun doInvoke( inputValues: List, schema: Schema, returnType: Type, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/query/caching/AbstractMergingStateStore.kt b/vyne-core-types/src/main/java/com/orbitalhq/query/caching/AbstractMergingStateStore.kt index 272f75aea..7b5d2c0f8 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/query/caching/AbstractMergingStateStore.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/query/caching/AbstractMergingStateStore.kt @@ -72,19 +72,21 @@ abstract class AbstractMergingStateStore( companion object { fun buildSumTypeFromValues(sumType: Type, values: List, schema: Schema): TypedInstance { - return TypedObjectFactory( - sumType, - FactBag.of(values, schema), - schema, - source = MixedSources, - // This is important. - // We take the last value when reading from a fact bag. - // More recent values overwrite older values - // This works down the tree - two different types with children fields of - // the same type will pick the more recently inserted - factBagSearchStrategy = FactDiscoveryStrategy.ANY_DEPTH_TAKE_LAST - ) - .build() + return kotlinx.coroutines.runBlocking { + TypedObjectFactory( + sumType, + FactBag.of(values, schema), + schema, + source = MixedSources, + // This is important. + // We take the last value when reading from a fact bag. + // More recent values overwrite older values + // This works down the tree - two different types with children fields of + // the same type will pick the more recently inserted + factBagSearchStrategy = FactDiscoveryStrategy.ANY_DEPTH_TAKE_LAST + ) + .build() + } } } diff --git a/vyne-csv-utils/src/main/java/com/orbitalhq/formats/csv/CsvImporterUtil.kt b/vyne-csv-utils/src/main/java/com/orbitalhq/formats/csv/CsvImporterUtil.kt index 5a219e14b..d163bcac5 100644 --- a/vyne-csv-utils/src/main/java/com/orbitalhq/formats/csv/CsvImporterUtil.kt +++ b/vyne-csv-utils/src/main/java/com/orbitalhq/formats/csv/CsvImporterUtil.kt @@ -33,15 +33,17 @@ object CsvImporterUtil { // .filter { parsed.headerNames == null || parsed.headerNames.isEmpty() || parsed.headerNames.size == it.size() } .map { csvRecord -> ParsedTypeInstance( - TypedObjectFactory( - targetType, - csvRecord, - schema, - nullValues, - source = Provided, - functionRegistry = functionRegistry, - formatSpecs = emptyList() - ).build() + kotlinx.coroutines.runBlocking { + TypedObjectFactory( + targetType, + csvRecord, + schema, + nullValues, + source = Provided, + functionRegistry = functionRegistry, + formatSpecs = emptyList() + ).build() + } ) } return records From f6e0f3359ced083054c48dfcd618de3ccf7660b2 Mon Sep 17 00:00:00 2001 From: Marty Pitt Date: Fri, 20 Feb 2026 09:13:08 +0000 Subject: [PATCH 4/6] refactor: fix compilation errors from async conversion, propagate suspend further Fixes compilation errors discovered when building the async TypedObjectFactory refactoring on a fresh checkout: - Make xtimed() and timeBucket() inline so suspend calls work inside their lambdas - Propagate suspend through: newFactory(), withAdditionalScopedFacts(), LogicalExpressionEvaluator, ConstraintEvaluation extension functions - Propagate suspend through taxiql-query-engine: DirectServiceInvocationStrategy, QueryOperationInvocationStrategy, TaxiQlGrammarQueryBuilder, Vyne - Convert non-suspend collection lambdas (fold, associateWith, mapNotNull, map) to explicit for loops where they call suspend functions - Delete dead code DefaultConstraintEvaluator (never instantiated, can't compile with suspend Constraint.evaluate extension) - Fix test compilation with runBlocking at test entry points Status: mvn compile succeeds. Tests in vyne-core-types and taxiql-query-engine pass. Remaining modules not yet tested (functions-binding, stream-engine, schema-server-core may have additional test compilation fixes needed). Co-Authored-By: Claude Opus 4.6 --- .../src/main/java/com/orbitalhq/Vyne.kt | 43 +++++---- .../query/DirectServiceInvocationStrategy.kt | 91 +++++++++---------- .../query/QueryOperationInvocationStrategy.kt | 27 +++--- .../queryBuilders/QueryGrammarQueryBuilder.kt | 2 +- .../TaxiQlGrammarQueryBuilder.kt | 20 ++-- .../models/ConditionalFieldReaderTest.kt | 6 +- .../models/functions/stdlib/StringsTest.kt | 6 +- .../QueryOperationInvocationStrategyTest.kt | 24 ++--- .../TaxiQlGrammarQueryBuilderTest.kt | 3 +- .../java/com/orbitalhq/utils/TimeBucketed.kt | 2 +- .../main/java/com/orbitalhq/utils/Timed.kt | 2 +- .../models/EvaluationValueSupplier.kt | 4 +- .../orbitalhq/models/FactBagValueSupplier.kt | 2 +- .../orbitalhq/models/TypedObjectFactory.kt | 4 +- .../conditional/LogicalExpressionEvaluator.kt | 12 ++- .../constraints/ConstraintEvaluation.kt | 4 +- .../constraints/DefaultConstraintEvaluator.kt | 14 --- 17 files changed, 130 insertions(+), 136 deletions(-) delete mode 100644 vyne-core-types/src/main/java/com/orbitalhq/models/constraints/DefaultConstraintEvaluator.kt diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt index c8566a52f..058698014 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt @@ -230,7 +230,7 @@ class Vyne( } @VisibleForTesting - internal fun buildContextAndExpression( + internal suspend fun buildContextAndExpression( taxiQl: TaxiQlQuery, queryId: String, clientQueryId: String?, @@ -277,7 +277,7 @@ class Vyne( ) } - private fun convertTaxiQlFactToInstances( + private suspend fun convertTaxiQlFactToInstances( taxiQl: TaxiQlQuery, arguments: Map, executionContextFacts: Set = emptySet() @@ -314,27 +314,26 @@ class Vyne( // to us are available in the expressions we're evaluating. // eg: // given { name : String = 'foo' , upper = upperCase(name) } - val evaluatedExpressions = taxiQl.facts - .filter { it.value is FactValue.Expression } - .fold(constants) { previousParams, parameter -> - val facts = CopyOnWriteFactBag(emptyList(), schema, previousParams) - val valueSupplier = FactBagValueSupplier(facts, schema) - val accessorReader = AccessorReader( - valueSupplier, - schema.functionRegistry, - schema - ) + var evaluatedExpressions = constants + for (parameter in taxiQl.facts.filter { it.value is FactValue.Expression }) { + val facts = CopyOnWriteFactBag(emptyList(), schema, evaluatedExpressions) + val valueSupplier = FactBagValueSupplier(facts, schema) + val accessorReader = AccessorReader( + valueSupplier, + schema.functionRegistry, + schema + ) - val expression = parameter.value as FactValue.Expression - val evaluationResult = accessorReader.evaluate( - value = facts, - returnType = schema.type(parameter.type), - expression = expression.expression, - format = null, - dataSource = Provided - ) - previousParams + ScopedFact(ProjectionFunctionScope(parameter.name, parameter.type), evaluationResult) - } + val expression = parameter.value as FactValue.Expression + val evaluationResult = accessorReader.evaluate( + value = facts, + returnType = schema.type(parameter.type), + expression = expression.expression, + format = null, + dataSource = Provided + ) + evaluatedExpressions = evaluatedExpressions + ScopedFact(ProjectionFunctionScope(parameter.name, parameter.type), evaluationResult) + } return evaluatedExpressions.map { it.scope.name to it.fact } .toMap() diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/DirectServiceInvocationStrategy.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/DirectServiceInvocationStrategy.kt index e02c7453a..3ab08e842 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/DirectServiceInvocationStrategy.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/DirectServiceInvocationStrategy.kt @@ -68,7 +68,7 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ return invokeOperations(operations, context, target) } - private fun lookForCandidateServices( + private suspend fun lookForCandidateServices( context: QueryContext, target: Set ): Map>> { @@ -84,13 +84,16 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ /** * Returns the operations that we can invoke, grouped by target query node. */ - private fun getCandidateOperations( + private suspend fun getCandidateOperations( schema: Schema, target: Set, context: QueryContext ): Map>> { - val grouped = target.map { it to getCandidateOperations(schema, it, context) } - .groupBy({ it.first }, { it.second }) + val grouped = mutableMapOf>>>() + for (item in target) { + val candidates = getCandidateOperations(schema, item, context) + grouped.getOrPut(item) { mutableListOf() }.add(candidates) + } val result = grouped.mapValues { (_, operationParameterMaps) -> operationParameterMaps.reduce { acc, map -> acc + map } @@ -103,7 +106,7 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ * (either because they have no parameters, or because all their parameters are populated by constraints) * and the set of parameters that we have identified values for */ - private fun getCandidateOperations( + private suspend fun getCandidateOperations( schema: Schema, target: QuerySpecTypeNode, context: QueryContext @@ -114,34 +117,28 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ it.returnType.isAssignableTo(target.type) && it.operationType == OperationScope.READ_ONLY } } - val operations = operationsForType - .mapNotNull { operation -> - val (satisfiesConstraints, operationParameters) = compareOperationContractToDataRequirementsAndFetchSearchParams( - operation, - target, - schema, - context - ) - if (!satisfiesConstraints) { - null - } else { - operation to operationParameters - } - } - .map { (operation, parameters) -> - populateParamsFromContextValues(operation, parameters, context) + val result = mutableMapOf>() + for (operation in operationsForType) { + val (satisfiesConstraints, operationParameters) = compareOperationContractToDataRequirementsAndFetchSearchParams( + operation, + target, + schema, + context + ) + if (!satisfiesConstraints) continue + + val (op1, params1) = populateParamsFromContextValues(operation, operationParameters, context) + val (op2, params2) = provideUnpopulatedParametersWithDefaults(op1, params1, context) + + // Check to see if there are any outstanding parameters that haven't been populated + val unpopulatedParams = op2.parameters.filter { parameter -> + !params2.containsKey(parameter) && !parameter.nullable } - .map { (operation, parameters) -> - provideUnpopulatedParametersWithDefaults(operation, parameters, context) + if (unpopulatedParams.isEmpty()) { + result[op2] = params2 } - .filter { (operation, populatedOperationParameters) -> - // Check to see if there are any outstanding parameters that haven't been populated - val unpopulatedParams = operation.parameters.filter { parameter -> - !populatedOperationParameters.containsKey(parameter) && !parameter.nullable - } - unpopulatedParams.isEmpty() - } - return operations.toMap() + } + return result } /** @@ -166,17 +163,17 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ * Adds any parameters that are so far unpopulated, but * have a default expression that can be used to populate them */ - private fun provideUnpopulatedParametersWithDefaults( + private suspend fun provideUnpopulatedParametersWithDefaults( operation: RemoteOperation, parameters: Map, context: QueryContext ): Pair> { - val defaultValues = operation.parameters - .filter { it.defaultValue != null } - .filter { !parameters.containsKey(it) } - .associateWith { parameter -> - context.evaluate(parameter.defaultValue!!) + val defaultValues = mutableMapOf() + for (param in operation.parameters) { + if (param.defaultValue != null && !parameters.containsKey(param)) { + defaultValues[param] = context.evaluate(param.defaultValue!!) } + } return operation to (parameters + defaultValues) } @@ -185,7 +182,7 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ * If a contract exists on the target which provides input params, and the operation * can satisfy the contract, then the parameters to search inputs are returned mapped. */ - private fun compareOperationContractToDataRequirementsAndFetchSearchParams( + private suspend fun compareOperationContractToDataRequirementsAndFetchSearchParams( remoteOperation: RemoteOperation, target: QuerySpecTypeNode, schema: Schema, @@ -208,12 +205,11 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ // when evaluted against a contract of // find { Film[](PublicationDate >= 2020-10-02) // would provide a value of `date` - val satisfiedConstraints = targetDataConstraints.mapNotNull { requiredConstraint -> + val satisfiedConstraints = mutableListOf>>>() + for (requiredConstraint in targetDataConstraints) { val constraintComparison = remoteOperation.contract.satisfies(requiredConstraint) if (constraintComparison.satisfiesRequestedConstraint) { - requiredConstraint to getProvidedParameterValues(remoteOperation, constraintComparison.providedValues, context, schema) - } else { - null + satisfiedConstraints.add(requiredConstraint to getProvidedParameterValues(remoteOperation, constraintComparison.providedValues, context, schema)) } } @@ -224,19 +220,20 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ return allOperationConstraintsSatisfied to operationConstraintParameterValues } - private fun getProvidedParameterValues( + private suspend fun getProvidedParameterValues( remoteOperation: Operation, providedValues: List>, context: QueryContext, schema: Schema ): List> { - return providedValues.mapNotNull { (paramExpression, providedValueExpression) -> + val result = mutableListOf>() + for ((paramExpression, providedValueExpression) in providedValues) { when (paramExpression) { is ArgumentSelector -> { val parameter = remoteOperation.parameter(paramExpression.scopeWithPath) if (parameter == null) { logger.warn { "An expression was found to provide a value for parameter ${paramExpression.path}, but no such parameter exists on operation ${remoteOperation.name}" } - return@mapNotNull null + continue } // Short circut - if it's a literal (which is the most common case), then // provide the value @@ -250,15 +247,15 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ else -> context.evaluate(expression = providedValueExpression) } - parameter to expressionResult + result.add(parameter to expressionResult) } else -> { logger.warn { "Not implemented: Mapping parameterExpression of type ${paramExpression::class.simpleName}" } - null } } } + return result } } diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryOperationInvocationStrategy.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryOperationInvocationStrategy.kt index 91293d9a2..828db8234 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryOperationInvocationStrategy.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryOperationInvocationStrategy.kt @@ -51,17 +51,19 @@ class QueryOperationInvocationStrategy( return result } - private fun lookForCandidateQueryOperations( + private suspend fun lookForCandidateQueryOperations( context: QueryContext, target: Set ): Map>> { - return target.associateWith { querySpecTypeNode -> - lookForCandidateQueryOperations(context.schema, querySpecTypeNode, context) + val result = mutableMapOf>>() + for (querySpecTypeNode in target) { + result[querySpecTypeNode] = lookForCandidateQueryOperations(context.schema, querySpecTypeNode, context) } + return result } @VisibleForTesting - internal fun lookForCandidateQueryOperations( + internal suspend fun lookForCandidateQueryOperations( schema: Schema, target: QuerySpecTypeNode, context: QueryContext @@ -75,7 +77,7 @@ class QueryOperationInvocationStrategy( .filter { it.returnType.isAssignableTo(target.type) } .filter { it.hasFilterCapability } - return queryOperations.filter { + val filtered = queryOperations.filter { queryServiceSatisfiesConstraints( schema, it, @@ -84,15 +86,16 @@ class QueryOperationInvocationStrategy( ) } .mapNotNull { queryOperation -> findGrammarBuilder(queryOperation) } - .map { (queryOperation, grammarBuilder) -> - val queryTarget = if (isCovariance(queryOperation.returnType, target.type)) { - target.copy(type = queryOperation.returnType) - } else target - queryOperation to grammarBuilder.buildQuery(queryTarget, queryOperation, schema, context) + val result = mutableMapOf>() + for ((queryOperation, grammarBuilder) in filtered) { + val queryTarget = if (isCovariance(queryOperation.returnType, target.type)) { + target.copy(type = queryOperation.returnType) + } else target - } - .toList().toMap() + result[queryOperation] = grammarBuilder.buildQuery(queryTarget, queryOperation, schema, context) + } + return result } // TODO : We shouldn't be doing this kind of covariance checking here - these filters diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/QueryGrammarQueryBuilder.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/QueryGrammarQueryBuilder.kt index bf965d02f..439dd84f8 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/QueryGrammarQueryBuilder.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/QueryGrammarQueryBuilder.kt @@ -23,7 +23,7 @@ interface QueryGrammarQueryBuilder { return this.supportedGrammars.contains(grammar) } - fun buildQuery( + suspend fun buildQuery( spec: QuerySpecTypeNode, queryOperation: QueryOperation, schema: Schema, diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilder.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilder.kt index 70b80fead..66c8ad4f6 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilder.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilder.kt @@ -39,7 +39,7 @@ private val logger = KotlinLogging.logger {} */ class TaxiQlGrammarQueryBuilder : QueryGrammarQueryBuilder { override val supportedGrammars: List = listOf(VyneQlGrammar.GRAMMAR_NAME) - override fun buildQuery( + override suspend fun buildQuery( spec: QuerySpecTypeNode, queryOperation: QueryOperation, schema: Schema, @@ -61,7 +61,7 @@ class TaxiQlGrammarQueryBuilder : QueryGrammarQueryBuilder { } @VisibleForTesting - internal fun buildTaxiQl(spec: QuerySpecTypeNode, context: QueryContext): Pair> { + internal suspend fun buildTaxiQl(spec: QuerySpecTypeNode, context: QueryContext): Pair> { val constraints = spec.dataConstraints if (constraints.size > 1) { logger.warn { "Received multiple constraints - expected a single, compound constraint. ${constraints.joinToString()}" } @@ -69,20 +69,23 @@ class TaxiQlGrammarQueryBuilder : QueryGrammarQueryBuilder { // In converting the expressions to Taxi, we also resolve any placeholder variables // using the context - val statementsAndValues = constraints.map { buildConstraint(it, context) } + val statementsAndValues = mutableListOf>>() + for (constraint in constraints) { + statementsAndValues.add(buildConstraint(constraint, context)) + } val constraintsStatement = statementsAndValues.joinToString("\n", prefix = "(\n", postfix = "\n)") { it.first } val resolvedValues = statementsAndValues.flatMap { it.second } return """find { ${spec.type.name.parameterizedName}${constraintsStatement} }""" to resolvedValues } - private fun buildConstraint(constraint: Constraint, context: QueryContext): Pair> { + private suspend fun buildConstraint(constraint: Constraint, context: QueryContext): Pair> { return when (constraint) { is ExpressionConstraint -> buildExpressionConstraint(constraint, context) else -> error("Support for constraint type ${constraint::class.simpleName} not implemented yet") } } - private fun buildExpressionConstraint( + private suspend fun buildExpressionConstraint( constraint: ExpressionConstraint, context: QueryContext ): Pair> { @@ -135,7 +138,7 @@ private fun typedInstanceToLiteralExpressionAndValues( * returning a new expression where things like ArgumentSelectors have been replaced * with Literals */ -fun Expression.resolveVariablesUsing(context: QueryContext): Pair> { +suspend fun Expression.resolveVariablesUsing(context: QueryContext): Pair> { return when (this) { is OperatorExpression -> { val (lhs, lhsInstances) = lhs.resolveVariablesUsing(context) @@ -148,8 +151,9 @@ fun Expression.resolveVariablesUsing(context: QueryContext): Pair { - val resolved = this.members.map { - it.resolveVariablesUsing(context) + val resolved = mutableListOf>>() + for (member in this.members) { + resolved.add(member.resolveVariablesUsing(context)) } val resolvedList = resolved.flatMap { it.second } diff --git a/taxiql-query-engine/src/test/java/com/orbitalhq/models/ConditionalFieldReaderTest.kt b/taxiql-query-engine/src/test/java/com/orbitalhq/models/ConditionalFieldReaderTest.kt index d1d22afea..ae397e240 100644 --- a/taxiql-query-engine/src/test/java/com/orbitalhq/models/ConditionalFieldReaderTest.kt +++ b/taxiql-query-engine/src/test/java/com/orbitalhq/models/ConditionalFieldReaderTest.kt @@ -123,7 +123,7 @@ type TransformedTradeRecord { } """) val json = """{ "bankDirection" : "Buy" }""" - val order = TypedObjectFactory(vyne.schema.type("Order"), json, vyne.schema, source = Provided).build() as TypedObject + val order = kotlinx.coroutines.runBlocking { TypedObjectFactory(vyne.schema.type("Order"), json, vyne.schema, source = Provided).build() } as TypedObject order["clientDirection"].value!!.should.equal("Sell") order["bankDirection"].value!!.should.equal("Buy") @@ -142,7 +142,7 @@ type TransformedTradeRecord { } """) val json = """{ "bankDirection" : "buy" }""" - val order = TypedObjectFactory(vyne.schema.type("Order"), json, vyne.schema, source = Provided).build() as TypedObject + val order = kotlinx.coroutines.runBlocking { TypedObjectFactory(vyne.schema.type("Order"), json, vyne.schema, source = Provided).build() } as TypedObject order["bankDirection"].value!!.should.equal("buy") order["clientDirection"].value.should.be.`null` @@ -162,7 +162,7 @@ type TransformedTradeRecord { } """) val json = """{ "bankDirection" : "buy" }""" - val order = TypedObjectFactory(vyne.schema.type("Order"), json, vyne.schema, source = Provided).build() as TypedObject + val order = kotlinx.coroutines.runBlocking { TypedObjectFactory(vyne.schema.type("Order"), json, vyne.schema, source = Provided).build() } as TypedObject order["bankDirection"].value!!.should.equal("buy") order["clientDirection"].value.should.equal("Sell") diff --git a/taxiql-query-engine/src/test/java/com/orbitalhq/models/functions/stdlib/StringsTest.kt b/taxiql-query-engine/src/test/java/com/orbitalhq/models/functions/stdlib/StringsTest.kt index a56c8e91f..a52d99485 100644 --- a/taxiql-query-engine/src/test/java/com/orbitalhq/models/functions/stdlib/StringsTest.kt +++ b/taxiql-query-engine/src/test/java/com/orbitalhq/models/functions/stdlib/StringsTest.kt @@ -207,9 +207,9 @@ class StringsTest { val endArg = 4.toTypedValue(intType) val nullArg = TypedNull.create(stringType) - Mid.invoke(listOf(nullArg, startArg, endArg), schema, stringType, stubAccessor, mock { },null).value.should.be.`null` - Mid.invoke(listOf(helloArg, nullArg, endArg), schema, stringType, stubAccessor, mock { },null).value.should.be.`null` - Mid.invoke(listOf(helloArg, startArg, nullArg), schema, stringType, stubAccessor, mock { },null).value.should.be.`null` + kotlinx.coroutines.runBlocking { Mid.invoke(listOf(nullArg, startArg, endArg), schema, stringType, stubAccessor, mock { },null) }.value.should.be.`null` + kotlinx.coroutines.runBlocking { Mid.invoke(listOf(helloArg, nullArg, endArg), schema, stringType, stubAccessor, mock { },null) }.value.should.be.`null` + kotlinx.coroutines.runBlocking { Mid.invoke(listOf(helloArg, startArg, nullArg), schema, stringType, stubAccessor, mock { },null) }.value.should.be.`null` } @Test diff --git a/taxiql-query-engine/src/test/java/com/orbitalhq/query/QueryOperationInvocationStrategyTest.kt b/taxiql-query-engine/src/test/java/com/orbitalhq/query/QueryOperationInvocationStrategyTest.kt index 3b96c7829..8a12804b5 100644 --- a/taxiql-query-engine/src/test/java/com/orbitalhq/query/QueryOperationInvocationStrategyTest.kt +++ b/taxiql-query-engine/src/test/java/com/orbitalhq/query/QueryOperationInvocationStrategyTest.kt @@ -42,14 +42,14 @@ class QueryOperationInvocationStrategyTest { @Test fun matchesQueryOperationForFindAll() { val (context,querySpecNode) = getQuerySpecNode("find { Person[] }", schema) - val candidates = queryOperationStrategy.lookForCandidateQueryOperations(schema, querySpecNode, context) + val candidates = kotlinx.coroutines.runBlocking { queryOperationStrategy.lookForCandidateQueryOperations(schema, querySpecNode, context) } candidates.should.have.size(1) } @Test fun matchesQueryOperationFilteringEqualsAttributeName() { val (context,querySpecNode) = getQuerySpecNode("find { Person[]( FirstName == 'Jimmy' ) }", schema) - val candidates = queryOperationStrategy.lookForCandidateQueryOperations(schema, querySpecNode, context) + val candidates = kotlinx.coroutines.runBlocking { queryOperationStrategy.lookForCandidateQueryOperations(schema, querySpecNode, context) } candidates.should.have.size(1) } @@ -75,7 +75,7 @@ class QueryOperationInvocationStrategyTest { """.trimIndent() ) val (context,querySpecNode) = getQuerySpecNode("find { Trade[]( TraderName == 'Jimmy' ) }", schema) - val candidates = queryOperationStrategy.lookForCandidateQueryOperations(schema, querySpecNode, context) + val candidates = kotlinx.coroutines.runBlocking { queryOperationStrategy.lookForCandidateQueryOperations(schema, querySpecNode, context) } candidates.should.have.size(2) } @@ -109,7 +109,7 @@ class QueryOperationInvocationStrategyTest { """.trimIndent() ) val (context,querySpecNode) = getQuerySpecNode("find { Trade[]( TraderName == 'Jimmy' ) }", schema) - val candidates = queryOperationStrategy.lookForCandidateQueryOperations(schema, querySpecNode, context) + val candidates = kotlinx.coroutines.runBlocking { queryOperationStrategy.lookForCandidateQueryOperations(schema, querySpecNode, context) } candidates.should.have.size(2) } @@ -190,13 +190,15 @@ class QueryOperationInvocationStrategyTest { fun getQuerySpecNode(taxiQl: String, schema: TaxiSchema): Pair { val (vyne, _) = testVyne(schema) val vyneQuery = Compiler(source = taxiQl, importSources = listOf(schema.document)).queries().first() - val (context, expression) = vyne.buildContextAndExpression( - vyneQuery, - queryId = UUID.randomUUID().toString(), - clientQueryId = null, - queryOptions = QueryOptions.default(), - querySchema = vyne.schema - ) + val (context, expression) = kotlinx.coroutines.runBlocking { + vyne.buildContextAndExpression( + vyneQuery, + queryId = UUID.randomUUID().toString(), + clientQueryId = null, + queryOptions = QueryOptions.default(), + querySchema = vyne.schema + ) + } val queryParser = QueryParser(schema) val querySpecNodes = queryParser.parse(expression) return context to querySpecNodes.first() diff --git a/taxiql-query-engine/src/test/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilderTest.kt b/taxiql-query-engine/src/test/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilderTest.kt index 681a86e33..5d59ad695 100644 --- a/taxiql-query-engine/src/test/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilderTest.kt +++ b/taxiql-query-engine/src/test/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilderTest.kt @@ -133,8 +133,9 @@ LastUpdated >= "2025-05-10T09:30:00Z" private fun generateQuery(querySpecNode: Pair): String { val (context, spec) = querySpecNode - val generated = + val generated = kotlinx.coroutines.runBlocking { queryBuilder.buildQuery(spec, schema.tableOperations.first().queryOperations.first(), schema, context) + } return generated.entries.single().value.toRawObject() as String } } diff --git a/utils/src/main/java/com/orbitalhq/utils/TimeBucketed.kt b/utils/src/main/java/com/orbitalhq/utils/TimeBucketed.kt index 630c956fc..9d61ecf42 100644 --- a/utils/src/main/java/com/orbitalhq/utils/TimeBucketed.kt +++ b/utils/src/main/java/com/orbitalhq/utils/TimeBucketed.kt @@ -26,7 +26,7 @@ class TimeBucketed { } -fun timeBucket(name: String, bucket:TimeBucketed = TimeBucketed.DEFAULT, lambda: () -> T):T { +inline fun timeBucket(name: String, bucket:TimeBucketed = TimeBucketed.DEFAULT, lambda: () -> T):T { val sw = Stopwatch.createStarted() val result = lambda() bucket.addActivity(name, sw.elapsed()) diff --git a/utils/src/main/java/com/orbitalhq/utils/Timed.kt b/utils/src/main/java/com/orbitalhq/utils/Timed.kt index c6bc4359b..a8453a173 100644 --- a/utils/src/main/java/com/orbitalhq/utils/Timed.kt +++ b/utils/src/main/java/com/orbitalhq/utils/Timed.kt @@ -8,7 +8,7 @@ object Timer { val log = Timer.log() } -fun xtimed(name: String, log: Boolean = false, timeUnit: TimeUnit = TimeUnit.MILLISECONDS, block: () -> T): T { +inline fun xtimed(name: String, log: Boolean = false, timeUnit: TimeUnit = TimeUnit.MILLISECONDS, block: () -> T): T { return block() } diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/EvaluationValueSupplier.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/EvaluationValueSupplier.kt index 017bb437f..5c71971ef 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/EvaluationValueSupplier.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/EvaluationValueSupplier.kt @@ -52,7 +52,7 @@ interface ScopedValueProvider { fun getScopedFactOrNull(scope: Argument): TypedInstance? suspend fun getValue(attributeName: AttributeName): TypedInstance - fun withAdditionalScopedFacts(scopedFacts: List):ScopedValueProvider + suspend fun withAdditionalScopedFacts(scopedFacts: List):ScopedValueProvider } class FactBagScopeValueProvider(private val factBag: FactBag, private val schema: Schema):ScopedValueProvider { @@ -69,7 +69,7 @@ class FactBagScopeValueProvider(private val factBag: FactBag, private val schema TODO("Not yet implemented") } - override fun withAdditionalScopedFacts(scopedFacts: List): ScopedValueProvider { + override suspend fun withAdditionalScopedFacts(scopedFacts: List): ScopedValueProvider { return FactBagScopeValueProvider( factBag.withAdditionalScopedFacts(scopedFacts, schema), schema diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/FactBagValueSupplier.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/FactBagValueSupplier.kt index cf6702c54..7a2504df2 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/FactBagValueSupplier.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/FactBagValueSupplier.kt @@ -49,7 +49,7 @@ class FactBagValueSupplier( override val hasDataContext: Boolean = true override val dataContext: SearchableDataContext = facts - override fun withAdditionalScopedFacts(scopedFacts: List): FactBagValueSupplier { + override suspend fun withAdditionalScopedFacts(scopedFacts: List): FactBagValueSupplier { return FactBagValueSupplier( this.facts, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt index f67a8bb92..825b54523 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt @@ -297,7 +297,7 @@ class TypedObjectFactory( * Returns a new TypedObjectFactory, * merging the current set of known values with the newValue if possible. */ - fun newFactory( + suspend fun newFactory( type: Type, newValue: Any, factsToExclude: Set = emptySet(), @@ -600,7 +600,7 @@ class TypedObjectFactory( } } - override fun withAdditionalScopedFacts(scopedFacts: List): TypedObjectFactory { + override suspend fun withAdditionalScopedFacts(scopedFacts: List): TypedObjectFactory { return when (value) { is FactBag -> newFactory( this.type, value.withAdditionalScopedFacts(scopedFacts, this.schema), diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/LogicalExpressionEvaluator.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/LogicalExpressionEvaluator.kt index 241f176fd..4a15c0eb0 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/LogicalExpressionEvaluator.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/LogicalExpressionEvaluator.kt @@ -18,9 +18,9 @@ import java.math.BigDecimal import java.util.* object LogicalExpressionEvaluator { - fun evaluate(cases: List, factory: EvaluationValueSupplier, type: Type): WhenCaseBlock? { - return cases.firstOrNull { case -> - when (case.matchExpression) { + suspend fun evaluate(cases: List, factory: EvaluationValueSupplier, type: Type): WhenCaseBlock? { + for (case in cases) { + val matches = when (case.matchExpression) { is LogicalExpression -> { val expressionStack = Stack>() pushExpression(case.matchExpression as LogicalExpression, expressionStack) @@ -29,7 +29,9 @@ object LogicalExpressionEvaluator { is ElseMatchExpression -> true else -> false } + if (matches) return case } + return null } private fun pushExpression(logicalExpression: LogicalExpression, expressionStack: Stack>) { @@ -48,7 +50,7 @@ object LogicalExpressionEvaluator { } } - private fun evaluateExpressionStack(expressionStack: Stack>, type: Type, factory: EvaluationValueSupplier): Boolean { + private suspend fun evaluateExpressionStack(expressionStack: Stack>, type: Type, factory: EvaluationValueSupplier): Boolean { var result = false var lastLogicalOp: LogicalOp? = null while (!expressionStack.empty()) { @@ -66,7 +68,7 @@ object LogicalExpressionEvaluator { } - private fun evaluateComparisonExpression(logicalExpression: ComparisonExpression, factory: EvaluationValueSupplier): Boolean { + private suspend fun evaluateComparisonExpression(logicalExpression: ComparisonExpression, factory: EvaluationValueSupplier): Boolean { val right = logicalExpression.right val left = logicalExpression.left val (leftValue, rightValue) = when { diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/constraints/ConstraintEvaluation.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/constraints/ConstraintEvaluation.kt index 83a8e3e08..9b84378ae 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/constraints/ConstraintEvaluation.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/constraints/ConstraintEvaluation.kt @@ -25,7 +25,7 @@ import lang.taxi.types.ArgumentSelector import mu.KotlinLogging import kotlin.reflect.KClass -fun Constraint.evaluate( +suspend fun Constraint.evaluate( value: TypedInstance, schema: Schema, context: InPlaceQueryEngine @@ -36,7 +36,7 @@ fun Constraint.evaluate( } } -fun ExpressionConstraint.evaluate( +suspend fun ExpressionConstraint.evaluate( value: TypedInstance, schema: Schema, context: InPlaceQueryEngine diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/constraints/DefaultConstraintEvaluator.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/constraints/DefaultConstraintEvaluator.kt deleted file mode 100644 index 11a009728..000000000 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/constraints/DefaultConstraintEvaluator.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.orbitalhq.models.constraints - -import com.orbitalhq.models.InPlaceQueryEngine -import com.orbitalhq.models.TypedInstance -import com.orbitalhq.schemas.ConstraintEvaluation -import lang.taxi.services.operations.constraints.Constraint - -data class DefaultConstraintEvaluator( - val context: InPlaceQueryEngine -) : ConstraintEvaluator { - override fun evaluate(constraint: Constraint, value: TypedInstance): ConstraintEvaluation { - return constraint.evaluate(value, context.schema, context) - } -} From e367a4d6f24d4deee7dec60e8f2df0ec85160366 Mon Sep 17 00:00:00 2001 From: Marty Pitt Date: Fri, 20 Feb 2026 10:07:51 +0000 Subject: [PATCH 5/6] bumped version --- analytics/pom.xml | 2 +- auth-common/pom.xml | 2 +- auth-tokens/pom.xml | 2 +- avro-message-format/pom.xml | 2 +- cockpit-core/pom.xml | 4 ++-- connectors/aws-connectors/aws-core/pom.xml | 2 +- .../aws-connectors/dynamo-db-connector/pom.xml | 2 +- connectors/aws-connectors/lambda-connector/pom.xml | 2 +- connectors/aws-connectors/pom.xml | 2 +- connectors/aws-connectors/s3-connector/pom.xml | 2 +- connectors/aws-connectors/sqs-connector/pom.xml | 2 +- connectors/azure-connectors/blob-connector/pom.xml | 2 +- connectors/azure-connectors/pom.xml | 2 +- .../azure-connectors/servicebus-connector/pom.xml | 2 +- connectors/connectors-calcite/pom.xml | 4 ++-- connectors/connectors-core/pom.xml | 2 +- connectors/hazelcast-connector/pom.xml | 2 +- connectors/jdbc-connector/pom.xml | 2 +- connectors/kafka-connector/pom.xml | 2 +- .../nosql-connectors/mongodb-connector/pom.xml | 2 +- connectors/nosql-connectors/pom.xml | 2 +- connectors/pom.xml | 2 +- connectors/soap-connector/pom.xml | 2 +- copilot-api/pom.xml | 2 +- copilot/pom.xml | 2 +- datatype-converters/pom.xml | 2 +- events-api/pom.xml | 2 +- formats-common/pom.xml | 2 +- function-loading/functions-api/pom.xml | 2 +- function-loading/functions-binding/pom.xml | 2 +- .../functions-kotlin-scripting-api/pom.xml | 2 +- function-loading/pom.xml | 2 +- function-loading/test-java-project/pom.xml | 2 +- history-persistence/pom.xml | 4 ++-- history-service/pom.xml | 6 +++--- licensing/license-api/pom.xml | 2 +- licensing/license-client/pom.xml | 2 +- licensing/pom.xml | 2 +- metrics-utils/pom.xml | 2 +- monitoring-common/pom.xml | 2 +- nebula-support/pom.xml | 2 +- orbital-taxi-publisher/pom.xml | 2 +- persistence-utils/pom.xml | 2 +- pipelines/pipeline-jet-api/pom.xml | 2 +- pipelines/pipeline-jet/pom.xml | 4 ++-- pipelines/pom.xml | 2 +- pipelines/stream-engine/pom.xml | 4 ++-- plugin-api/pom.xml | 2 +- plugin-loader/pom.xml | 2 +- policy-evaluator/pom.xml | 2 +- pom.xml | 2 +- protobuf-utils/pom.xml | 2 +- query-node-api/pom.xml | 2 +- query-node-core/pom.xml | 4 ++-- query-node-native/pom.xml | 2 +- query-node-service/pom.xml | 2 +- schema-management/pom.xml | 2 +- schema-management/schema-api/pom.xml | 2 +- schema-management/schema-consumer-api/pom.xml | 6 +++--- schema-management/schema-http-common/pom.xml | 2 +- schema-management/schema-http-consumer/pom.xml | 8 ++++---- schema-management/schema-http-publisher/pom.xml | 6 +++--- schema-management/schema-publisher-api/pom.xml | 4 ++-- schema-management/schema-publisher-cli/pom.xml | 4 ++-- .../schema-publisher-spring-boot-starter/pom.xml | 2 +- schema-management/schema-rsocket-common/pom.xml | 4 ++-- schema-management/schema-rsocket-consumer/pom.xml | 8 ++++---- schema-management/schema-rsocket-publisher/pom.xml | 6 +++--- schema-management/schema-spring/pom.xml | 14 +++++++------- schema-server-api/pom.xml | 2 +- schema-server-core/pom.xml | 2 +- schema-server/pom.xml | 2 +- schema-store-client/pom.xml | 2 +- spring-utils/pom.xml | 2 +- station/pom.xml | 2 +- taxi-playground-core/pom.xml | 2 +- taxi-playground/pom.xml | 2 +- taxiql-query-engine/pom.xml | 4 ++-- test-utils/pom.xml | 2 +- utils/pom.xml | 2 +- vyne-analytics-server/pom.xml | 2 +- vyne-client-spring/pom.xml | 2 +- vyne-client/pom.xml | 2 +- vyne-core-types/pom.xml | 2 +- vyne-csv-utils/pom.xml | 4 ++-- vyne-history-core/pom.xml | 2 +- vyne-query-api/pom.xml | 2 +- vyne-query-service/pom.xml | 2 +- vyne-search/pom.xml | 2 +- vyne-spring-http-client/pom.xml | 2 +- vyne-spring-http/pom.xml | 2 +- vyne-spring/pom.xml | 2 +- 92 files changed, 123 insertions(+), 123 deletions(-) diff --git a/analytics/pom.xml b/analytics/pom.xml index a4981c671..508379eec 100644 --- a/analytics/pom.xml +++ b/analytics/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT analytics diff --git a/auth-common/pom.xml b/auth-common/pom.xml index ca221528c..d7bba4cec 100644 --- a/auth-common/pom.xml +++ b/auth-common/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/auth-tokens/pom.xml b/auth-tokens/pom.xml index d3df3db56..810d9b9a4 100644 --- a/auth-tokens/pom.xml +++ b/auth-tokens/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT auth-tokens diff --git a/avro-message-format/pom.xml b/avro-message-format/pom.xml index a76742180..9898731a7 100644 --- a/avro-message-format/pom.xml +++ b/avro-message-format/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT avro-message-format diff --git a/cockpit-core/pom.xml b/cockpit-core/pom.xml index bc4a70af1..fb68f7934 100644 --- a/cockpit-core/pom.xml +++ b/cockpit-core/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT cockpit-core @@ -221,7 +221,7 @@ com.orbitalhq query-node-core - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.taxilang diff --git a/connectors/aws-connectors/aws-core/pom.xml b/connectors/aws-connectors/aws-core/pom.xml index 224e40eff..3da0aae44 100644 --- a/connectors/aws-connectors/aws-core/pom.xml +++ b/connectors/aws-connectors/aws-core/pom.xml @@ -5,7 +5,7 @@ aws-connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/aws-connectors/dynamo-db-connector/pom.xml b/connectors/aws-connectors/dynamo-db-connector/pom.xml index af8fb5c70..cc678571e 100644 --- a/connectors/aws-connectors/dynamo-db-connector/pom.xml +++ b/connectors/aws-connectors/dynamo-db-connector/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq aws-connectors - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT dynamo-db-connector diff --git a/connectors/aws-connectors/lambda-connector/pom.xml b/connectors/aws-connectors/lambda-connector/pom.xml index 82dd3781f..cd7e5f99c 100644 --- a/connectors/aws-connectors/lambda-connector/pom.xml +++ b/connectors/aws-connectors/lambda-connector/pom.xml @@ -5,7 +5,7 @@ aws-connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/aws-connectors/pom.xml b/connectors/aws-connectors/pom.xml index 3a26e0ade..7252669e6 100644 --- a/connectors/aws-connectors/pom.xml +++ b/connectors/aws-connectors/pom.xml @@ -5,7 +5,7 @@ connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/aws-connectors/s3-connector/pom.xml b/connectors/aws-connectors/s3-connector/pom.xml index 4bcd2183c..331b5b73d 100644 --- a/connectors/aws-connectors/s3-connector/pom.xml +++ b/connectors/aws-connectors/s3-connector/pom.xml @@ -5,7 +5,7 @@ aws-connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/aws-connectors/sqs-connector/pom.xml b/connectors/aws-connectors/sqs-connector/pom.xml index 56ab85e79..5a29891c3 100644 --- a/connectors/aws-connectors/sqs-connector/pom.xml +++ b/connectors/aws-connectors/sqs-connector/pom.xml @@ -5,7 +5,7 @@ aws-connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/azure-connectors/blob-connector/pom.xml b/connectors/azure-connectors/blob-connector/pom.xml index b9519a704..0a6d7ffad 100644 --- a/connectors/azure-connectors/blob-connector/pom.xml +++ b/connectors/azure-connectors/blob-connector/pom.xml @@ -5,7 +5,7 @@ azure-connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/azure-connectors/pom.xml b/connectors/azure-connectors/pom.xml index f0d3b0910..be46a8884 100644 --- a/connectors/azure-connectors/pom.xml +++ b/connectors/azure-connectors/pom.xml @@ -5,7 +5,7 @@ connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/azure-connectors/servicebus-connector/pom.xml b/connectors/azure-connectors/servicebus-connector/pom.xml index fbb4c8b95..c1f5ed115 100644 --- a/connectors/azure-connectors/servicebus-connector/pom.xml +++ b/connectors/azure-connectors/servicebus-connector/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq azure-connectors - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT servicebus-connector diff --git a/connectors/connectors-calcite/pom.xml b/connectors/connectors-calcite/pom.xml index d6e4a16a9..5acfa8fcc 100644 --- a/connectors/connectors-calcite/pom.xml +++ b/connectors/connectors-calcite/pom.xml @@ -5,7 +5,7 @@ connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -27,7 +27,7 @@ com.orbitalhq datatype-converters - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.taxilang diff --git a/connectors/connectors-core/pom.xml b/connectors/connectors-core/pom.xml index fe57f5627..a1442207a 100644 --- a/connectors/connectors-core/pom.xml +++ b/connectors/connectors-core/pom.xml @@ -5,7 +5,7 @@ connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/hazelcast-connector/pom.xml b/connectors/hazelcast-connector/pom.xml index 302677312..71d8e3f10 100644 --- a/connectors/hazelcast-connector/pom.xml +++ b/connectors/hazelcast-connector/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq connectors - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT hazelcast-connector diff --git a/connectors/jdbc-connector/pom.xml b/connectors/jdbc-connector/pom.xml index ce16cea74..5438d8565 100644 --- a/connectors/jdbc-connector/pom.xml +++ b/connectors/jdbc-connector/pom.xml @@ -5,7 +5,7 @@ connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/kafka-connector/pom.xml b/connectors/kafka-connector/pom.xml index 43a50a424..1949fd6a6 100644 --- a/connectors/kafka-connector/pom.xml +++ b/connectors/kafka-connector/pom.xml @@ -5,7 +5,7 @@ connectors com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/nosql-connectors/mongodb-connector/pom.xml b/connectors/nosql-connectors/mongodb-connector/pom.xml index 34cd4ab33..073433f8e 100644 --- a/connectors/nosql-connectors/mongodb-connector/pom.xml +++ b/connectors/nosql-connectors/mongodb-connector/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq nosql-connectors - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT mongodb-connector diff --git a/connectors/nosql-connectors/pom.xml b/connectors/nosql-connectors/pom.xml index 8cdc5a4c0..87d6c9a14 100644 --- a/connectors/nosql-connectors/pom.xml +++ b/connectors/nosql-connectors/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq connectors - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT nosql-connectors diff --git a/connectors/pom.xml b/connectors/pom.xml index 7a503dde6..1f9bebac9 100644 --- a/connectors/pom.xml +++ b/connectors/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/connectors/soap-connector/pom.xml b/connectors/soap-connector/pom.xml index 3a33f849f..83e530dd0 100644 --- a/connectors/soap-connector/pom.xml +++ b/connectors/soap-connector/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq connectors - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT soap-connector diff --git a/copilot-api/pom.xml b/copilot-api/pom.xml index 0b4b108c7..6a83e4511 100644 --- a/copilot-api/pom.xml +++ b/copilot-api/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT copilot-api diff --git a/copilot/pom.xml b/copilot/pom.xml index 867f787f6..3f51c16e3 100644 --- a/copilot/pom.xml +++ b/copilot/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT copilot diff --git a/datatype-converters/pom.xml b/datatype-converters/pom.xml index 9270f2375..b50b168d8 100644 --- a/datatype-converters/pom.xml +++ b/datatype-converters/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/events-api/pom.xml b/events-api/pom.xml index 0035e05c3..e3d13d31b 100644 --- a/events-api/pom.xml +++ b/events-api/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/formats-common/pom.xml b/formats-common/pom.xml index 77cbf3a4a..74e8f50e0 100644 --- a/formats-common/pom.xml +++ b/formats-common/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.example diff --git a/function-loading/functions-api/pom.xml b/function-loading/functions-api/pom.xml index 7e22b5f2b..f65baaae1 100644 --- a/function-loading/functions-api/pom.xml +++ b/function-loading/functions-api/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq function-loading - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT API for defining custom user functions diff --git a/function-loading/functions-binding/pom.xml b/function-loading/functions-binding/pom.xml index 246b17482..1adbe9096 100644 --- a/function-loading/functions-binding/pom.xml +++ b/function-loading/functions-binding/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq function-loading - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT functions-binding diff --git a/function-loading/functions-kotlin-scripting-api/pom.xml b/function-loading/functions-kotlin-scripting-api/pom.xml index 85531b28f..249b9438a 100644 --- a/function-loading/functions-kotlin-scripting-api/pom.xml +++ b/function-loading/functions-kotlin-scripting-api/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq function-loading - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT functions-kotlin-scripting-api diff --git a/function-loading/pom.xml b/function-loading/pom.xml index 99429ad74..0db389dd4 100644 --- a/function-loading/pom.xml +++ b/function-loading/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT function-loading diff --git a/function-loading/test-java-project/pom.xml b/function-loading/test-java-project/pom.xml index 2079301f0..9286989f1 100644 --- a/function-loading/test-java-project/pom.xml +++ b/function-loading/test-java-project/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq function-loading - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT test-java-project diff --git a/history-persistence/pom.xml b/history-persistence/pom.xml index aa216e5d5..9de803434 100644 --- a/history-persistence/pom.xml +++ b/history-persistence/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT history-persistence @@ -45,7 +45,7 @@ com.orbitalhq vyne-spring - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT io.projectreactor diff --git a/history-service/pom.xml b/history-service/pom.xml index 142df05dd..1dac31dd0 100644 --- a/history-service/pom.xml +++ b/history-service/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT history-service @@ -37,12 +37,12 @@ com.orbitalhq history-persistence - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.orbitalhq vyne-history-core - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.springframework.security diff --git a/licensing/license-api/pom.xml b/licensing/license-api/pom.xml index 684ac4617..89fc4a0a7 100644 --- a/licensing/license-api/pom.xml +++ b/licensing/license-api/pom.xml @@ -5,7 +5,7 @@ licensing com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/licensing/license-client/pom.xml b/licensing/license-client/pom.xml index a839f99c2..a76f34ecf 100644 --- a/licensing/license-client/pom.xml +++ b/licensing/license-client/pom.xml @@ -5,7 +5,7 @@ licensing com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/licensing/pom.xml b/licensing/pom.xml index c3c6a36eb..12678949d 100644 --- a/licensing/pom.xml +++ b/licensing/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/metrics-utils/pom.xml b/metrics-utils/pom.xml index 97527f3bb..b093f7ea2 100644 --- a/metrics-utils/pom.xml +++ b/metrics-utils/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT metrics-utils diff --git a/monitoring-common/pom.xml b/monitoring-common/pom.xml index e66f8ca21..083a58db6 100644 --- a/monitoring-common/pom.xml +++ b/monitoring-common/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT monitoring-common diff --git a/nebula-support/pom.xml b/nebula-support/pom.xml index 547f051c2..dcc7c33b7 100644 --- a/nebula-support/pom.xml +++ b/nebula-support/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT nebula-support diff --git a/orbital-taxi-publisher/pom.xml b/orbital-taxi-publisher/pom.xml index f4d5076ec..ec7dee051 100644 --- a/orbital-taxi-publisher/pom.xml +++ b/orbital-taxi-publisher/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT orbital-taxi-publisher diff --git a/persistence-utils/pom.xml b/persistence-utils/pom.xml index 112019512..c3f6535d6 100644 --- a/persistence-utils/pom.xml +++ b/persistence-utils/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT persistence-utils diff --git a/pipelines/pipeline-jet-api/pom.xml b/pipelines/pipeline-jet-api/pom.xml index f78e160f7..d9c5487cf 100644 --- a/pipelines/pipeline-jet-api/pom.xml +++ b/pipelines/pipeline-jet-api/pom.xml @@ -5,7 +5,7 @@ pipelines com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/pipelines/pipeline-jet/pom.xml b/pipelines/pipeline-jet/pom.xml index 7cd588498..105d75ecd 100644 --- a/pipelines/pipeline-jet/pom.xml +++ b/pipelines/pipeline-jet/pom.xml @@ -5,7 +5,7 @@ pipelines com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -217,7 +217,7 @@ com.orbitalhq schema-server-core - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT test diff --git a/pipelines/pom.xml b/pipelines/pom.xml index 0af16fe3c..e11967be0 100644 --- a/pipelines/pom.xml +++ b/pipelines/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/pipelines/stream-engine/pom.xml b/pipelines/stream-engine/pom.xml index 1fbcd6d57..2d9c05851 100644 --- a/pipelines/stream-engine/pom.xml +++ b/pipelines/stream-engine/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq pipelines - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT stream-engine @@ -212,7 +212,7 @@ com.orbitalhq schema-server-core - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT test diff --git a/plugin-api/pom.xml b/plugin-api/pom.xml index 281a95dd3..65d0479de 100644 --- a/plugin-api/pom.xml +++ b/plugin-api/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT plugin-api diff --git a/plugin-loader/pom.xml b/plugin-loader/pom.xml index abb83dc20..770f4421a 100644 --- a/plugin-loader/pom.xml +++ b/plugin-loader/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT plugin-loader diff --git a/policy-evaluator/pom.xml b/policy-evaluator/pom.xml index 52423008c..d09a22a17 100644 --- a/policy-evaluator/pom.xml +++ b/policy-evaluator/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.example diff --git a/pom.xml b/pom.xml index 21c198361..2a5d3fcfe 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.orbitalhq platform pom - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.springframework.boot diff --git a/protobuf-utils/pom.xml b/protobuf-utils/pom.xml index 417e1e398..fa17f4022 100644 --- a/protobuf-utils/pom.xml +++ b/protobuf-utils/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/query-node-api/pom.xml b/query-node-api/pom.xml index a3184bad9..ab0d1667d 100644 --- a/query-node-api/pom.xml +++ b/query-node-api/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT query-node-api diff --git a/query-node-core/pom.xml b/query-node-core/pom.xml index 7d785441f..ac6950f45 100644 --- a/query-node-core/pom.xml +++ b/query-node-core/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT query-node-core @@ -197,7 +197,7 @@ com.orbitalhq vyne-spring-http - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.orbitalhq diff --git a/query-node-native/pom.xml b/query-node-native/pom.xml index d04760dbf..9b77fbdb1 100644 --- a/query-node-native/pom.xml +++ b/query-node-native/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT query-node-native diff --git a/query-node-service/pom.xml b/query-node-service/pom.xml index 26d761225..328cd2939 100644 --- a/query-node-service/pom.xml +++ b/query-node-service/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT query-node-service diff --git a/schema-management/pom.xml b/schema-management/pom.xml index 12fba01f2..5f651ea95 100644 --- a/schema-management/pom.xml +++ b/schema-management/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/schema-management/schema-api/pom.xml b/schema-management/schema-api/pom.xml index ed750830c..d0a72bf2c 100644 --- a/schema-management/schema-api/pom.xml +++ b/schema-management/schema-api/pom.xml @@ -5,7 +5,7 @@ schema-management com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/schema-management/schema-consumer-api/pom.xml b/schema-management/schema-consumer-api/pom.xml index ffcb7f55d..6f2bb05ee 100644 --- a/schema-management/schema-consumer-api/pom.xml +++ b/schema-management/schema-consumer-api/pom.xml @@ -5,7 +5,7 @@ schema-management com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -21,12 +21,12 @@ com.orbitalhq schema-api - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.orbitalhq events-api - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.orbitalhq diff --git a/schema-management/schema-http-common/pom.xml b/schema-management/schema-http-common/pom.xml index 322adf29a..2589d0471 100644 --- a/schema-management/schema-http-common/pom.xml +++ b/schema-management/schema-http-common/pom.xml @@ -5,7 +5,7 @@ schema-management com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/schema-management/schema-http-consumer/pom.xml b/schema-management/schema-http-consumer/pom.xml index 728645843..3b5eb7fd0 100644 --- a/schema-management/schema-http-consumer/pom.xml +++ b/schema-management/schema-http-consumer/pom.xml @@ -5,7 +5,7 @@ schema-management com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -20,17 +20,17 @@ com.orbitalhq schema-http-common - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.orbitalhq schema-consumer-api - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.orbitalhq events-api - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT diff --git a/schema-management/schema-http-publisher/pom.xml b/schema-management/schema-http-publisher/pom.xml index abb519513..d05e6cbab 100644 --- a/schema-management/schema-http-publisher/pom.xml +++ b/schema-management/schema-http-publisher/pom.xml @@ -5,7 +5,7 @@ schema-management com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -20,12 +20,12 @@ com.orbitalhq schema-http-common - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.orbitalhq schema-publisher-api - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.springframework.retry diff --git a/schema-management/schema-publisher-api/pom.xml b/schema-management/schema-publisher-api/pom.xml index 494fc6be9..b592b3f80 100644 --- a/schema-management/schema-publisher-api/pom.xml +++ b/schema-management/schema-publisher-api/pom.xml @@ -5,7 +5,7 @@ schema-management com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -20,7 +20,7 @@ com.orbitalhq schema-api - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.taxilang diff --git a/schema-management/schema-publisher-cli/pom.xml b/schema-management/schema-publisher-cli/pom.xml index a23fd093d..396005b84 100644 --- a/schema-management/schema-publisher-cli/pom.xml +++ b/schema-management/schema-publisher-cli/pom.xml @@ -5,7 +5,7 @@ schema-management com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -28,7 +28,7 @@ com.orbitalhq schema-http-publisher - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.github.ajalt.clikt diff --git a/schema-management/schema-publisher-spring-boot-starter/pom.xml b/schema-management/schema-publisher-spring-boot-starter/pom.xml index f2d1cbe52..390f774ee 100644 --- a/schema-management/schema-publisher-spring-boot-starter/pom.xml +++ b/schema-management/schema-publisher-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq schema-management - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT schema-publisher-spring-boot-starter diff --git a/schema-management/schema-rsocket-common/pom.xml b/schema-management/schema-rsocket-common/pom.xml index d2fbc5005..9f0506d5a 100644 --- a/schema-management/schema-rsocket-common/pom.xml +++ b/schema-management/schema-rsocket-common/pom.xml @@ -5,7 +5,7 @@ schema-management com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -22,7 +22,7 @@ com.orbitalhq schema-api - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT diff --git a/schema-management/schema-spring/pom.xml b/schema-management/schema-spring/pom.xml index 7a4d1d1e8..3fc8273c0 100644 --- a/schema-management/schema-spring/pom.xml +++ b/schema-management/schema-spring/pom.xml @@ -5,7 +5,7 @@ schema-management com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -46,7 +46,7 @@ com.orbitalhq schema-publisher-api - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.taxilang @@ -56,12 +56,12 @@ com.orbitalhq schema-rsocket-publisher - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.orbitalhq schema-rsocket-consumer - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.springframework.boot @@ -70,17 +70,17 @@ com.orbitalhq schema-rsocket-common - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.orbitalhq schema-http-publisher - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT com.orbitalhq schema-http-consumer - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT org.springframework diff --git a/schema-server-api/pom.xml b/schema-server-api/pom.xml index 1ed30f2fc..a4aa6b6a6 100644 --- a/schema-server-api/pom.xml +++ b/schema-server-api/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/schema-server-core/pom.xml b/schema-server-core/pom.xml index 86dcd098c..236c8e3d6 100644 --- a/schema-server-core/pom.xml +++ b/schema-server-core/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/schema-server/pom.xml b/schema-server/pom.xml index 27e60335e..aa86f41a8 100644 --- a/schema-server/pom.xml +++ b/schema-server/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/schema-store-client/pom.xml b/schema-store-client/pom.xml index bea1efe04..22d14ea48 100644 --- a/schema-store-client/pom.xml +++ b/schema-store-client/pom.xml @@ -3,7 +3,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/spring-utils/pom.xml b/spring-utils/pom.xml index 13759fea0..f434df5cb 100644 --- a/spring-utils/pom.xml +++ b/spring-utils/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT spring-utils diff --git a/station/pom.xml b/station/pom.xml index 9369e2e1b..d52054b01 100644 --- a/station/pom.xml +++ b/station/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/taxi-playground-core/pom.xml b/taxi-playground-core/pom.xml index 86d7dc06a..e76b4bc5f 100644 --- a/taxi-playground-core/pom.xml +++ b/taxi-playground-core/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT taxi-playground-core diff --git a/taxi-playground/pom.xml b/taxi-playground/pom.xml index 5ffcc15f7..9bc4f9bbd 100644 --- a/taxi-playground/pom.xml +++ b/taxi-playground/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/taxiql-query-engine/pom.xml b/taxiql-query-engine/pom.xml index d0e2689b7..3b02ff0d0 100644 --- a/taxiql-query-engine/pom.xml +++ b/taxiql-query-engine/pom.xml @@ -4,7 +4,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -191,7 +191,7 @@ Do not allow this to leak beyond a test scope com.orbitalhq vyne-spring-http - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT test diff --git a/test-utils/pom.xml b/test-utils/pom.xml index fef5ead35..acecf2536 100644 --- a/test-utils/pom.xml +++ b/test-utils/pom.xml @@ -6,7 +6,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT test-utils diff --git a/utils/pom.xml b/utils/pom.xml index 794c6779c..ca078717f 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-analytics-server/pom.xml b/vyne-analytics-server/pom.xml index 61fefba31..a7556d686 100644 --- a/vyne-analytics-server/pom.xml +++ b/vyne-analytics-server/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-client-spring/pom.xml b/vyne-client-spring/pom.xml index 9ce25152b..c4548f0b5 100644 --- a/vyne-client-spring/pom.xml +++ b/vyne-client-spring/pom.xml @@ -3,7 +3,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-client/pom.xml b/vyne-client/pom.xml index bfd54d7eb..94d088168 100644 --- a/vyne-client/pom.xml +++ b/vyne-client/pom.xml @@ -4,7 +4,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-core-types/pom.xml b/vyne-core-types/pom.xml index 93bc808a3..a8e6a41a4 100644 --- a/vyne-core-types/pom.xml +++ b/vyne-core-types/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-csv-utils/pom.xml b/vyne-csv-utils/pom.xml index 2fccc6a29..ddb1c145a 100644 --- a/vyne-csv-utils/pom.xml +++ b/vyne-csv-utils/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 @@ -58,7 +58,7 @@ com.orbitalhq datatype-converters - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT test diff --git a/vyne-history-core/pom.xml b/vyne-history-core/pom.xml index 200936870..14fc4109a 100644 --- a/vyne-history-core/pom.xml +++ b/vyne-history-core/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-query-api/pom.xml b/vyne-query-api/pom.xml index f36840463..5133172d1 100644 --- a/vyne-query-api/pom.xml +++ b/vyne-query-api/pom.xml @@ -4,7 +4,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-query-service/pom.xml b/vyne-query-service/pom.xml index a724f55d5..2dd676704 100644 --- a/vyne-query-service/pom.xml +++ b/vyne-query-service/pom.xml @@ -4,7 +4,7 @@ com.orbitalhq platform - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-search/pom.xml b/vyne-search/pom.xml index 107df34b3..1995d6c77 100644 --- a/vyne-search/pom.xml +++ b/vyne-search/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-spring-http-client/pom.xml b/vyne-spring-http-client/pom.xml index cd1bce152..9a3bb25d8 100644 --- a/vyne-spring-http-client/pom.xml +++ b/vyne-spring-http-client/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-spring-http/pom.xml b/vyne-spring-http/pom.xml index 40f734569..d04d8aa33 100644 --- a/vyne-spring-http/pom.xml +++ b/vyne-spring-http/pom.xml @@ -5,7 +5,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 diff --git a/vyne-spring/pom.xml b/vyne-spring/pom.xml index 42dfa1425..570f8ba0a 100644 --- a/vyne-spring/pom.xml +++ b/vyne-spring/pom.xml @@ -3,7 +3,7 @@ platform com.orbitalhq - 0.37.0-SNAPSHOT + d0.38.0-SNAPSHOT 4.0.0 From 1aa13182392b4ed7d9f37c91f904f4feae0a5a53 Mon Sep 17 00:00:00 2001 From: Marty Pitt Date: Fri, 20 Feb 2026 10:53:56 +0000 Subject: [PATCH 6/6] PR feedback, removed unrelated changes --- cockpit-core/pom.xml | 10 +- .../ResultStreamAuthorizationDecorator.kt | 20 ++- .../src/main/java/com/orbitalhq/Vyne.kt | 15 +-- .../query/DirectServiceInvocationStrategy.kt | 17 +-- .../query/QueryOperationInvocationStrategy.kt | 6 +- .../TaxiQlGrammarQueryBuilder.kt | 10 +- .../orbitalhq/models/TypedObjectFactory.kt | 24 +--- .../conditional/LogicalExpressionEvaluator.kt | 125 ------------------ .../models/functions/stdlib/Functional.kt | 42 ++++-- .../CollectionFilteringFunction.kt | 26 ++-- .../CollectionPredicateFunctions.kt | 15 ++- .../functions/stdlib/collections/SingleBy.kt | 6 +- .../functions/stdlib/transform/Convert.kt | 19 ++- vyne-query-service/pom.xml | 10 +- 14 files changed, 114 insertions(+), 231 deletions(-) delete mode 100644 vyne-core-types/src/main/java/com/orbitalhq/models/conditional/LogicalExpressionEvaluator.kt diff --git a/cockpit-core/pom.xml b/cockpit-core/pom.xml index fb68f7934..cc5d67c09 100644 --- a/cockpit-core/pom.xml +++ b/cockpit-core/pom.xml @@ -434,11 +434,11 @@ - - com.orbitalhq - test-cli - ${project.version} - + + + + + org.jetbrains.kotlinx diff --git a/pipelines/stream-engine/src/main/java/com/orbitalhq/pipelines/jet/streams/ResultStreamAuthorizationDecorator.kt b/pipelines/stream-engine/src/main/java/com/orbitalhq/pipelines/jet/streams/ResultStreamAuthorizationDecorator.kt index f018e6011..56aee224c 100644 --- a/pipelines/stream-engine/src/main/java/com/orbitalhq/pipelines/jet/streams/ResultStreamAuthorizationDecorator.kt +++ b/pipelines/stream-engine/src/main/java/com/orbitalhq/pipelines/jet/streams/ResultStreamAuthorizationDecorator.kt @@ -9,6 +9,7 @@ import com.orbitalhq.models.TypedInstance import com.orbitalhq.query.policyManager.ExecutionScope import com.orbitalhq.query.policyManager.PolicyEvaluator import com.orbitalhq.schemas.Schema +import kotlinx.coroutines.reactor.mono import lang.taxi.policies.PolicyOperationScope import lang.taxi.services.OperationScope import org.springframework.security.core.Authentication @@ -44,13 +45,18 @@ class ResultStreamAuthorizationDecorator( val queryReturnType = querySchema.type(query.returnType) val instanceType = queryReturnType.collectionType ?: queryReturnType - return stream.mapNotNull { value -> - // first, parse back to a typed instance - val valueAsTypedInstance = TypedInstance.from(instanceType, value, querySchema, source = Provided) - val evaluatedTypedInstance = kotlinx.coroutines.runBlocking { policyEvaluator.evaluate(valueAsTypedInstance, queryContext, executionScope) } - // Convert back to a raw object, since that's what we started with - evaluatedTypedInstance.toRawObject() - } + return stream + .flatMap { value -> + val valueAsTypedInstance = + TypedInstance.from(instanceType, value, querySchema, source = Provided) + + mono { + val evaluatedTypedInstance = + policyEvaluator.evaluate(valueAsTypedInstance, queryContext, executionScope) + // Convert back to a raw object, since that's what we started with + evaluatedTypedInstance.toRawObject() + } + } } fun applyPolicies(stream: Flux, streamName: String, principal: Authentication, schema: Schema): Flux { return applyPolicies(stream, streamName, principal.toVyneUser(), schema) diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt index 058698014..2ba9a119c 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/Vyne.kt @@ -314,9 +314,10 @@ class Vyne( // to us are available in the expressions we're evaluating. // eg: // given { name : String = 'foo' , upper = upperCase(name) } - var evaluatedExpressions = constants - for (parameter in taxiQl.facts.filter { it.value is FactValue.Expression }) { - val facts = CopyOnWriteFactBag(emptyList(), schema, evaluatedExpressions) + val evaluatedExpressions = taxiQl.facts + .filter { it.value is FactValue.Expression } + .fold(constants) { previousParams, parameter -> + val facts = CopyOnWriteFactBag(emptyList(), schema, previousParams) val valueSupplier = FactBagValueSupplier(facts, schema) val accessorReader = AccessorReader( valueSupplier, @@ -332,7 +333,7 @@ class Vyne( format = null, dataSource = Provided ) - evaluatedExpressions = evaluatedExpressions + ScopedFact(ProjectionFunctionScope(parameter.name, parameter.type), evaluationResult) + previousParams + ScopedFact(ProjectionFunctionScope(parameter.name, parameter.type), evaluationResult) } return evaluatedExpressions.map { it.scope.name to it.fact } .toMap() @@ -410,7 +411,7 @@ class Vyne( } @Deprecated("Looks like this is only called in tests. Does not propogate trace contexts. If this gets used, then it needs to accept a traceContext (or similar)") - fun evaluate(taxiExpression: String, returnType: Type): TypedInstance { + suspend fun evaluate(taxiExpression: String, returnType: Type): TypedInstance { val (schemaWithType, expressionType) = this.schema.compileExpression(taxiExpression, returnType) val queryContext = queryEngine(schema = schemaWithType) @@ -422,8 +423,7 @@ class Vyne( // to our predicate. // That's wrong, as generally the collection will be the input, especially if our predciate / expression // is a contains(...) - val buildResult = kotlinx.coroutines.runBlocking { - TypedObjectFactory( + val buildResult = TypedObjectFactory( expressionType, queryContext.facts, schemaWithType, @@ -431,7 +431,6 @@ class Vyne( inPlaceQueryEngine = queryContext, functionResultCache = queryContext.functionResultCache ).build() - } return buildResult } diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/DirectServiceInvocationStrategy.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/DirectServiceInvocationStrategy.kt index 3ab08e842..4f18de9ea 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/DirectServiceInvocationStrategy.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/DirectServiceInvocationStrategy.kt @@ -168,11 +168,11 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ parameters: Map, context: QueryContext ): Pair> { - val defaultValues = mutableMapOf() - for (param in operation.parameters) { - if (param.defaultValue != null && !parameters.containsKey(param)) { - defaultValues[param] = context.evaluate(param.defaultValue!!) - } + val defaultValues = operation.parameters + .filter { it.defaultValue != null } + .filter { !parameters.containsKey(it) } + .associateWith { parameter -> + context.evaluate(parameter.defaultValue!!) } return operation to (parameters + defaultValues) } @@ -205,11 +205,12 @@ class DirectServiceInvocationStrategy(invocationService: OperationInvocationServ // when evaluted against a contract of // find { Film[](PublicationDate >= 2020-10-02) // would provide a value of `date` - val satisfiedConstraints = mutableListOf>>>() - for (requiredConstraint in targetDataConstraints) { + val satisfiedConstraints = targetDataConstraints.mapNotNull { requiredConstraint -> val constraintComparison = remoteOperation.contract.satisfies(requiredConstraint) if (constraintComparison.satisfiesRequestedConstraint) { - satisfiedConstraints.add(requiredConstraint to getProvidedParameterValues(remoteOperation, constraintComparison.providedValues, context, schema)) + requiredConstraint to getProvidedParameterValues(remoteOperation, constraintComparison.providedValues, context, schema) + } else { + null } } diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryOperationInvocationStrategy.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryOperationInvocationStrategy.kt index 828db8234..5b9244a60 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryOperationInvocationStrategy.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/QueryOperationInvocationStrategy.kt @@ -55,11 +55,9 @@ class QueryOperationInvocationStrategy( context: QueryContext, target: Set ): Map>> { - val result = mutableMapOf>>() - for (querySpecTypeNode in target) { - result[querySpecTypeNode] = lookForCandidateQueryOperations(context.schema, querySpecTypeNode, context) + return target.associateWith { querySpecTypeNode -> + lookForCandidateQueryOperations(context.schema, querySpecTypeNode, context) } - return result } @VisibleForTesting diff --git a/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilder.kt b/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilder.kt index 66c8ad4f6..1154c7512 100644 --- a/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilder.kt +++ b/taxiql-query-engine/src/main/java/com/orbitalhq/query/queryBuilders/TaxiQlGrammarQueryBuilder.kt @@ -69,10 +69,7 @@ class TaxiQlGrammarQueryBuilder : QueryGrammarQueryBuilder { // In converting the expressions to Taxi, we also resolve any placeholder variables // using the context - val statementsAndValues = mutableListOf>>() - for (constraint in constraints) { - statementsAndValues.add(buildConstraint(constraint, context)) - } + val statementsAndValues = constraints.map { buildConstraint(it, context) } val constraintsStatement = statementsAndValues.joinToString("\n", prefix = "(\n", postfix = "\n)") { it.first } val resolvedValues = statementsAndValues.flatMap { it.second } return """find { ${spec.type.name.parameterizedName}${constraintsStatement} }""" to resolvedValues @@ -151,9 +148,8 @@ suspend fun Expression.resolveVariablesUsing(context: QueryContext): Pair { - val resolved = mutableListOf>>() - for (member in this.members) { - resolved.add(member.resolveVariablesUsing(context)) + val resolved = this.members.map { + it.resolveVariablesUsing(context) } val resolvedList = resolved.flatMap { it.second } diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt index 825b54523..b1bdd4148 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/TypedObjectFactory.kt @@ -19,6 +19,7 @@ import com.orbitalhq.schemas.* import com.orbitalhq.schemas.taxi.toVyneQualifiedName import com.orbitalhq.utils.timeBucket import com.orbitalhq.utils.xtimed +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope @@ -89,23 +90,9 @@ class TypedObjectFactory( private companion object { private val logger = KotlinLogging.logger {} - /* - * THREADING MODEL - * - * All methods that perform async operations (querying the InPlaceQueryEngine, invoking - * operations, etc.) are suspend functions. This means no thread is ever blocked waiting - * for a coroutine — callers suspend and yield their worker back to the pool. - * - * Cross-field dependencies (e.g. `total = quantity * price`) are handled via a simple - * HashMap cache: when getOrBuild() is called for a field, we check the cache first. If - * the field hasn't been computed yet, we compute it (calling suspend buildField()), cache - * the result, and return it. Since a single factory instance is only ever accessed from - * one coroutine at a time, no synchronization is needed. - * - * Callers in suspend contexts (e.g. ObjectBuilder.buildAsync) call build() directly. - * Callers in non-suspend contexts (e.g. TypedObject.fromValue) use runBlocking at the - * call site — but crucially, no runBlocking exists inside this class or its call chain. - */ + val parallelism = Runtime.getRuntime().availableProcessors() + val limitedParallelismDispatcher = Dispatchers.Default.limitedParallelism(parallelism) + } @@ -237,9 +224,10 @@ class TypedObjectFactory( } val projectedFieldValue = if (valueToProject is TypedCollection && targetType.isCollection) { // Project each member of the collection using coroutines instead of parallelStream + coroutineScope { valueToProject.map { collectionMember -> - async { + async(limitedParallelismDispatcher) { newFactory( targetType.collectionType!!, collectionMember, diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/LogicalExpressionEvaluator.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/LogicalExpressionEvaluator.kt deleted file mode 100644 index 4a15c0eb0..000000000 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/conditional/LogicalExpressionEvaluator.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.orbitalhq.models.conditional - -import arrow.core.Either -import arrow.core.left -import arrow.core.right -import com.orbitalhq.models.EvaluationValueSupplier -import com.orbitalhq.schemas.Type -import lang.taxi.types.AndExpression -import lang.taxi.types.ComparisonExpression -import lang.taxi.types.ComparisonOperator -import lang.taxi.types.ConstantEntity -import lang.taxi.types.ElseMatchExpression -import lang.taxi.types.FieldReferenceEntity -import lang.taxi.types.LogicalExpression -import lang.taxi.types.OrExpression -import lang.taxi.types.WhenCaseBlock -import java.math.BigDecimal -import java.util.* - -object LogicalExpressionEvaluator { - suspend fun evaluate(cases: List, factory: EvaluationValueSupplier, type: Type): WhenCaseBlock? { - for (case in cases) { - val matches = when (case.matchExpression) { - is LogicalExpression -> { - val expressionStack = Stack>() - pushExpression(case.matchExpression as LogicalExpression, expressionStack) - evaluateExpressionStack(expressionStack, type, factory) - } - is ElseMatchExpression -> true - else -> false - } - if (matches) return case - } - return null - } - - private fun pushExpression(logicalExpression: LogicalExpression, expressionStack: Stack>) { - when (logicalExpression) { - is ComparisonExpression -> expressionStack.push(logicalExpression.left()) - is AndExpression -> { - pushExpression(logicalExpression.right, expressionStack) - expressionStack.push(LogicalOp.And.right()) - pushExpression(logicalExpression.left, expressionStack) - } - is OrExpression -> { - pushExpression(logicalExpression.right, expressionStack) - expressionStack.push(LogicalOp.Or.right()) - pushExpression(logicalExpression.left, expressionStack) - } - } - } - - private suspend fun evaluateExpressionStack(expressionStack: Stack>, type: Type, factory: EvaluationValueSupplier): Boolean { - var result = false - var lastLogicalOp: LogicalOp? = null - while (!expressionStack.empty()) { - when (val item = expressionStack.pop()) { - is Either.Left -> result = when (lastLogicalOp) { - LogicalOp.And -> result && evaluateComparisonExpression(item.value as ComparisonExpression, factory) - LogicalOp.Or -> result || evaluateComparisonExpression(item.value as ComparisonExpression, factory) - else -> evaluateComparisonExpression(item.value as ComparisonExpression, factory) - } - - is Either.Right -> lastLogicalOp = item.value - } - } - return result - } - - - private suspend fun evaluateComparisonExpression(logicalExpression: ComparisonExpression, factory: EvaluationValueSupplier): Boolean { - val right = logicalExpression.right - val left = logicalExpression.left - val (leftValue, rightValue) = when { - right is FieldReferenceEntity && left is FieldReferenceEntity -> { - (factory.getValue(left.fieldName).value to factory.getValue(right.fieldName).value) - } - right is ConstantEntity && left is FieldReferenceEntity -> { - factory.getValue(left.fieldName).value to right.value - } - - right is FieldReferenceEntity && left is ConstantEntity -> { - left.value to factory.getValue(right.fieldName).value - } - else -> null to null - } - - return evaluateExpression(leftValue, rightValue, logicalExpression.operator) - } - - private fun evaluateExpression(left: Any?, right: Any?, operator: ComparisonOperator): Boolean { - return when { - left == null && right == null && operator == ComparisonOperator.EQ -> true - left == null && right != null && operator == ComparisonOperator.NQ -> true - left != null && right == null && operator == ComparisonOperator.NQ -> true - left != null && right != null -> evaluateExpressionForPopulatedOperands(left, right, operator) - else -> false - } - } - - private fun evaluateExpressionForPopulatedOperands(left: Any, right: Any, operator: ComparisonOperator): Boolean { - return when { - left is Int && right is Int -> evaluateComparables(left, right, operator) - left is BigDecimal && right is BigDecimal -> evaluateComparables(left, right, operator) - left is String && right is String -> evaluateComparables(left, right, operator) - else -> false - } - } - - private fun > evaluateComparables(left: T, right: T, operator: ComparisonOperator): Boolean { - return when (operator) { - ComparisonOperator.LT -> left < right - ComparisonOperator.NQ -> left != right - ComparisonOperator.EQ -> left == right - ComparisonOperator.GE -> left >= right - ComparisonOperator.GT -> left > right - ComparisonOperator.LE -> left <= right - } - } -} - -enum class LogicalOp { - And, - Or -} diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Functional.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Functional.kt index ca007dcd0..1dfa96263 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Functional.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/Functional.kt @@ -54,20 +54,21 @@ object Fold : NamedFunctionInvoker { function.asTaxi(), inputValues ) - var accumulator: TypedValue = initialValue - for (typedInstance in sourceCollection) { + val foldedValue = sourceCollection.fold(initialValue) { acc, typedInstance -> val factBagValueSupplier = FactBagValueSupplier.of( - listOf(accumulator, typedInstance), + listOf(acc, typedInstance), schema, objectFactory, + // Exact match so that the accumulated value (which is likely an INT) doesn't conflict with semantic subtypes. + // We should be smarter about this. TypeMatchingStrategy.EXACT_MATCH ) val reader = AccessorReader(factBagValueSupplier, schema.functionRegistry, schema) val evaluated = reader.evaluate(typedInstance, expressionReturnType, expression, dataSource = dataSource, format = null) - accumulator = evaluated as TypedValue + evaluated as TypedValue } - return accumulator + return foldedValue } } @@ -101,16 +102,31 @@ object MapFunction : NullSafeInvoker() { error("Cannot evaluate expression ${function.asTaxi()} as the function declares multiple inputs. This should've been detected by the compiler") } val mapFunctionInput = lambdaExpression.inputs[0] - val result = mutableListOf() - for (typedInstance in sourceCollection) { + val result = sourceCollection.map { typedInstance -> + // Before we can evaluate the actual map, we need to resolve the instance in the collection against + // the arguments to the lambda. + // eg: + // Foo[].map( (Foo) -> ...... // the 2nd (Foo) there + // If they're the same, as in that example, then it's fine. + // But we also need to consider: + // Foo[].map ( (f:Foo) -> // named args val scopedFact = if (typedInstance.type.taxiType.isAssignableTo(mapFunctionInput.type)) { ScopedFact(mapFunctionInput, typedInstance) } else { + // It's not assignable. + // This is an error, as the function is defined as; + // declare extension function map(collection: T[], callback: (T) -> A):A[] + // Therefore, this MUST be a T. + // However, if the compiler didn't enforce it, we have a problem + // ORB-1004 logger.error { "Map function expected iterable values of ${mapFunctionInput.type.toVyneQualifiedName().shortDisplayName}, but found an incompatible value of ${typedInstance.type.qualifiedName.shortDisplayName}. This should've been detected by the compiler. Mapping may not behave as expected" } ScopedFact(mapFunctionInput, typedInstance) } + // The type of expression determines how we should behave + // (which is annoying)... val evaluated = if (lambdaExpression.expression is TypeExpression) { + // If the expression is in the form of T1[].map((T1) -> T2), then we should build T2 from T1 thisScopeValueSupplier.newFactory( schema.type(lambdaExpression.expression.returnType), typedInstance, factsToExclude = setOf(sourceCollection), @@ -118,11 +134,13 @@ object MapFunction : NullSafeInvoker() { ) .build() } else { + // If the expression is in the form of T1[].map((T1) -> T1.someOtherExpression()), then we should evaluate the + // expression against the scope of T1 val factBag = FactBag.of(emptyList(), schema).withAdditionalScopedFacts(listOf(scopedFact), schema) thisScopeValueSupplier.newFactoryWithOnly(expressionReturnType, factBag) .evaluateExpression(lambdaExpression) } - result.add(evaluated) + evaluated } return if (result.isEmpty()) { TypedCollection.empty(returnType) @@ -152,11 +170,11 @@ object Reduce : NamedFunctionInvoker { function.asTaxi(), inputValues ) - var acc: TypedInstance = sourceCollection.first() - for (i in 1 until sourceCollection.size) { - val typedInstance = sourceCollection.toList()[i] + sourceCollection.reduce { acc, typedInstance -> val reader = AccessorReader.forFacts(listOf(acc, typedInstance), schema) - acc = reader.evaluate(typedInstance, expressionReturnType, expression, dataSource = dataSource, format = null) + val evaluated = + reader.evaluate(typedInstance, expressionReturnType, expression, dataSource = dataSource, format = null) + evaluated } sourceCollection.forEach { instance -> // val reader = AccessorReader(SimpleValueStore(listOf(instance), schema), schema.functionRegistry, schema) diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionFilteringFunction.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionFilteringFunction.kt index c9eb476d3..68b4f1fc5 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionFilteringFunction.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionFilteringFunction.kt @@ -65,13 +65,10 @@ abstract class CollectionFilteringFunction : NullSafeInvoker() { objectFactory: EvaluationValueSupplier, rawMessageBeingParsed: Any? ): Either> { - return when (val extracted = extractAndValidateInputs(inputValues, schema, returnType, function)) { - is Either.Left -> extracted - is Either.Right -> { - val (collection, deferredExpression, dataSource) = extracted.value - val filtered = mutableListOf() - for (collectionMember in collection) { - val filterResult = evaluatePredicateAgainstMember( + return extractAndValidateInputs(inputValues, schema, returnType, function) + .map { (collection, deferredExpression, dataSource) -> + val filtered = collection.filter { collectionMember -> + val filtered = evaluatePredicateAgainstMember( collectionMember, schema, objectFactory, @@ -81,14 +78,19 @@ abstract class CollectionFilteringFunction : NullSafeInvoker() { function, inputValues ) - when (filterResult) { - is Either.Left -> return filterResult.value.left() - is Either.Right -> if (filterResult.value) filtered.add(collectionMember) + when (filtered) { + // If the evaluation returned a typedNull, it indicates it failed, so + // bail out of the evaluation, returning at the top level + is Either.Left -> return filtered.value.left() + // Otherwise, return the filter result + is Either.Right -> return@filter filtered.value } + } - filtered.right() + filtered } - } + + } protected suspend fun evaluatePredicateAgainstMember( diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionPredicateFunctions.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionPredicateFunctions.kt index 34e3f1d8f..6c8ecea5d 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionPredicateFunctions.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/CollectionPredicateFunctions.kt @@ -40,11 +40,9 @@ abstract class BaseCollectionPredicateInvoker(val operationType: CollectionOpera returnTypeFormat: FormatsAndZoneOffset?, resultCache: MutableMap ): TypedInstance { - return when (val extracted = extractAndValidateInputs(inputValues, schema, returnType, function)) { - is Either.Left -> extracted.value - is Either.Right -> { - val (collection, deferredExpression, dataSource) = extracted.value - for (instance in collection) { + return extractAndValidateInputs(inputValues, schema, returnType, function) + .map { (collection, deferredExpression, dataSource) -> + collection.map { instance -> val eval = evaluatePredicateAgainstMember( instance, schema, @@ -56,6 +54,8 @@ abstract class BaseCollectionPredicateInvoker(val operationType: CollectionOpera inputValues ) when (eval) { + // If the evaluation returned a typedNull, it indicates it failed, so + // bail out of the evaluation, returning at the top level is Either.Left -> return eval.value is Either.Right -> { if (eval.value == operationType.terminateWhenEvaluatesAs) { @@ -65,13 +65,14 @@ abstract class BaseCollectionPredicateInvoker(val operationType: CollectionOpera schema, source = dataSource ) + } else { + eval.value } } } } TypedInstance.from(returnType, operationType.valueIfAllEvaluated, schema, source = dataSource) - } - } + }.getOrHandle { it } } } diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/SingleBy.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/SingleBy.kt index 5fa230cd4..7e401e906 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/SingleBy.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/collections/SingleBy.kt @@ -43,15 +43,15 @@ object SingleBy : NullSafeInvoker() { resultCacheKey ) { val stopwatch = Stopwatch.createStarted() - val grouped = mutableMapOf>() - for (collectionMember in collection) { + val grouped = collection.groupBy { collectionMember -> val factBag = FactBagValueSupplier.of(listOf(collectionMember), schema, thisScopeValueSupplier = thisScopeValueSupplier) +// val reader = AccessorReader(factBag, schema.functionRegistry, schema, functionResultCache = resultCache) val evaluated = deferredInstance.evaluate(collectionMember, dataSource, factBag, functionResultCache = resultCache) if (evaluated is TypedNull) { deferredInstance.evaluate(collectionMember, dataSource, factBag, functionResultCache = resultCache) } - grouped.getOrPut(evaluated) { mutableListOf() }.add(collectionMember) + evaluated } logger.debug { "singleBy grouping function took ${stopwatch.elapsed().toMillis()}ms" } grouped diff --git a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/Convert.kt b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/Convert.kt index 33f579be9..0ac570e2b 100644 --- a/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/Convert.kt +++ b/vyne-core-types/src/main/java/com/orbitalhq/models/functions/stdlib/transform/Convert.kt @@ -48,20 +48,19 @@ object Convert : NullSafeInvoker() { val dataSource = EvaluatedExpression(function.asTaxi(), inputValues) val converted = resultCache.getOrPut(resultCacheKey) { - kotlinx.coroutines.runBlocking { - if (targetType.isCollection && source is Collection<*>) { - val typedInstances = source.map { member -> - TypedObjectFactory(targetType.collectionType!!, FactBag.of(member as TypedInstance, schema), schema, source = dataSource, functionRegistry = schema.functionRegistry) - .build() - .convertToRawTypeIfRequired() - } - TypedCollection.arrayOf(targetType.collectionType!!, typedInstances, dataSource) - } else { - TypedObjectFactory(targetType, FactBag.of(source, schema), schema, source = dataSource, functionRegistry = schema.functionRegistry) + if (targetType.isCollection && source is Collection<*>) { + val typedInstances = source.map { member -> + TypedObjectFactory(targetType.collectionType!!, FactBag.of(member as TypedInstance, schema), schema, source = dataSource, functionRegistry = schema.functionRegistry) .build() .convertToRawTypeIfRequired() } + TypedCollection.arrayOf(targetType.collectionType!!, typedInstances, dataSource) + } else { + TypedObjectFactory(targetType, FactBag.of(source, schema), schema, source = dataSource, functionRegistry = schema.functionRegistry) + .build() + .convertToRawTypeIfRequired() } + } return converted as TypedInstance } diff --git a/vyne-query-service/pom.xml b/vyne-query-service/pom.xml index 2dd676704..2dfb0dcc3 100644 --- a/vyne-query-service/pom.xml +++ b/vyne-query-service/pom.xml @@ -330,11 +330,11 @@ 20231013 test - - com.orbitalhq - test-cli - ${project.version} - + + + + + org.jetbrains.kotlinx