diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/TimelineRemoteTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/TimelineRemoteTests.java deleted file mode 100644 index 0cdef7f02e..0000000000 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/TimelineRemoteTests.java +++ /dev/null @@ -1,149 +0,0 @@ -package gov.nasa.jpl.aerie.e2e; - -import com.microsoft.playwright.Playwright; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan; -import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults; -import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests; -import gov.nasa.jpl.aerie.e2e.utils.HasuraRequests; -import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; -import gov.nasa.ammos.aerie.procedural.timeline.Interval; -import gov.nasa.ammos.aerie.procedural.timeline.collections.profiles.Real; -import gov.nasa.ammos.aerie.procedural.timeline.payloads.LinearEquation; -import gov.nasa.ammos.aerie.procedural.timeline.payloads.Segment; -import gov.nasa.ammos.aerie.procedural.remote.AeriePostgresPlan; -import gov.nasa.ammos.aerie.procedural.remote.AeriePostgresSimulationResults; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import javax.json.Json; -import java.io.IOException; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertIterableEquals; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TimelineRemoteTests { - // Requests - private Playwright playwright; - private HasuraRequests hasura; - - // Per-Test Data - private int modelId; - private int planId; - private int activityId; - - private Plan plan; - private SimulationResults simResults; - private Connection connection; - private HikariDataSource dataSource; - @BeforeAll - void beforeAll() throws SQLException { - // Setup Requests - playwright = Playwright.create(); - hasura = new HasuraRequests(playwright); - - // Connect to the database - final var hikariConfig = new HikariConfig(); - - hikariConfig.setDataSourceClassName("org.postgresql.ds.PGSimpleDataSource"); - - hikariConfig.addDataSourceProperty("serverName", "localhost"); - hikariConfig.addDataSourceProperty("portNumber", 5432); - hikariConfig.addDataSourceProperty("databaseName", "aerie"); - hikariConfig.addDataSourceProperty("applicationName", "Merlin Server"); - hikariConfig.setUsername(System.getenv("MERLIN_USERNAME")); - hikariConfig.setPassword(System.getenv("MERLIN_PASSWORD")); - - hikariConfig.setConnectionInitSql("set time zone 'UTC'"); - dataSource = new HikariDataSource(hikariConfig); - connection = dataSource.getConnection(); - } - - @AfterAll - void afterAll() throws SQLException { - // Cleanup Requests - hasura.close(); - playwright.close(); - connection.close(); - dataSource.close(); - } - - @BeforeEach - void beforeEach() throws IOException, InterruptedException { - // Insert the Mission Model - try (final var gateway = new GatewayRequests(playwright)) { - modelId = hasura.createMissionModel( - gateway.uploadJarFile(), - "Banananation (e2e tests)", - "aerie_e2e_tests", - "Timeline Remote Tests"); - } - // Insert the Plan - planId = hasura.createPlan( - modelId, - "Test Plan - Timeline Remote Tests", - "1212h", - "2021-01-01T00:00:00Z"); - //Insert the Activity - activityId = hasura.insertActivityDirective( - planId, - "BiteBanana", - "1h", - Json.createObjectBuilder().add("biteSize", 1).build()); - int simDatasetId = hasura.awaitSimulation(planId).simDatasetId(); - - // Connect to the database - - plan = new AeriePostgresPlan(connection, planId); - simResults = new AeriePostgresSimulationResults(connection, simDatasetId, plan, false); - } - - @AfterEach - void afterEach() throws IOException { - hasura.deletePlan(planId); - hasura.deleteMissionModel(modelId); - } - - @Test - void queryActivityInstances() { - final var instances = simResults.instances().collect(); - assertEquals(1, instances.size()); - final var instance = instances.get(0); - assertEquals("BiteBanana", instance.getType()); - assert instance.directiveId != null; - assertEquals(activityId, instance.directiveId.id()); - assertEquals(1, instance.inner.arguments.get("biteSize").asInt().get()); - assertEquals(Duration.ZERO, instance.getInterval().duration()); - } - - @Test - void queryActivityDirectives() { - final var directives = plan.directives().collect(); - assertEquals(1, directives.size()); - final var directive = directives.get(0); - assertEquals("BiteBanana", directive.getType()); - assertEquals(activityId, directive.id.id()); - assertEquals(1, directive.inner.arguments.get("biteSize").asInt().get()); - } - - @Test - void queryResources() { - final var fruit = simResults.resource("/fruit", Real.deserializer()).collect(); - assertIterableEquals( - List.of( - Segment.of(Interval.betweenClosedOpen(Duration.ZERO, Duration.HOUR), new LinearEquation(4.0)), - Segment.of(Interval.betweenClosedOpen(Duration.HOUR, Duration.HOUR.times(1212)), new LinearEquation(3.0)) - ), - fruit - ); - } -} diff --git a/procedural/remote/build.gradle b/procedural/remote/build.gradle index a4eecc4d3c..e8c38106e7 100644 --- a/procedural/remote/build.gradle +++ b/procedural/remote/build.gradle @@ -64,24 +64,24 @@ dokkaHtmlPartial.configure { } } -publishing { - publications { - library(MavenPublication) { - version = findProperty("publishing.version") - from components.java - } - } - - publishing { - repositories { - maven { - name = findProperty("publishing.name") - url = findProperty("publishing.url") - credentials { - username = System.getenv(findProperty("publishing.usernameEnvironmentVariable")) - password = System.getenv(findProperty("publishing.passwordEnvironmentVariable")) - } - } - } - } -} +//publishing { +// publications { +// library(MavenPublication) { +// version = findProperty("publishing.version") +// from components.java +// } +// } +// +// publishing { +// repositories { +// maven { +// name = findProperty("publishing.name") +// url = findProperty("publishing.url") +// credentials { +// username = System.getenv(findProperty("publishing.usernameEnvironmentVariable")) +// password = System.getenv(findProperty("publishing.passwordEnvironmentVariable")) +// } +// } +// } +// } +//} diff --git a/procedural/remote/src/main/kotlin/gov/nasa/ammos/aerie/procedural/remote/AeriePostgresPlan.kt b/procedural/remote/src/main/kotlin/gov/nasa/ammos/aerie/procedural/remote/AeriePostgresPlan.kt deleted file mode 100644 index 05f7701f0e..0000000000 --- a/procedural/remote/src/main/kotlin/gov/nasa/ammos/aerie/procedural/remote/AeriePostgresPlan.kt +++ /dev/null @@ -1,124 +0,0 @@ -package gov.nasa.ammos.aerie.procedural.remote - -import gov.nasa.jpl.aerie.merlin.driver.json.SerializedValueJsonParser.serializedValueP -import gov.nasa.jpl.aerie.merlin.protocol.types.Duration -import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue -import gov.nasa.ammos.aerie.procedural.timeline.BaseTimeline -import gov.nasa.ammos.aerie.procedural.timeline.BoundsTransformer -import gov.nasa.ammos.aerie.procedural.timeline.Interval.Companion.between -import gov.nasa.ammos.aerie.procedural.timeline.collections.Directives -import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.Directive -import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart -import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan -import gov.nasa.ammos.aerie.procedural.timeline.util.duration.plus -import gov.nasa.ammos.aerie.procedural.timeline.util.duration.minus -import gov.nasa.jpl.aerie.types.ActivityDirectiveId -import java.io.StringReader -import java.sql.Connection -import java.time.Instant -import javax.json.Json - -/** - * A connection to Aerie's database for a particular simulation result. - * - * @param c A connection to Aerie's database - * @param planId The ID of the plan as specified by in the postgres database - */ -data class AeriePostgresPlan( - private val c: Connection, - private val planId: Int -): Plan { - - private val planInfo by lazy { - val statement = c.prepareStatement("select start_time, duration from merlin.plan where id = ?;") - statement.setInt(1, planId) - intervalStyleStatement(c).execute() - val response = statement.executeQuery() - if (!response.next()) throw DatabaseError("Expected exactly one result for query, found none: $statement") - val result = object { - val startTime = response.getTimestamp(1).toInstant() - val duration = Duration.parseISO8601(response.getString(2)) - val id = this@AeriePostgresPlan.planId - } - if (response.next()) throw DatabaseError("Expected exactly one result for query, found more than one: $statement") - result - } - - override fun totalBounds() = between(Duration.ZERO, planInfo.duration) - - override fun toRelative(abs: Instant) = abs - planInfo.startTime - override fun toAbsolute(rel: Duration) = planInfo.startTime + rel - - - /***/ class DatabaseError(message: String): Error(message) - - private fun parseJson(jsonStr: String): SerializedValue = Json.createReader(StringReader(jsonStr)).use { reader -> - val requestJson = reader.readValue() - val result = serializedValueP.parse(requestJson) - return result.getSuccessOrThrow { DatabaseError(it.toString()) } - } - - private val allDirectivesStatement = c.prepareStatement( - "select name, start_offset, type, arguments, id, anchor_id, anchored_to_start from merlin.activity_directive where plan_id = ?" + - " and start_offset > ?::interval and start_offset < ?::interval;" - ) - override fun directives(type: String?, deserializer: (SerializedValue) -> A) = - Directives( - ( - if (type == null) allDirectives - else allDirectives.filter { it.type == type } - ) - ).unsafeMap(::Directives, BoundsTransformer.IDENTITY, false) { - it.mapInner(deserializer) - } - - private val allDirectives by lazy { - BaseTimeline(::Directives) { opts -> - allDirectivesStatement.clearParameters() - allDirectivesStatement.setInt(1, planInfo.id) - allDirectivesStatement.setString(2, opts.bounds.start.toISO8601()) - allDirectivesStatement.setString(3, opts.bounds.end.toISO8601()) - intervalStyleStatement(c).execute() - val response = allDirectivesStatement.executeQuery() - var unresolved = mutableListOf>() - while (response.next()) { - val anchorId = response.getLong(6) - val offset = Duration.parseISO8601(response.getString(2)) - val start = if (anchorId != 0L) { // this means SQL null. Terrible interface imo - val anchoredToStart = response.getBoolean(7) - DirectiveStart.Anchor(ActivityDirectiveId(anchorId), offset, if (anchoredToStart) DirectiveStart.Anchor.AnchorPoint.Start else DirectiveStart.Anchor.AnchorPoint.End, Duration.ZERO) - } else DirectiveStart.Absolute(offset) - unresolved.add(Directive( - parseJson(response.getString(4)), - response.getString(1), - ActivityDirectiveId(response.getLong(5)), - response.getString(3), - start - )) - } - val result = mutableListOf>() - while (unresolved.size != 0) { - val sizeAtStartOfStep = unresolved.size - unresolved = unresolved.filterNot { - when (val s = it.start) { - is DirectiveStart.Absolute -> { - result.add(it) - } - is DirectiveStart.Anchor -> { - val index = result.binarySearch { a -> a.id.id.compareTo(s.parentId.id) } - if (index >= 0) { - val parent = result[index] - s.estimatedStart = parent.startTime + s.offset - result.add(it) - } else false - } - } - }.toMutableList() - if (sizeAtStartOfStep == unresolved.size) throw Error("Cannot resolve anchors: $unresolved") - result.sortBy { it.id.id } - } - result - }.specialize() - .collect() - } -} diff --git a/procedural/remote/src/main/kotlin/gov/nasa/ammos/aerie/procedural/remote/AeriePostgresSimulationResults.kt b/procedural/remote/src/main/kotlin/gov/nasa/ammos/aerie/procedural/remote/AeriePostgresSimulationResults.kt deleted file mode 100644 index d5be02a5bf..0000000000 --- a/procedural/remote/src/main/kotlin/gov/nasa/ammos/aerie/procedural/remote/AeriePostgresSimulationResults.kt +++ /dev/null @@ -1,180 +0,0 @@ -package gov.nasa.ammos.aerie.procedural.remote - -import gov.nasa.jpl.aerie.merlin.driver.json.SerializedValueJsonParser.serializedValueP -import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue -import gov.nasa.jpl.aerie.merlin.protocol.types.Duration -import gov.nasa.ammos.aerie.procedural.timeline.Interval.Companion.between -import gov.nasa.ammos.aerie.procedural.timeline.Interval.Inclusivity.Exclusive -import gov.nasa.ammos.aerie.procedural.timeline.Interval.Inclusivity.Inclusive -import gov.nasa.ammos.aerie.procedural.timeline.collections.Instances -import gov.nasa.ammos.aerie.procedural.timeline.ops.coalesce.CoalesceSegmentsOp -import gov.nasa.ammos.aerie.procedural.timeline.payloads.Segment -import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.Instance -import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan -import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults -import gov.nasa.jpl.aerie.types.ActivityDirectiveId -import gov.nasa.jpl.aerie.types.ActivityInstanceId -import java.io.StringReader -import java.sql.Connection -import javax.json.Json -import kotlin.jvm.optionals.getOrNull - -/** - * A connection to Aerie's database for a particular simulation result. - * - * @param c A connection to Aerie's database - * @param simDatasetId The simulation dataset id to query for (this is different from the dataset id) - * @param plan a plan object representing the plan associated with this simulation dataset - * @param stale whether these results are not up-to-date - */ -data class AeriePostgresSimulationResults( - /** */ - private val c: Connection, - /** The particular simulation dataset to query. */ - private val simDatasetId: Int, - private val plan: Plan, - private val stale: Boolean -): SimulationResults { - - private val datasetId by lazy { - val statement = c.prepareStatement("select dataset_id from merlin.simulation_dataset where id = ?;") - statement.setInt(1, simDatasetId) - getSingleLongQueryResult(statement) - } - - private val simulationId by lazy { - val statement = c.prepareStatement("select simulation_id from merlin.simulation_dataset where id = ?;") - statement.setInt(1, simDatasetId) - getSingleLongQueryResult(statement) - } - - private val simulationInfo by lazy { - val statement = c.prepareStatement("select simulation_start_time, simulation_end_time from merlin.simulation where id = ?;") - statement.setLong(1, simulationId) - val response = statement.executeQuery() - if (!response.next()) throw DatabaseError("Expected exactly one result for query, found none: $statement") - val result = object { - val startTime = response.getTimestamp(1).toInstant() - val endTime = response.getTimestamp(2).toInstant() - } - if (response.next()) throw DatabaseError("Expected exactly one result for query, found more than one: $statement") - result - } - - override fun simBounds() = between( - plan.toRelative(simulationInfo.startTime), - plan.toRelative(simulationInfo.endTime), - ) - - private val profileInfoStatement = c.prepareStatement( - "select id, duration from merlin.profile where dataset_id = ? and name = ?;" - ) - private data class ProfileInfo(val id: Int, val duration: Duration) - - private val segmentsStatement = c.prepareStatement( - "select start_offset, dynamics, is_gap from merlin.profile_segment where profile_id = ? and dataset_id = ? order by start_offset asc;" - ) - - /***/ class DatabaseError(message: String): Error(message) - - override fun > resource(name: String, deserializer: (List>) -> TL): TL { - val profileInfo = getProfileInfo(name) - - segmentsStatement.clearParameters() - segmentsStatement.setInt(1, profileInfo.id) - segmentsStatement.setLong(2, datasetId) - intervalStyleStatement(c).execute() - val response = segmentsStatement.executeQuery() - - val result = mutableListOf>() - - var previousValue: SerializedValue? = null - var previousStart: Duration? = null - - while (response.next()) { - val thisStart = Duration.parseISO8601(response.getString(1)) - if (previousStart !== null) { - val interval = between(previousStart, thisStart, Inclusive, Exclusive) - val newSegment = Segment( - interval, - previousValue!! - ) - result.add(newSegment) - } - if (!response.getBoolean(3)) { // if not gap - val serializedValue = parseJson(response.getString(2)) - previousValue = serializedValue - previousStart = thisStart - } else { - previousValue = null - previousStart = null - } - } - if (previousStart !== null) { - val interval = between(previousStart, profileInfo.duration, Inclusive, Exclusive) - result.add( - Segment( - interval, - previousValue!! - ) - ) - } - return deserializer(result) - } - - private fun getProfileInfo(name: String): ProfileInfo { - profileInfoStatement.clearParameters() - profileInfoStatement.setLong(1, datasetId) - profileInfoStatement.setString(2, name) - intervalStyleStatement(c).execute() - val profileResult = profileInfoStatement.executeQuery() - if (!profileResult.next()) throw DatabaseError("Profile $name not found in database") - val id = profileResult.getInt(1) - val duration = Duration.parseISO8601(profileResult.getString(2)) - if (profileResult.next()) throw DatabaseError("Multiple profiles named $name found in one simulation dataset") - return ProfileInfo(id, duration) - } - - private fun parseJson(jsonStr: String): SerializedValue = Json.createReader(StringReader(jsonStr)).use { reader -> - val requestJson = reader.readValue() - val result = serializedValueP.parse(requestJson) - return result.getSuccessOrThrow { DatabaseError(it.toString()) } - } - - private val allInstancesStatement = c.prepareStatement( - "select start_offset, duration, attributes, activity_type_name, id, parent_id from merlin.simulated_activity" + - " where simulation_dataset_id = ?;" - ) - private val filteredInstancesStatement = c.prepareStatement( - "select start_offset, duration, attributes, activity_type_name, id, parent_id from merlin.simulated_activity" + - " where simulation_dataset_id = ? and activity_type_name = ?;" - ) - override fun instances(type: String?, deserializer: (SerializedValue) -> A): Instances { - val statement = if (type == null) allInstancesStatement else filteredInstancesStatement - statement.clearParameters() - statement.setInt(1, simDatasetId) - if (type != null) statement.setString(2, type); - intervalStyleStatement(c).execute() - val response = statement.executeQuery() - val result = mutableListOf>() - while (response.next()) { - val start = Duration.parseISO8601(response.getString(1)) - val id = response.getLong(5) - val parentId = response.getLong(6) - val attributesString = response.getString(3) - val attributes = parseJson(attributesString) - val directiveId = attributes.asMap().getOrNull()?.get("directiveId")?.asInt()?.getOrNull() - result.add(Instance( - deserializer(attributes), - response.getString(4), - ActivityInstanceId(id), - directiveId?.let { ActivityDirectiveId(it) }, - if (parentId == 0L) null else ActivityInstanceId(parentId), - between(start, start.plus(Duration.parseISO8601(response.getString(2)))) - )) - } - return Instances(result) - } - - override fun isStale() = stale -} diff --git a/procedural/remote/src/main/kotlin/gov/nasa/ammos/aerie/procedural/remote/SqlUtils.kt b/procedural/remote/src/main/kotlin/gov/nasa/ammos/aerie/procedural/remote/SqlUtils.kt deleted file mode 100644 index e941e90a1a..0000000000 --- a/procedural/remote/src/main/kotlin/gov/nasa/ammos/aerie/procedural/remote/SqlUtils.kt +++ /dev/null @@ -1,14 +0,0 @@ -package gov.nasa.ammos.aerie.procedural.remote - -import java.sql.Connection -import java.sql.PreparedStatement - -internal fun getSingleLongQueryResult(statement: PreparedStatement): Long { - val result = statement.executeQuery() - if (!result.next()) throw AeriePostgresSimulationResults.DatabaseError("Expected exactly one result for query, found none: $statement") - val int = result.getLong(1) - if (result.next()) throw AeriePostgresSimulationResults.DatabaseError("Expected exactly one result for query, found more than one: $statement") - return int -} - -internal fun intervalStyleStatement(c: Connection): PreparedStatement = c.prepareStatement("set intervalstyle = 'iso_8601';")