2626import de .rwth .idsg .steve .utils .TransactionStopServiceHelper ;
2727import de .rwth .idsg .steve .web .dto .QueryPeriodType ;
2828import de .rwth .idsg .steve .web .dto .TransactionQueryForm ;
29- import jooq .steve .db .tables .records .ConnectorMeterValueRecord ;
3029import jooq .steve .db .tables .records .TransactionRecord ;
3130import jooq .steve .db .tables .records .TransactionStartRecord ;
3231import lombok .RequiredArgsConstructor ;
3534import org .joda .time .DateTime ;
3635import org .jooq .Condition ;
3736import org .jooq .DSLContext ;
38- import org .jooq .Field ;
3937import org .jooq .Result ;
40- import org .jooq .SelectQuery ;
41- import org .jooq .Table ;
4238import org .springframework .stereotype .Repository ;
4339
4440import java .io .Writer ;
4541import java .util .ArrayList ;
4642import java .util .List ;
4743
48- import static de .rwth .idsg .steve .utils .CustomDSL .DATE_TIME_TYPE ;
4944import static de .rwth .idsg .steve .utils .CustomDSL .getTimeCondition ;
5045import static jooq .steve .db .Tables .USER_OCPP_TAG ;
5146import static jooq .steve .db .tables .ChargeBox .CHARGE_BOX ;
@@ -205,7 +200,9 @@ public TransactionDetails getDetails(int transactionPk) {
205200 // the last active transaction
206201 timestampCondition = CONNECTOR_METER_VALUE .VALUE_TIMESTAMP .greaterOrEqual (startTimestamp );
207202 } else {
208- timestampCondition = CONNECTOR_METER_VALUE .VALUE_TIMESTAMP .between (startTimestamp , nextTx .getStartTimestamp ());
203+ // startTimestamp is inclusive, nextTx.startTimestamp is exclusive
204+ timestampCondition = CONNECTOR_METER_VALUE .VALUE_TIMESTAMP .greaterOrEqual (startTimestamp )
205+ .and (CONNECTOR_METER_VALUE .VALUE_TIMESTAMP .lessThan (nextTx .getStartTimestamp ()));
209206 }
210207 } else {
211208 // finished transaction
@@ -216,58 +213,39 @@ public TransactionDetails getDetails(int transactionPk) {
216213 Condition unitCondition = CONNECTOR_METER_VALUE .UNIT .isNull ()
217214 .or (CONNECTOR_METER_VALUE .UNIT .in ("" , UnitOfMeasure .WH .value (), UnitOfMeasure .K_WH .value ()));
218215
219- // Case 1: Ideal and most accurate case. Station sends meter values with transaction id set.
220- //
221- SelectQuery <ConnectorMeterValueRecord > transactionQuery =
222- ctx .selectFrom (CONNECTOR_METER_VALUE )
223- .where (CONNECTOR_METER_VALUE .TRANSACTION_PK .eq (transactionPk ))
224- .and (unitCondition )
225- .getQuery ();
216+ var connectorPkQuery = ctx .select (CONNECTOR .CONNECTOR_PK )
217+ .from (CONNECTOR )
218+ .where (CONNECTOR .CHARGE_BOX_ID .eq (chargeBoxId ))
219+ .and (CONNECTOR .CONNECTOR_ID .eq (connectorId ));
226220
227- // Case 2: Fall back to filtering according to time windows
228- //
229- SelectQuery <ConnectorMeterValueRecord > timestampQuery =
230- ctx .selectFrom (CONNECTOR_METER_VALUE )
231- .where (CONNECTOR_METER_VALUE .CONNECTOR_PK .eq (ctx .select (CONNECTOR .CONNECTOR_PK )
232- .from (CONNECTOR )
233- .where (CONNECTOR .CHARGE_BOX_ID .eq (chargeBoxId ))
234- .and (CONNECTOR .CONNECTOR_ID .eq (connectorId ))))
235- .and (timestampCondition )
236- .and (unitCondition )
237- .getQuery ();
238-
239- // Actually, either case 1 applies or 2. If we retrieved values using 1, case 2 is should not be
240- // executed (best case). In worst case (1 returns empty list and we fall back to case 2) though,
241- // we make two db calls. Alternatively, we can pass both queries in one go, and make the db work.
221+ // Case 1: Ideal and most accurate case. Station sends meter values with transaction id set.
242222 //
243- // UNION removes all duplicate records
223+ // Case 2: Fall back to filtering according to time windows. Timestamp fallback only considers rows
224+ // not explicitly assigned to another transaction, because such rows can otherwise leak across
225+ // zombie-transaction boundaries.
244226 //
245- Table <ConnectorMeterValueRecord > t1 = transactionQuery .union (timestampQuery ).asTable ("t1" );
246-
247- Field <DateTime > dateTimeField = t1 .field (2 , DATE_TIME_TYPE );
227+ var selectionCriteria = CONNECTOR_METER_VALUE .TRANSACTION_PK .eq (transactionPk )
228+ .or (timestampCondition
229+ .and (CONNECTOR_METER_VALUE .TRANSACTION_PK .isNull ()
230+ .and (CONNECTOR_METER_VALUE .CONNECTOR_PK .eq (connectorPkQuery ))
231+ )
232+ );
248233
249234 List <TransactionDetails .MeterValues > values =
250- ctx .select (
251- dateTimeField ,
252- t1 .field (3 , String .class ),
253- t1 .field (4 , String .class ),
254- t1 .field (5 , String .class ),
255- t1 .field (6 , String .class ),
256- t1 .field (7 , String .class ),
257- t1 .field (8 , String .class ),
258- t1 .field (9 , String .class ))
259- .from (t1 )
260- .orderBy (dateTimeField )
235+ ctx .selectFrom (CONNECTOR_METER_VALUE )
236+ .where (unitCondition )
237+ .and (selectionCriteria )
238+ .orderBy (CONNECTOR_METER_VALUE .VALUE_TIMESTAMP )
261239 .fetch ()
262240 .map (r -> TransactionDetails .MeterValues .builder ()
263- .valueTimestamp (r .value1 ())
264- .value (r .value2 ())
265- .readingContext (r .value3 ())
266- .format (r .value4 ())
267- .measurand (r .value5 ())
268- .location (r .value6 ())
269- .unit (r .value7 ())
270- .phase (r .value8 ())
241+ .valueTimestamp (r .getValueTimestamp ())
242+ .value (r .getValue ())
243+ .readingContext (r .getReadingContext ())
244+ .format (r .getFormat ())
245+ .measurand (r .getMeasurand ())
246+ .location (r .getLocation ())
247+ .unit (r .getUnit ())
248+ .phase (r .getPhase ())
271249 .build ())
272250 .stream ()
273251 .filter (TransactionStopServiceHelper ::isEnergyValue )
0 commit comments