Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: View updates #112

Merged
merged 3 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
368 changes: 0 additions & 368 deletions akka-javasdk-tests/src/test/java/akkajavasdk/SdkIntegrationTest.java

Large diffs are not rendered by default.

455 changes: 455 additions & 0 deletions akka-javasdk-tests/src/test/java/akkajavasdk/ViewIntegrationTest.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2021-2024 Lightbend Inc. <https://www.lightbend.com>
*/

package akkajavasdk.components.views;

import akka.javasdk.annotations.ComponentId;
import akka.javasdk.keyvalueentity.KeyValueEntity;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

@ComponentId("all-the-types-kve")
public class AllTheTypesKvEntity extends KeyValueEntity<AllTheTypesKvEntity.AllTheTypes> {

public enum AnEnum {
ONE, TWO, THREE
}

// common query parameter for views in this file
public record ByEmail(String email) {
}

public record Recursive(Recursive recurse) {}

public record AllTheTypes(
int intValue,
long longValue,
float floatValue,
double doubleValue,
boolean booleanValue,
String stringValue,
Integer wrappedInt,
Long wrappedLong,
Float wrappedFloat,
Double wrappedDouble,
Boolean wrappedBoolean,
Instant instant,
// FIXME bytes does not work yet in runtime Byte[] bytes,
Optional<String> optionalString,
List<String> repeatedString,
ByEmail nestedMessage,
AnEnum anEnum,
Recursive recursive
) {}



public Effect<String> store(AllTheTypes value) {
return effects().updateState(value).thenReply("OK");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2021-2024 Lightbend Inc. <https://www.lightbend.com>
*/

package akkajavasdk.components.views;

import akka.javasdk.annotations.ComponentId;
import akka.javasdk.annotations.Consume;
import akka.javasdk.annotations.Query;
import akka.javasdk.view.TableUpdater;
import akka.javasdk.view.View;

@ComponentId("all_the_field_types_view")
public class AllTheTypesView extends View {


@Consume.FromKeyValueEntity(AllTheTypesKvEntity.class)
public static class Events extends TableUpdater<AllTheTypesKvEntity.AllTheTypes> { }

@Query("SELECT * FROM events")
public QueryStreamEffect<AllTheTypesKvEntity.AllTheTypes> allRows() {
return queryStreamResult();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ akka.javasdk {
"akkajavasdk.components.keyvalueentities.user.UserEntity",
"akkajavasdk.components.workflowentities.WalletEntity",
"akkajavasdk.components.keyvalueentities.user.AssignedCounterEntity",
"akkajavasdk.components.keyvalueentities.hierarchy.TextKvEntity"
"akkajavasdk.components.keyvalueentities.hierarchy.TextKvEntity",
"akkajavasdk.components.views.AllTheTypesKvEntity"
]
view = [
"akkajavasdk.components.views.user.UsersByEmailAndName",
Expand All @@ -44,7 +45,8 @@ akka.javasdk {
"akkajavasdk.components.views.counter.CountersByValueSubscriptions",
"akkajavasdk.components.pubsub.ViewFromCounterEventsTopic",
"akkajavasdk.components.views.user.UsersByPrimitives",
"akkajavasdk.components.views.hierarchy.HierarchyCountersByValue"
"akkajavasdk.components.views.hierarchy.HierarchyCountersByValue",
"akkajavasdk.components.views.AllTheTypesView"
]
workflow = [
"akkajavasdk.components.workflowentities.TransferWorkflow",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ private[impl] object ViewDescriptorFactory {
tableUpdaterClass.getAnnotation(classOf[Table]).value()
} else {
// figure out from first query
val query = allQueryStrings.head
val query = allQueryStrings.headOption.getOrElse(
throw new IllegalArgumentException(
s"View [$componentId] does not have any queries defined, must have at least one query"))
TableNamePattern
.findFirstMatchIn(query)
.map(_.group(1))
Expand Down
77 changes: 43 additions & 34 deletions akka-javasdk/src/main/scala/akka/javasdk/impl/view/ViewSchema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,40 +48,49 @@ private[view] object ViewSchema {
classOf[String] -> SpiString,
classOf[java.time.Instant] -> SpiTimestamp)

def apply(javaType: Type): SpiType =
typeNameMap.get(javaType.getTypeName) match {
case Some(found) => found
case None =>
val clazz = javaType match {
case c: Class[_] => c
case p: ParameterizedType => p.getRawType.asInstanceOf[Class[_]]
}
knownConcreteClasses.get(clazz) match {
case Some(found) => found
case None =>
// trickier ones where we have to look at type parameters etc
if (clazz.isArray && clazz.componentType() == classOf[java.lang.Byte]) {
SpiByteString
} else if (clazz.isEnum) {
new SpiType.SpiEnum(clazz.getName)
} else {
javaType match {
case p: ParameterizedType if clazz == classOf[Optional[_]] =>
new SpiType.SpiOptional(apply(p.getActualTypeArguments.head).asInstanceOf[SpiNestableType])
case p: ParameterizedType if classOf[java.util.Collection[_]].isAssignableFrom(clazz) =>
new SpiType.SpiList(apply(p.getActualTypeArguments.head).asInstanceOf[SpiNestableType])
case _: Class[_] =>
new SpiType.SpiClass(
clazz.getName,
clazz.getDeclaredFields
.filterNot(f => f.accessFlags().contains(AccessFlag.STATIC))
// FIXME recursive classes with fields of their own type
.filterNot(_.getType == clazz)
.map(field => new SpiType.SpiField(field.getName, apply(field.getGenericType)))
.toSeq)
}
def apply(rootType: Type): SpiType = {
// Note: not tail recursive but trees should not ever be deep enough that it is a problem
def loop(currentType: Type, seenClasses: Set[Class[_]]): SpiType =
typeNameMap.get(currentType.getTypeName) match {
case Some(found) => found
case None =>
val clazz = currentType match {
case c: Class[_] => c
case p: ParameterizedType => p.getRawType.asInstanceOf[Class[_]]
}
if (seenClasses.contains(clazz)) new SpiType.SpiClassRef(clazz.getName)
else
knownConcreteClasses.get(clazz) match {
case Some(found) => found
case None =>
// trickier ones where we have to look at type parameters etc
if (clazz.isArray && clazz.componentType() == classOf[java.lang.Byte]) {
SpiByteString
} else if (clazz.isEnum) {
new SpiType.SpiEnum(clazz.getName)
} else {
currentType match {
case p: ParameterizedType if clazz == classOf[Optional[_]] =>
new SpiType.SpiOptional(
loop(p.getActualTypeArguments.head, seenClasses).asInstanceOf[SpiNestableType])
case p: ParameterizedType if classOf[java.util.Collection[_]].isAssignableFrom(clazz) =>
new SpiType.SpiList(
loop(p.getActualTypeArguments.head, seenClasses).asInstanceOf[SpiNestableType])
case _: Class[_] =>
val seenIncludingThis = seenClasses + clazz
new SpiType.SpiClass(
clazz.getName,
clazz.getDeclaredFields
.filterNot(f => f.accessFlags().contains(AccessFlag.STATIC))
.map(field =>
new SpiType.SpiField(field.getName, loop(field.getGenericType, seenIncludingThis)))
.toSeq)
}
}
}
}
}
}

loop(rootType, Set.empty)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import akka.javasdk.testmodels.keyvalueentity.TimeTrackerEntity;
import akka.javasdk.testmodels.keyvalueentity.User;
import akka.javasdk.testmodels.keyvalueentity.UserEntity;
import akka.util.ByteString;

import java.time.Instant;
import java.util.List;
Expand All @@ -47,13 +46,21 @@ public record EveryType(
Byte[] bytes,
Optional<String> optionalString,
List<String> repeatedString,
ByEmail nestedMessage
ByEmail nestedMessage,
AnEnum anEnum
) {}

public enum AnEnum {
ONE, TWO, THREE
}

// common query parameter for views in this file
public record ByEmail(String email) {
}

public record Recursive(String id, Recursive child) {}
public record TwoStepRecursive(TwoStepRecursiveChild child) {}
public record TwoStepRecursiveChild(TwoStepRecursive recursive) {}

@ComponentId("users_view")
public static class UserByEmailWithGet extends View {
Expand Down Expand Up @@ -720,4 +727,29 @@ public QueryEffect<Employee> getEmployeeByEmail(ByEmail byEmail) {
return queryResult();
}
}


public record ById(String id) {}

@ComponentId("recursive_view")
public static class RecursiveViewStateView extends View {
@Consume.FromTopic(value = "recursivetopic")
public static class Events extends TableUpdater<Recursive> { }

@Query("SELECT * FROM events WHERE id = :id")
public QueryEffect<Employee> getEmployeeByEmail(ById id) {
return queryResult();
}
}

@ComponentId("all_the_field_types_view")
public static class AllTheFieldTypesView extends View {
@Consume.FromTopic(value = "allthetypestopic")
public static class Events extends TableUpdater<EveryType> { }

@Query("SELECT * FROM rows")
public QueryStreamEffect<Employee> allRows() {
return queryStreamResult();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -483,5 +483,18 @@ class ViewDescriptorFactorySpec extends AnyWordSpec with Matchers {
table.updateHandler shouldBe defined
}
}

"create a descriptor for a view with a recursive table type" in {
assertDescriptor[RecursiveViewStateView] { desc =>
// just check that it parses
}
}

"create a descriptor for a view with a table type with all possible column types" in {
assertDescriptor[AllTheFieldTypesView] { desc =>
// just check that it parses
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import akka.javasdk.testmodels.view.ViewTestModels
import akka.runtime.sdk.spi.views.SpiType.SpiBoolean
import akka.runtime.sdk.spi.views.SpiType.SpiByteString
import akka.runtime.sdk.spi.views.SpiType.SpiClass
import akka.runtime.sdk.spi.views.SpiType.SpiClassRef
import akka.runtime.sdk.spi.views.SpiType.SpiDouble
import akka.runtime.sdk.spi.views.SpiType.SpiEnum
import akka.runtime.sdk.spi.views.SpiType.SpiField
import akka.runtime.sdk.spi.views.SpiType.SpiFloat
import akka.runtime.sdk.spi.views.SpiType.SpiInteger
Expand Down Expand Up @@ -47,8 +49,9 @@ class ViewSchemaSpec extends AnyWordSpec with Matchers {
"optionalString" -> new SpiOptional(SpiString),
"repeatedString" -> new SpiList(SpiString),
"nestedMessage" -> new SpiClass(
"akka.javasdk.testmodels.view.ViewTestModels$ByEmail",
Seq(new SpiField("email", SpiString))))
classOf[ViewTestModels.ByEmail].getName,
Seq(new SpiField("email", SpiString))),
"anEnum" -> new SpiEnum(classOf[ViewTestModels.AnEnum].getName))
clazz.fields should have size expectedFields.size

expectedFields.foreach { case (name, expectedType) =>
Expand All @@ -59,7 +62,26 @@ class ViewSchemaSpec extends AnyWordSpec with Matchers {
}
}

// FIXME self-referencing/recursive types
"handle self referencing type trees" in {
val result = ViewSchema(classOf[ViewTestModels.Recursive])
result shouldBe a[SpiClass]
result.asInstanceOf[SpiClass].getField("child").get.fieldType shouldBe new SpiClassRef(
classOf[ViewTestModels.Recursive].getName)
}

"handle self referencing type trees with longer cycles" in {
val result = ViewSchema(classOf[ViewTestModels.TwoStepRecursive])
result shouldBe a[SpiClass]
result
.asInstanceOf[SpiClass]
.getField("child")
.get
.fieldType
.asInstanceOf[SpiClass]
.getField("recursive")
.get
.fieldType shouldBe new SpiClassRef(classOf[ViewTestModels.TwoStepRecursive].getName)
}
}

}
Loading