Skip to content

Commit a4d43a2

Browse files
committed
fix(shape-edits): update trip#shape_id if pattern value changes
refs ibi-group/datatools-ui#385; refs #109
1 parent ce68dc6 commit a4d43a2

File tree

2 files changed

+69
-26
lines changed

2 files changed

+69
-26
lines changed

src/main/java/com/conveyal/gtfs/loader/JdbcTableWriter.java

+66-25
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ public JdbcTableWriter (
7777

7878
// TODO: verify specTable.name is ok to use for constructing dynamic sql statements
7979
this.specTable = specTable;
80+
// Below is a bit messy because the connection field on this class is set to final and we cannot set this to
81+
// the optionally passed-in connection with the ternary statement unless connection1 already exists.
8082
Connection connection1;
8183
try {
82-
connection1 = dataSource.getConnection();
84+
connection1 = this.dataSource.getConnection();
8385
} catch (SQLException e) {
8486
e.printStackTrace();
8587
connection1 = null;
@@ -170,11 +172,34 @@ public String update(Integer id, String json, boolean autoCommit) throws SQLExce
170172
updateChildTable(childEntitiesArray, entityId, isCreating, referencingTable, connection);
171173
}
172174
}
173-
// Iterate over table's fields and apply linked values to any tables
174-
if ("routes".equals(specTable.name)) {
175-
updateLinkedFields(specTable, jsonObject, "trips", "route_id", "wheelchair_accessible");
176-
} else if ("patterns".equals(specTable.name)) {
177-
updateLinkedFields(specTable, jsonObject, "trips", "pattern_id", "direction_id");
175+
// Iterate over table's fields and apply linked values to any tables. This is to account for "exemplar"
176+
// fields that exist in one place in our tables, but are duplicated in GTFS. For example, we have a
177+
// Route#wheelchair_accessible field, which is used to set the Trip#wheelchair_accessible values for all
178+
// trips on a route.
179+
// NOTE: pattern_stops linked fields are updated in the updateChildTable method.
180+
switch (specTable.name) {
181+
case "routes":
182+
updateLinkedFields(
183+
specTable,
184+
jsonObject,
185+
"trips",
186+
"route_id",
187+
"wheelchair_accessible"
188+
);
189+
break;
190+
case "patterns":
191+
updateLinkedFields(
192+
specTable,
193+
jsonObject,
194+
"trips",
195+
"pattern_id",
196+
"direction_id", "shape_id"
197+
);
198+
break;
199+
default:
200+
LOG.debug("No linked fields to update.");
201+
// Do nothing.
202+
break;
178203
}
179204
if (autoCommit) {
180205
// If nothing failed up to this point, it is safe to assume there were no problems updating/creating the
@@ -205,6 +230,7 @@ public String update(Integer id, String json, boolean autoCommit) throws SQLExce
205230
* the related table (e.g., pattern_stop#timepoint should update all of its related stop_times).
206231
*/
207232
private void updateLinkedFields(Table referenceTable, ObjectNode jsonObject, String tableName, String keyField, String ...fieldNames) throws SQLException {
233+
boolean updatingStopTimes = "stop_times".equals(tableName);
208234
// Collect fields, the JSON values for these fields, and the strings to add to the prepared statement into Lists.
209235
List<Field> fields = new ArrayList<>();
210236
List<JsonNode> values = new ArrayList<>();
@@ -216,12 +242,17 @@ private void updateLinkedFields(Table referenceTable, ObjectNode jsonObject, Str
216242
}
217243
String setFields = String.join(", ", fieldStrings);
218244
// If updating stop_times, use a more complex query that joins trips to stop_times in order to match on pattern_id
219-
boolean updatingStopTimes = "stop_times".equals(tableName);
220245
Field orderField = updatingStopTimes ? referenceTable.getFieldForName(referenceTable.getOrderFieldName()) : null;
221246
String sql = updatingStopTimes
222-
? String.format("update %s.stop_times st set %s from %s.trips t " +
223-
"where st.trip_id = t.trip_id AND t.%s = ? AND st.%s = ?",
224-
tablePrefix, setFields, tablePrefix, keyField, orderField.name)
247+
? String.format(
248+
"update %s.stop_times st set %s from %s.trips t " +
249+
"where st.trip_id = t.trip_id AND t.%s = ? AND st.%s = ?",
250+
tablePrefix,
251+
setFields,
252+
tablePrefix,
253+
keyField,
254+
orderField.name
255+
)
225256
: String.format("update %s.%s set %s where %s = ?", tablePrefix, tableName, setFields, keyField);
226257
// Prepare the statement and set statement parameters
227258
PreparedStatement statement = connection.prepareStatement(sql);
@@ -371,8 +402,6 @@ private void updateChildTable(ArrayNode subEntities, Integer id, boolean isCreat
371402
// Get parent entity's key value
372403
keyValue = getValueForId(id, keyField.name, tablePrefix, specTable, connection);
373404
String childTableName = String.join(".", tablePrefix, subTable.name);
374-
// FIXME: add check for pattern stop consistency.
375-
// FIXME: re-order stop times if pattern stop order changes.
376405
// Reconciling pattern stops MUST happen before original pattern stops are deleted in below block (with
377406
// getUpdateReferencesSql)
378407
if ("pattern_stops".equals(subTable.name)) {
@@ -390,15 +419,14 @@ private void updateChildTable(ArrayNode subEntities, Integer id, boolean isCreat
390419
}
391420
reconcilePatternStops(keyValue, newPatternStops, connection);
392421
}
393-
// FIXME: allow shapes to be updated on pattern geometry change.
394422
if (!isCreatingNewEntity) {
395423
// Delete existing sub-entities for given entity ID if the parent entity is not being newly created.
396424
String deleteSql = getUpdateReferencesSql(SqlMethod.DELETE, childTableName, keyField, keyValue, null);
397425
LOG.info(deleteSql);
398426
Statement statement = connection.createStatement();
399427
// FIXME: Use copy on update for a pattern's shape instead of deleting the previous shape and replacing it.
400-
// This would better account for GTFS data loaded from a file where multiple patterns reference a single
401-
// shape.
428+
// This would better account for GTFS data loaded from a file where multiple patterns reference a single
429+
// shape.
402430
int result = statement.executeUpdate(deleteSql);
403431
LOG.info("Deleted {} {}", result, subTable.name);
404432
// FIXME: are there cases when an update should not return zero?
@@ -436,7 +464,7 @@ private void updateChildTable(ArrayNode subEntities, Integer id, boolean isCreat
436464
// If handling first iteration, create the prepared statement (later iterations will add to batch).
437465
insertStatement = createPreparedUpdate(id, true, subEntity, subTable, connection, true);
438466
}
439-
// Update linked stop times fields for updated pattern stop (e.g., timepoint, pickup/drop off type).
467+
// Update linked stop times fields for each updated pattern stop (e.g., timepoint, pickup/drop off type).
440468
if ("pattern_stops".equals(subTable.name)) {
441469
updateLinkedFields(
442470
subTable,
@@ -610,8 +638,13 @@ else if (i == newStops.size() - 1 || !originalStopIds.get(i).equals(newStops.get
610638
}
611639
// Increment sequences for stops that follow the inserted location (including the stop at the changed index).
612640
// NOTE: This should happen before the blank stop time insertion for logical consistency.
613-
String updateSql = String.format("update %s.stop_times set stop_sequence = stop_sequence + 1 from %s.trips where stop_sequence >= %d AND %s",
614-
tablePrefix, tablePrefix, differenceLocation, joinToTrips);
641+
String updateSql = String.format(
642+
"update %s.stop_times set stop_sequence = stop_sequence + 1 from %s.trips where stop_sequence >= %d AND %s",
643+
tablePrefix,
644+
tablePrefix,
645+
differenceLocation,
646+
joinToTrips
647+
);
615648
LOG.info(updateSql);
616649
PreparedStatement updateStatement = connection.prepareStatement(updateSql);
617650
int updated = updateStatement.executeUpdate();
@@ -638,13 +671,23 @@ else if (originalStopIds.size() == newStops.size() + 1) {
638671
}
639672
}
640673
// Delete stop at difference location
641-
String deleteSql = String.format("delete from %s.stop_times using %s.trips where stop_sequence = %d AND %s",
642-
tablePrefix, tablePrefix, differenceLocation, joinToTrips);
674+
String deleteSql = String.format(
675+
"delete from %s.stop_times using %s.trips where stop_sequence = %d AND %s",
676+
tablePrefix,
677+
tablePrefix,
678+
differenceLocation,
679+
joinToTrips
680+
);
643681
LOG.info(deleteSql);
644682
PreparedStatement deleteStatement = connection.prepareStatement(deleteSql);
645683
// Decrement all stops with sequence greater than difference location
646-
String updateSql = String.format("update %s.stop_times set stop_sequence = stop_sequence - 1 from %s.trips where stop_sequence > %d AND %s",
647-
tablePrefix, tablePrefix, differenceLocation, joinToTrips);
684+
String updateSql = String.format(
685+
"update %s.stop_times set stop_sequence = stop_sequence - 1 from %s.trips where stop_sequence > %d AND %s",
686+
tablePrefix,
687+
tablePrefix,
688+
differenceLocation,
689+
joinToTrips
690+
);
648691
LOG.info(updateSql);
649692
PreparedStatement updateStatement = connection.prepareStatement(updateSql);
650693
int deleted = deleteStatement.executeUpdate();
@@ -764,9 +807,7 @@ else if (originalStopIds.size() < newStops.size()) {
764807
insertBlankStopTimes(tripsForPattern, newStops, firstDifferentIndex, stopsToInsert, connection);
765808
}
766809
// ANY OTHER TYPE OF MODIFICATION IS NOT SUPPORTED
767-
else {
768-
throw new IllegalStateException(RECONCILE_STOPS_ERROR_MSG);
769-
}
810+
else throw new IllegalStateException(RECONCILE_STOPS_ERROR_MSG);
770811
}
771812

772813
/**

src/main/java/com/conveyal/gtfs/loader/Table.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ public Table (String name, Class<? extends Entity> entityClass, Requirement requ
163163
new ColorField("route_text_color", OPTIONAL),
164164
// Editor fields below.
165165
new ShortField("publicly_visible", EDITOR, 1),
166+
// wheelchair_accessible is an exemplar field applied to all trips on a route.
166167
new ShortField("wheelchair_accessible", EDITOR, 2).permitEmptyValue(),
167168
new IntegerField("route_sort_order", OPTIONAL, 0, Integer.MAX_VALUE),
168169
// Status values are In progress (0), Pending approval (1), and Approved (2).
@@ -196,7 +197,8 @@ public Table (String name, Class<? extends Entity> entityClass, Requirement requ
196197
new StringField("pattern_id", REQUIRED),
197198
new StringField("route_id", REQUIRED).isReferenceTo(ROUTES),
198199
new StringField("name", OPTIONAL),
199-
// Editor-specific fields
200+
// Editor-specific fields.
201+
// direction_id and shape_id are exemplar fields applied to all trips for a pattern.
200202
new ShortField("direction_id", EDITOR, 1),
201203
new ShortField("use_frequency", EDITOR, 1),
202204
new StringField("shape_id", EDITOR).isReferenceTo(SHAPES)

0 commit comments

Comments
 (0)