diff --git a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/PacketFinder.java b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/PacketFinder.java index 73ee63d6..b39f0445 100644 --- a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/PacketFinder.java +++ b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/PacketFinder.java @@ -33,7 +33,7 @@ public PacketFinder(SimpleQueryHandler simpleQueryHandler) { } } private void updateWithRange(long queryStart, long queryEnd) { - List rawPackets = simpleQueryHandler.queryStatus(queryStart, queryEnd, null); + List rawPackets = simpleQueryHandler.queryStatus(queryStart, queryEnd, null, null); synchronized (cacheMap) { for (InstancePacketGroup instancePacketGroup : rawPackets) { int fragmentId = instancePacketGroup.getFragmentId(); diff --git a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/SimpleQueryHandler.java b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/SimpleQueryHandler.java index e50f1c68..df7a72ae 100644 --- a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/SimpleQueryHandler.java +++ b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/SimpleQueryHandler.java @@ -5,6 +5,7 @@ import me.retrodaredevil.couchdbjava.CouchDbInstance; import me.retrodaredevil.solarthing.SolarThingConstants; import me.retrodaredevil.solarthing.annotations.NotNull; +import me.retrodaredevil.solarthing.annotations.Nullable; import me.retrodaredevil.solarthing.config.databases.implementations.CouchDbDatabaseSettings; import me.retrodaredevil.solarthing.database.MillisDatabase; import me.retrodaredevil.solarthing.database.MillisQuery; @@ -15,13 +16,17 @@ import me.retrodaredevil.solarthing.database.couchdb.CouchDbSolarThingDatabase; import me.retrodaredevil.solarthing.database.exception.NotFoundSolarThingDatabaseException; import me.retrodaredevil.solarthing.database.exception.SolarThingDatabaseException; +import me.retrodaredevil.solarthing.packets.collection.DefaultInstanceOptions; +import me.retrodaredevil.solarthing.packets.collection.FragmentedPacketGroup; +import me.retrodaredevil.solarthing.packets.collection.InstancePacketGroup; +import me.retrodaredevil.solarthing.packets.collection.PacketGroup; +import me.retrodaredevil.solarthing.packets.collection.PacketGroups; +import me.retrodaredevil.solarthing.packets.collection.parsing.PacketParsingErrorHandler; +import me.retrodaredevil.solarthing.rest.exceptions.DatabaseException; import me.retrodaredevil.solarthing.type.alter.StoredAlterPacket; import me.retrodaredevil.solarthing.type.closed.meta.DefaultMetaDatabase; import me.retrodaredevil.solarthing.type.closed.meta.EmptyMetaDatabase; import me.retrodaredevil.solarthing.type.closed.meta.MetaDatabase; -import me.retrodaredevil.solarthing.packets.collection.*; -import me.retrodaredevil.solarthing.packets.collection.parsing.PacketParsingErrorHandler; -import me.retrodaredevil.solarthing.rest.exceptions.DatabaseException; import me.retrodaredevil.solarthing.type.closed.meta.RootMetaPacket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +36,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -89,7 +93,7 @@ public List sortPackets(List queryPackets(MillisDatabase database, long from, long to, String sourceId) { + private List queryPackets(MillisDatabase database, long from, long to, @Nullable String sourceId, @Nullable Integer fragmentId) { MillisQuery millisQuery = new MillisQueryBuilder() .startKey(from) @@ -148,21 +152,21 @@ private List queryPackets(MillisDatabase database } return Collections.emptyList(); } - if (sourceId == null) { - return PacketGroups.parseToInstancePacketGroups(rawPacketGroups, defaultInstanceOptions); - } - Map> map = PacketGroups.parsePackets(rawPacketGroups, defaultInstanceOptions); - if(map.containsKey(sourceId)){ - List instancePacketGroupList = map.get(sourceId); - return PacketGroups.orderByFragment(instancePacketGroupList); - } - throw new NoSuchElementException("No element with sourceId: '" + sourceId + "' available keys are: " + map.keySet()); + // Note: Before 2024-04-05 this method would throw a NoSuchElementException if no packets were found under a given Source ID + // Additionally PacketGroups.orderByFragment() was used to order the result ONLY IF a Source ID was provided. + // This behavior of this method has changed since then, which may have unintended effects. + // I really don't know if the ordering by Fragment ID was necessary, and I also don't think we really need that exception to be thrown. + return rawPacketGroups.stream() + .map(packetGroup -> PacketGroups.parseToInstancePacketGroup(packetGroup, defaultInstanceOptions)) + .filter(instancePacketGroup -> sourceId == null || instancePacketGroup.getSourceId().equals(sourceId)) + .filter(instancePacketGroup -> fragmentId == null || instancePacketGroup.getFragmentId() == fragmentId) + .toList(); } - public List queryStatus(long from, long to, String sourceId) { - return queryPackets(database.getStatusDatabase(), from, to, sourceId); + public List queryStatus(long from, long to, @Nullable String sourceId, @Nullable Integer fragmentId) { + return queryPackets(database.getStatusDatabase(), from, to, sourceId, fragmentId); } - public List queryEvent(long from, long to, String sourceId) { - return queryPackets(database.getEventDatabase(), from, to, sourceId); + public List queryEvent(long from, long to, @Nullable String sourceId, @Nullable Integer fragmentId) { + return queryPackets(database.getEventDatabase(), from, to, sourceId, fragmentId); } public MetaDatabase queryMeta() { diff --git a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/packets/FragmentFilter.java b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/packets/FragmentFilter.java index bc48a350..3aa31f1b 100644 --- a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/packets/FragmentFilter.java +++ b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/packets/FragmentFilter.java @@ -2,6 +2,10 @@ import me.retrodaredevil.solarthing.rest.graphql.packets.nodes.PacketNode; +/** + * @deprecated Should not be needed anymore as {@link me.retrodaredevil.solarthing.rest.graphql.SimpleQueryHandler} accepts fragmentIds for most of its methods now. + */ +@Deprecated public class FragmentFilter implements PacketFilter { private final int fragmentId; diff --git a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SchemaConstants.java b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SchemaConstants.java index 5f3f093f..a1cec6b1 100644 --- a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SchemaConstants.java +++ b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SchemaConstants.java @@ -11,6 +11,7 @@ public final class SchemaConstants { public static final String DESCRIPTION_TO = "The maximum time in milliseconds since the epoch to get data from."; public static final String DESCRIPTION_OPTIONAL_SOURCE = "The Source ID to include packets from, or null to include packets from multiple sources."; public static final String DESCRIPTION_REQUIRED_SOURCE = "The Source ID to include packets from."; - public static final String DESCRIPTION_FRAGMENT_ID = "The fragment ID to include data from."; + public static final String DESCRIPTION_OPTIONAL_FRAGMENT_ID = "The fragment ID to include data from."; + public static final String DESCRIPTION_REQUIRED_FRAGMENT_ID = "The fragment ID to include data from."; } diff --git a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLDailyService.java b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLDailyService.java index c3a5c485..81118597 100644 --- a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLDailyService.java +++ b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLDailyService.java @@ -280,6 +280,7 @@ public SolarThingFullDayStatusQuery queryFullDay( @GraphQLArgument(name = "from", description = "The epoch millis value that will be used to determine the starting day. Set to null to guarantee a query of a single day.") @Nullable Long from, @GraphQLArgument(name = "to", description = "The epoch millis value that will be used to determine the ending day.") long to, @GraphQLArgument(name = "sourceId", description = DESCRIPTION_OPTIONAL_SOURCE) @Nullable String sourceId, + @GraphQLArgument(name = "fragmentId", description = DESCRIPTION_OPTIONAL_FRAGMENT_ID) @Nullable Integer fragmentId, @GraphQLArgument(name = "useCache", defaultValue = "false") boolean useCache){ LocalDate fromDate = Instant.ofEpochMilli(from == null ? to : from).atZone(zoneId).toLocalDate(); @@ -292,7 +293,8 @@ public SolarThingFullDayStatusQuery queryFullDay( List packets = simpleQueryHandler.queryStatus( queryStart, queryEnd, - sourceId + sourceId, + fragmentId ); return new SimpleSolarThingFullDayStatusQuery(new BasicPacketGetter(packets, PacketFilter.KEEP_ALL), simpleQueryHandler.sortPackets(packets, sourceId)); } diff --git a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLFXService.java b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLFXService.java index 10081d06..29b8be01 100644 --- a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLFXService.java +++ b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLFXService.java @@ -53,7 +53,8 @@ public List> queryFXCharging( } long startTime = from - 3 * 60 * 60 * 1000; // 3 hours back - List packets = simpleQueryHandler.queryStatus(startTime, to, null); + // Don't filter on fragmentId here. Even though we are provided with one, we still want data from other fragments as those might have temperature sensor data + List packets = simpleQueryHandler.queryStatus(startTime, to, null, null); // We make masterIdIgnoreDistance null because we will only be using fragmentId as the master fragment ID Map> map = PacketGroups.sortPackets( // separate based on source ID diff --git a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLService.java b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLService.java index 62549c05..2cec037a 100644 --- a/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLService.java +++ b/server/src/main/java/me/retrodaredevil/solarthing/rest/graphql/service/SolarThingGraphQLService.java @@ -73,16 +73,34 @@ public SolarThingGraphQLService(SimpleQueryHandler simpleQueryHandler) { @GraphQLQuery(description = "Query status packets in the specified time range.") public @NotNull SolarThingStatusQuery queryStatus( @GraphQLArgument(name = "from", description = DESCRIPTION_FROM) long from, @GraphQLArgument(name = "to", description = DESCRIPTION_TO) long to, - @GraphQLArgument(name = "sourceId", description = DESCRIPTION_OPTIONAL_SOURCE) @Nullable String sourceId){ - List packets = simpleQueryHandler.queryStatus(from, to, sourceId); + @GraphQLArgument(name = "sourceId", description = DESCRIPTION_OPTIONAL_SOURCE) @Nullable String sourceId, + @GraphQLArgument(name = "fragmentId", description = DESCRIPTION_OPTIONAL_FRAGMENT_ID) @Nullable Integer fragmentId + ){ + List packets = simpleQueryHandler.queryStatus(from, to, sourceId, fragmentId); return new SolarThingStatusQuery(new BasicPacketGetter(packets, PacketFilter.KEEP_ALL), simpleQueryHandler.sortPackets(packets, sourceId), simpleQueryHandler); } + @GraphQLQuery(description = "Queries status packets in the specified time range while only including the specified identifier in the specified fragment") + public @NotNull SolarThingStatusQuery queryStatusIdentifier( + @GraphQLArgument(name = "from", description = DESCRIPTION_FROM) long from, @GraphQLArgument(name = "to", description = DESCRIPTION_TO) long to, + @GraphQLArgument(name = "fragmentId", description = DESCRIPTION_REQUIRED_FRAGMENT_ID) int fragmentId, + @GraphQLArgument(name = "identifier") @NotNull String identifierRepresentation, + @GraphQLArgument(name = "acceptSupplementary", defaultValue = "true") boolean acceptSupplementary + ) { + // null source ID because each fragment ID is unique, even over multiple sources + List packets = simpleQueryHandler.queryStatus(from, to, null, fragmentId); + return new SolarThingStatusQuery( + new BasicPacketGetter(packets, new IdentifierFilter(identifierRepresentation, acceptSupplementary)), + simpleQueryHandler.sortPackets(packets, null), + simpleQueryHandler + ); + } @GraphQLQuery(description = "Query the latest collection of status packets on or before the 'to' timestamp.") public @NotNull SolarThingStatusQuery queryStatusLast( @GraphQLArgument(name = "to", description = DESCRIPTION_TO) long to, @GraphQLArgument(name = "sourceId", description = DESCRIPTION_OPTIONAL_SOURCE) @Nullable String sourceId, + @GraphQLArgument(name = "fragmentId", description = DESCRIPTION_OPTIONAL_FRAGMENT_ID) @Nullable Integer fragmentId, @GraphQLArgument(name = "reversed", defaultValue = "false", description = "If set to true, the returned list will be reversed. Useful to set to true if you want the very latest packet to be first.") boolean reversed){ - List packets = simpleQueryHandler.queryStatus(to - SolarThingConstants.LATEST_PACKETS_DURATION.toMillis(), to, sourceId); + List packets = simpleQueryHandler.queryStatus(to - SolarThingConstants.LATEST_PACKETS_DURATION.toMillis(), to, sourceId, fragmentId); List lastPackets = new ArrayList<>(); for(List packetGroups : PacketGroups.mapFragments(packets).values()) { lastPackets.add(packetGroups.get(packetGroups.size() - 1)); @@ -98,31 +116,33 @@ public SolarThingGraphQLService(SimpleQueryHandler simpleQueryHandler) { public @NotNull SolarThingEventQuery queryEvent( @GraphQLArgument(name = "from", description = DESCRIPTION_FROM) long from, @GraphQLArgument(name = "to", description = DESCRIPTION_TO) long to, @GraphQLArgument(name = "sourceId", description = DESCRIPTION_OPTIONAL_SOURCE) @Nullable String sourceId, + @GraphQLArgument(name = "fragmentId", description = DESCRIPTION_OPTIONAL_FRAGMENT_ID) @Nullable Integer fragmentId, @GraphQLArgument(name = "includeUnknownChangePackets", defaultValue = "false", description = DESCRIPTION_INCLUDE_UNKNOWN_CHANGE) boolean includeUnknownChangePackets ) { - return new SolarThingEventQuery(new BasicPacketGetter(simpleQueryHandler.queryEvent(from, to, sourceId), new UnknownChangePacketsFilter(includeUnknownChangePackets))); + return new SolarThingEventQuery(new BasicPacketGetter(simpleQueryHandler.queryEvent(from, to, sourceId, fragmentId), new UnknownChangePacketsFilter(includeUnknownChangePackets))); } @GraphQLQuery(description = "Queries events in the specified time range while only including the specified identifier in the specified fragment") public @NotNull SolarThingEventQuery queryEventIdentifier( @GraphQLArgument(name = "from", description = DESCRIPTION_FROM) long from, @GraphQLArgument(name = "to", description = DESCRIPTION_TO) long to, - @GraphQLArgument(name = "fragmentId", description = DESCRIPTION_FRAGMENT_ID) int fragmentId, + @GraphQLArgument(name = "fragmentId", description = DESCRIPTION_REQUIRED_FRAGMENT_ID) int fragmentId, @GraphQLArgument(name = "identifier") @NotNull String identifierRepresentation, @GraphQLArgument(name = "includeUnknownChangePackets", defaultValue = "false", description = DESCRIPTION_INCLUDE_UNKNOWN_CHANGE) boolean includeUnknownChangePackets, @GraphQLArgument(name = "acceptSupplementary", defaultValue = "true") boolean acceptSupplementary ) { return new SolarThingEventQuery(new BasicPacketGetter( - simpleQueryHandler.queryEvent(from, to, null), // null source ID because each fragment ID is unique, even over multiple sources - new PacketFilterMultiplexer(Arrays.asList(new FragmentFilter(fragmentId), new IdentifierFilter(identifierRepresentation, acceptSupplementary), new UnknownChangePacketsFilter(includeUnknownChangePackets))) + simpleQueryHandler.queryEvent(from, to, null, fragmentId), // null source ID because each fragment ID is unique, even over multiple sources + new PacketFilterMultiplexer(Arrays.asList(new IdentifierFilter(identifierRepresentation, acceptSupplementary), new UnknownChangePacketsFilter(includeUnknownChangePackets))) )); } - @GraphQLQuery(description = "Queries events in the specified time range while only including the specified fragment") + @Deprecated(forRemoval = true) + @GraphQLQuery(description = "Queries events in the specified time range while only including the specified fragment. Deprecated: Use queryEvent instead") public @NotNull SolarThingEventQuery queryEventFragment( @GraphQLArgument(name = "from", description = DESCRIPTION_FROM) long from, @GraphQLArgument(name = "to", description = DESCRIPTION_TO) long to, - @GraphQLArgument(name = "fragmentId", description = DESCRIPTION_FRAGMENT_ID) int fragmentId, + @GraphQLArgument(name = "fragmentId", description = DESCRIPTION_REQUIRED_FRAGMENT_ID) int fragmentId, @GraphQLArgument(name = "includeUnknownChangePackets", defaultValue = "false", description = DESCRIPTION_INCLUDE_UNKNOWN_CHANGE) boolean includeUnknownChangePackets ) { return new SolarThingEventQuery(new BasicPacketGetter( - simpleQueryHandler.queryEvent(from, to, null), + simpleQueryHandler.queryEvent(from, to, null, null), // pass null as a fragmentId here because this is deprecated and might as well not change anything until this is removed new PacketFilterMultiplexer(Arrays.asList(new FragmentFilter(fragmentId), new UnknownChangePacketsFilter(includeUnknownChangePackets))) )); }