Skip to content

Commit 9d3104e

Browse files
authored
Merge pull request #2038 from steve-community/improve-transaction-getdetails
Improve reading logic for transaction meter values
2 parents 0a396b9 + c1b48e5 commit 9d3104e

2 files changed

Lines changed: 77 additions & 51 deletions

File tree

src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java

Lines changed: 29 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import de.rwth.idsg.steve.utils.TransactionStopServiceHelper;
2727
import de.rwth.idsg.steve.web.dto.QueryPeriodType;
2828
import de.rwth.idsg.steve.web.dto.TransactionQueryForm;
29-
import jooq.steve.db.tables.records.ConnectorMeterValueRecord;
3029
import jooq.steve.db.tables.records.TransactionRecord;
3130
import jooq.steve.db.tables.records.TransactionStartRecord;
3231
import lombok.RequiredArgsConstructor;
@@ -35,17 +34,13 @@
3534
import org.joda.time.DateTime;
3635
import org.jooq.Condition;
3736
import org.jooq.DSLContext;
38-
import org.jooq.Field;
3937
import org.jooq.Result;
40-
import org.jooq.SelectQuery;
41-
import org.jooq.Table;
4238
import org.springframework.stereotype.Repository;
4339

4440
import java.io.Writer;
4541
import java.util.ArrayList;
4642
import java.util.List;
4743

48-
import static de.rwth.idsg.steve.utils.CustomDSL.DATE_TIME_TYPE;
4944
import static de.rwth.idsg.steve.utils.CustomDSL.getTimeCondition;
5045
import static jooq.steve.db.Tables.USER_OCPP_TAG;
5146
import 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)

src/test/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImplIT.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.io.StringWriter;
3131

3232
import static jooq.steve.db.tables.Connector.CONNECTOR;
33+
import static jooq.steve.db.tables.ConnectorMeterValue.CONNECTOR_METER_VALUE;
3334
import static jooq.steve.db.tables.TransactionStart.TRANSACTION_START;
3435

3536
/**
@@ -89,9 +90,56 @@ public void getDetails() {
8990
Assertions.assertEquals(txId, details.getTransaction().getId());
9091
}
9192

93+
@Test
94+
public void getDetailsForZombieTransactionDoesNotIncludeNextTransactionStartValue() {
95+
Integer connectorPk = dslContext.select(CONNECTOR.CONNECTOR_PK)
96+
.from(CONNECTOR)
97+
.where(CONNECTOR.CHARGE_BOX_ID.eq(KNOWN_CHARGE_BOX_ID))
98+
.and(CONNECTOR.CONNECTOR_ID.eq(1))
99+
.fetchOne(CONNECTOR.CONNECTOR_PK);
100+
101+
DateTime firstStart = DateTime.now().minusMinutes(20);
102+
DateTime nextStart = firstStart.plusMinutes(10);
103+
104+
Integer firstTxId = insertTransactionStart(connectorPk, firstStart, "100");
105+
insertTransactionStart(connectorPk, nextStart, "200");
106+
107+
dslContext.insertInto(CONNECTOR_METER_VALUE)
108+
.set(CONNECTOR_METER_VALUE.CONNECTOR_PK, connectorPk)
109+
.set(CONNECTOR_METER_VALUE.VALUE_TIMESTAMP, firstStart.plusMinutes(5))
110+
.set(CONNECTOR_METER_VALUE.VALUE, "150")
111+
.set(CONNECTOR_METER_VALUE.UNIT, "Wh")
112+
.execute();
113+
114+
dslContext.insertInto(CONNECTOR_METER_VALUE)
115+
.set(CONNECTOR_METER_VALUE.CONNECTOR_PK, connectorPk)
116+
.set(CONNECTOR_METER_VALUE.VALUE_TIMESTAMP, nextStart)
117+
.set(CONNECTOR_METER_VALUE.VALUE, "200")
118+
.set(CONNECTOR_METER_VALUE.UNIT, "Wh")
119+
.execute();
120+
121+
var details = assertNoDatabaseException(() -> repository.getDetails(firstTxId));
122+
123+
Assertions.assertEquals(1, details.getValues().size());
124+
Assertions.assertEquals("150", details.getValues().getFirst().getValue());
125+
Assertions.assertEquals(nextStart, details.getNextTransactionStart().getStartTimestamp());
126+
}
127+
92128
@Test
93129
public void getStoppedTransactions() {
94130
var result = assertNoDatabaseException(() -> repository.getStoppedTransactions(DateTime.now().minusDays(1), DateTime.now()));
95131
Assertions.assertNotNull(result);
96132
}
133+
134+
private Integer insertTransactionStart(Integer connectorPk, DateTime startTimestamp, String startValue) {
135+
return dslContext.insertInto(TRANSACTION_START)
136+
.set(TRANSACTION_START.EVENT_TIMESTAMP, startTimestamp)
137+
.set(TRANSACTION_START.CONNECTOR_PK, connectorPk)
138+
.set(TRANSACTION_START.ID_TAG, KNOWN_OCPP_TAG)
139+
.set(TRANSACTION_START.START_TIMESTAMP, startTimestamp)
140+
.set(TRANSACTION_START.START_VALUE, startValue)
141+
.returning(TRANSACTION_START.TRANSACTION_PK)
142+
.fetchOne()
143+
.getTransactionPk();
144+
}
97145
}

0 commit comments

Comments
 (0)